diff --git a/.github/workflows/pull-request-from-branch-check.yaml b/.github/workflows/pull-request-from-branch-check.yaml new file mode 100644 index 00000000..a40f09fa --- /dev/null +++ b/.github/workflows/pull-request-from-branch-check.yaml @@ -0,0 +1,18 @@ +name: Main Branch Protection + +on: + pull_request: + branches: + - master + +jobs: + check-branch: + runs-on: ubuntu-latest + steps: + - name: Check branch + run: | + if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]]; + then + echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch" + exit 1 + fi diff --git a/.github/workflows/pull-request-lint-check.yaml b/.github/workflows/pull-request-lint-check.yaml new file mode 100644 index 00000000..0c04e87a --- /dev/null +++ b/.github/workflows/pull-request-lint-check.yaml @@ -0,0 +1,20 @@ +name: Lint Check + +on: + pull_request: + branches: + - never + +jobs: + lint-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install dependencies + run: npm i + + - name: Linting + run: npm run lint diff --git a/appinfo/info.xml b/appinfo/info.xml index e9496ccd..b2573a89 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.43 + 0.6.52 agpl Conduction Acato diff --git a/appinfo/routes.php b/appinfo/routes.php index 427b3af4..b9baa786 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -6,6 +6,7 @@ 'publications' => ['url' => '/api/publications'], 'organizations' => ['url' => '/api/organizations'], 'themes' => ['url' => '/api/themes'], + 'pages' => ['url' => '/api/pages'], 'attachments' => ['url' => '/api/attachments'], 'catalogi' => ['url' => '/api/catalogi'], 'listings' => ['url' => '/api/listings'], @@ -37,6 +38,8 @@ ['name' => 'search#publication', 'url' => '/api/search/publications/{publicationId}', 'verb' => 'GET', 'requirements' => ['publicationId' => '[^/]+']], ['name' => 'search#attachments', 'url' => '/api/search/publications/{publicationId}/attachments', 'verb' => 'GET', 'requirements' => ['publicationId' => '[^/]+']], ['name' => 'search#themes', 'url' => '/api/search/themes', 'verb' => 'GET'], - ['name' => 'search#theme', 'url' => '/api/search/themes/{themeId}', 'verb' => 'GET', 'requirements' => ['themeId' => '\d+']] + ['name' => 'search#theme', 'url' => '/api/search/themes/{themeId}', 'verb' => 'GET', 'requirements' => ['themeId' => '\d+']], + ['name' => 'search#pages', 'url' => '/api/public/pages', 'verb' => 'GET'], + ['name' => 'search#page', 'url' => '/api/public/pages/{pageSlug}', 'verb' => 'GET', 'requirements' => ['pageId' => '.+']] ] ]; diff --git a/composer.json b/composer.json index 89c318a1..9672442e 100644 --- a/composer.json +++ b/composer.json @@ -44,8 +44,9 @@ "adbario/php-dot-notation": "^3.3.0", "bamarni/composer-bin-plugin": "^1.8", "elasticsearch/elasticsearch": "^v8.14.0", - "guzzlehttp/guzzle": "^7.0", + "guzzlehttp/guzzle": "^7.0", "mpdf/mpdf": "^8.2", + "opis/json-schema": "^2.3", "symfony/twig-bundle": "^6.4", "symfony/uid": "^6.4" }, diff --git a/composer.lock b/composer.lock index 7284d015..917ed994 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "93ceb09bbb9b85fb7d77d68d6d6eefe1", + "content-hash": "bd1fa2147c63c25a4c1759758bcf0fbb", "packages": [ { "name": "adbario/php-dot-notation", @@ -119,20 +119,21 @@ }, { "name": "elastic/transport", - "version": "v8.8.0", + "version": "v8.10.0", "source": { "type": "git", "url": "https://github.com/elastic/elastic-transport-php.git", - "reference": "cdf9f63a16ec6bfb4c881ab89aa0e2a61fb7c20b" + "reference": "8be37d679637545e50b1cea9f8ee903888783021" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elastic-transport-php/zipball/cdf9f63a16ec6bfb4c881ab89aa0e2a61fb7c20b", - "reference": "cdf9f63a16ec6bfb4c881ab89aa0e2a61fb7c20b", + "url": "https://api.github.com/repos/elastic/elastic-transport-php/zipball/8be37d679637545e50b1cea9f8ee903888783021", + "reference": "8be37d679637545e50b1cea9f8ee903888783021", "shasum": "" }, "require": { "composer-runtime-api": "^2.0", + "open-telemetry/api": "^1.0", "php": "^7.4 || ^8.0", "php-http/discovery": "^1.14", "php-http/httplug": "^2.3", @@ -143,9 +144,11 @@ }, "require-dev": { "nyholm/psr7": "^1.5", + "open-telemetry/sdk": "^1.0", "php-http/mock-client": "^1.5", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "symfony/http-client": "^5.4" }, "type": "library", "autoload": { @@ -168,26 +171,26 @@ ], "support": { "issues": "https://github.com/elastic/elastic-transport-php/issues", - "source": "https://github.com/elastic/elastic-transport-php/tree/v8.8.0" + "source": "https://github.com/elastic/elastic-transport-php/tree/v8.10.0" }, - "time": "2023-11-08T10:51:51+00:00" + "time": "2024-08-14T08:55:07+00:00" }, { "name": "elasticsearch/elasticsearch", - "version": "v8.14.0", + "version": "v8.15.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "bff3c3e2402f6a20449404637f91a5ae214eff46" + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/bff3c3e2402f6a20449404637f91a5ae214eff46", - "reference": "bff3c3e2402f6a20449404637f91a5ae214eff46", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b", "shasum": "" }, "require": { - "elastic/transport": "^8.8", + "elastic/transport": "^8.10", "guzzlehttp/guzzle": "^7.0", "php": "^7.4 || ^8.0", "psr/http-client": "^1.0", @@ -226,28 +229,28 @@ ], "support": { "issues": "https://github.com/elastic/elasticsearch-php/issues", - "source": "https://github.com/elastic/elasticsearch-php/tree/v8.14.0" + "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0" }, - "time": "2024-06-12T19:58:31+00:00" + "time": "2024-08-14T14:32:50+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -258,9 +261,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -338,7 +341,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -354,20 +357,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -375,7 +378,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -421,7 +424,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -437,20 +440,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -465,8 +468,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -537,7 +540,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -553,7 +556,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "mpdf/mpdf", @@ -788,6 +791,325 @@ ], "time": "2024-06-12T14:39:25+00:00" }, + { + "name": "open-telemetry/api", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "542064815d38a6df55af7957cd6f1d7d967c99c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/542064815d38a6df55af7957cd6f1d7d967c99c6", + "reference": "542064815d38a6df55af7957cd6f1d7d967c99c6", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.0", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1.x-dev" + }, + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-10-15T22:42:37+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-08-21T00:29:20+00:00" + }, + { + "name": "opis/json-schema", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.0", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.3.0" + }, + "time": "2022-01-08T20:38:03+00:00" + }, + { + "name": "opis/string", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.0.1" + }, + "time": "2022-01-14T15:42:23+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.100", @@ -840,16 +1162,16 @@ }, { "name": "php-http/discovery", - "version": "1.19.4", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "0700efda8d7526335132360167315fdab3aeb599" + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", - "reference": "0700efda8d7526335132360167315fdab3aeb599", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { @@ -913,22 +1235,22 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.19.4" + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2024-03-29T13:00:05+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { "name": "php-http/httplug", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", "shasum": "" }, "require": { @@ -970,9 +1292,9 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.4.0" + "source": "https://github.com/php-http/httplug/tree/2.4.1" }, - "time": "2023-04-14T15:10:03+00:00" + "time": "2024-09-23T11:39:58+00:00" }, { "name": "php-http/promise", @@ -1385,16 +1707,16 @@ }, { "name": "setasign/fpdi", - "version": "v2.6.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" + "reference": "09a816004fcee9ed3405bd164147e3fdbb79a56f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", - "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/09a816004fcee9ed3405bd164147e3fdbb79a56f", + "reference": "09a816004fcee9ed3405bd164147e3fdbb79a56f", "shasum": "" }, "require": { @@ -1445,7 +1767,7 @@ ], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.6.0" + "source": "https://github.com/Setasign/FPDI/tree/v2.6.1" }, "funding": [ { @@ -1453,20 +1775,20 @@ "type": "tidelift" } ], - "time": "2023-12-11T16:03:32+00:00" + "time": "2024-09-02T10:17:15+00:00" }, { "name": "symfony/config", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", + "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", "shasum": "" }, "require": { @@ -1512,7 +1834,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.8" + "source": "https://github.com/symfony/config/tree/v6.4.14" }, "funding": [ { @@ -1528,20 +1850,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.10", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "5caf9c5f6085f13b27d70a236b776c07e4a1c3eb" + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5caf9c5f6085f13b27d70a236b776c07e4a1c3eb", - "reference": "5caf9c5f6085f13b27d70a236b776c07e4a1c3eb", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", "shasum": "" }, "require": { @@ -1593,7 +1915,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.10" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" }, "funding": [ { @@ -1609,7 +1931,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T07:32:07+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1680,16 +2002,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", "shasum": "" }, "require": { @@ -1735,7 +2057,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.10" + "source": "https://github.com/symfony/error-handler/tree/v6.4.14" }, "funding": [ { @@ -1751,20 +2073,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:30:32+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { @@ -1815,7 +2137,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -1831,7 +2153,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -1911,16 +2233,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -1957,7 +2279,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.9" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -1973,20 +2295,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:49:33+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "117f1f20a7ade7bcea28b861fb79160a21a1e37b" + "reference": "ba020a321a95519303a3f09ec2824d34d601c388" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/117f1f20a7ade7bcea28b861fb79160a21a1e37b", - "reference": "117f1f20a7ade7bcea28b861fb79160a21a1e37b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ba020a321a95519303a3f09ec2824d34d601c388", + "reference": "ba020a321a95519303a3f09ec2824d34d601c388", "shasum": "" }, "require": { @@ -2034,7 +2356,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.10" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.14" }, "funding": [ { @@ -2050,20 +2372,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:36:27+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "147e0daf618d7575b5007055340d09aece5cf068" + "reference": "8278a947d0369754a47b758a9e17b72cab970951" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/147e0daf618d7575b5007055340d09aece5cf068", - "reference": "147e0daf618d7575b5007055340d09aece5cf068", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8278a947d0369754a47b758a9e17b72cab970951", + "reference": "8278a947d0369754a47b758a9e17b72cab970951", "shasum": "" }, "require": { @@ -2148,7 +2470,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.10" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.14" }, "funding": [ { @@ -2164,24 +2486,24 @@ "type": "tidelift" } ], - "time": "2024-07-26T14:52:04+00:00" + "time": "2024-11-06T09:45:21+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2227,7 +2549,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -2243,24 +2565,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2307,7 +2629,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -2323,24 +2645,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "name": "symfony/polyfill-php81", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -2354,7 +2676,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -2365,10 +2687,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2378,7 +2696,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2387,7 +2705,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -2403,24 +2721,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "name": "symfony/polyfill-php82", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -2434,7 +2752,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php82\\": "" }, "classmap": [ "Resources/stubs" @@ -2454,7 +2772,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2463,7 +2781,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" }, "funding": [ { @@ -2479,24 +2797,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -2539,7 +2857,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -2555,24 +2873,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -2618,7 +2936,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -2634,7 +2952,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/service-contracts", @@ -2799,16 +3117,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6" + "reference": "ec3511eef0576f378b2758da9e1c157086babd59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9bcb26445b9d4ef1087c389234bf33fb00e10ea6", - "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ec3511eef0576f378b2758da9e1c157086babd59", + "reference": "ec3511eef0576f378b2758da9e1c157086babd59", "shasum": "" }, "require": { @@ -2888,7 +3206,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.9" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.13" }, "funding": [ { @@ -2904,20 +3222,20 @@ "type": "tidelift" } ], - "time": "2024-06-21T16:04:15+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/twig-bundle", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65" + "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", - "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/c3beeb5336aba1ea03c37e526968c2fde3ef25c4", + "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4", "shasum": "" }, "require": { @@ -2972,7 +3290,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v6.4.8" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.13" }, "funding": [ { @@ -2988,20 +3306,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/uid", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", "shasum": "" }, "require": { @@ -3046,7 +3364,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.8" + "source": "https://github.com/symfony/uid/tree/v6.4.13" }, "funding": [ { @@ -3062,20 +3380,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "a71cc3374f5fb9759da1961d28c452373b343dd4" + "reference": "93c09246038178717a9c14b809ea8151ffcf7091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a71cc3374f5fb9759da1961d28c452373b343dd4", - "reference": "a71cc3374f5fb9759da1961d28c452373b343dd4", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/93c09246038178717a9c14b809ea8151ffcf7091", + "reference": "93c09246038178717a9c14b809ea8151ffcf7091", "shasum": "" }, "require": { @@ -3131,7 +3449,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.10" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.14" }, "funding": [ { @@ -3147,20 +3465,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:30:32+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", "shasum": "" }, "require": { @@ -3208,7 +3526,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" }, "funding": [ { @@ -3224,28 +3542,27 @@ "type": "tidelift" } ], - "time": "2024-06-24T15:53:56+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "twig/twig", - "version": "v3.11.0", + "version": "v3.14.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d" + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", - "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22", "symfony/polyfill-php81": "^1.29" }, "require-dev": { @@ -3292,7 +3609,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.11.0" + "source": "https://github.com/twigphp/Twig/tree/v3.14.2" }, "funding": [ { @@ -3304,7 +3621,7 @@ "type": "tidelift" } ], - "time": "2024-08-08T16:15:16+00:00" + "time": "2024-11-07T12:36:22+00:00" } ], "packages-dev": [ @@ -3314,12 +3631,12 @@ "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51" + "reference": "5054ce1e0018c7f0946df391a54861f8172d7be2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/65b6744fca5d4b3c366754295e5cb0680a580c51", - "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/5054ce1e0018c7f0946df391a54861f8172d7be2", + "reference": "5054ce1e0018c7f0946df391a54861f8172d7be2", "shasum": "" }, "require": { @@ -3350,7 +3667,7 @@ "issues": "https://github.com/nextcloud-deps/ocp/issues", "source": "https://github.com/nextcloud-deps/ocp/tree/stable29" }, - "time": "2024-07-11T00:37:34+00:00" + "time": "2024-09-17T00:34:06+00:00" }, { "name": "psr/clock", @@ -3406,23 +3723,23 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "d44cb505c9515db5ff905ec510d69a0f80b366a2" + "reference": "e63317470a1b96346be224a68f9e64567e1001c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/d44cb505c9515db5ff905ec510d69a0f80b366a2", - "reference": "d44cb505c9515db5ff905ec510d69a0f80b366a2", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e63317470a1b96346be224a68f9e64567e1001c3", + "reference": "e63317470a1b96346be224a68f9e64567e1001c3", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.2.13", + "admidio/admidio": "<4.3.12", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", - "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.04.6", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", - "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", "airesvsg/acf-to-rest-api": "<=3.1", @@ -3431,6 +3748,7 @@ "alextselegidis/easyappointments": "<1.5", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", "amphp/artax": "<1.0.6|>=2,<2.0.6", "amphp/http": "<=1.7.2|>=2,<=2.1", "amphp/http-client": ">=4,<4.4", @@ -3449,12 +3767,12 @@ "athlon1600/php-proxy-app": "<=3", "austintoddj/canvas": "<=3.4.2", "auth0/wordpress": "<=4.6", - "automad/automad": "<=1.10.9", + "automad/automad": "<2.0.0.0-alpha5", "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<3.288.1", "azuracast/azuracast": "<0.18.3", - "backdrop/backdrop": "<1.24.2", + "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", "badaso/core": "<2.7", @@ -3463,13 +3781,13 @@ "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.2", "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<5.0.9", + "baserproject/basercms": "<=5.1.1", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bbpress/bbpress": "<2.6.5", "bcosca/fatfree": "<3.7.2", "bedita/bedita": "<4", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", - "billz/raspap-webgui": "<2.9.5", + "billz/raspap-webgui": "<=3.1.4", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", "blueimp/jquery-file-upload": "==6.4.4", "bmarshall511/wordpress_zero_spam": "<5.2.13", @@ -3508,31 +3826,34 @@ "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", - "concrete5/concrete5": "<9.2.8", + "concrete5/concrete5": "<9.3.4", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", - "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/contao": "<=5.4.1", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.40|>=5,<5.3.4", + "contao/core-bundle": "<4.13.49|>=5,<5.3.15|>=5.4,<5.4.3", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", - "craftcms/cms": "<4.6.2", + "craftcms/cms": "<4.6.2|>=5,<=5.2.2", "croogo/croogo": "<4", "cuyz/valinor": "<0.12", + "czim/file-handling": "<1.5|>=2,<2.3", "czproject/git-php": "<4.0.3", + "damienharper/auditor-bundle": "<5.2.6", "dapphp/securimage": "<3.6.6", "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", - "dcat/laravel-admin": "<=2.1.3.0-beta", + "dcat/laravel-admin": "<=2.1.3", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", + "dev-lancer/minecraft-motd-parser": "<=1.0.5", "devgroup/dotplant": "<2020.09.14-dev", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", "doctrine/annotations": "<1.2.7", @@ -3547,8 +3868,9 @@ "dolibarr/dolibarr": "<19.0.2", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", - "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.1.8|>=10.2,<10.2.2", - "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", + "drupal/core-recommended": ">=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", @@ -3572,29 +3894,33 @@ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26|>=3.3,<3.3.39", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev|>=3.3,<3.3.40", "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", - "ezyang/htmlpurifier": "<4.1.1", + "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", "facturascripts/facturascripts": "<=2022.08", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", + "filament/infolists": ">=3,<3.2.115", + "filament/tables": ">=3,<3.2.115", "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", "flarum/core": "<1.8.5", @@ -3617,19 +3943,19 @@ "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1,<1.3.5", "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", - "friendsofsymfony1/symfony1": ">=1.1,<1.15.19", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3", - "froxlor/froxlor": "<2.1.9", + "froxlor/froxlor": "<=2.2.0.0-RC3", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "funadmin/funadmin": "<=5.0.2", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", "getformwork/formwork": "<1.13.1|==2.0.0.0-beta1", "getgrav/grav": "<1.7.46", - "getkirby/cms": "<4.1.1", + "getkirby/cms": "<=3.6.6.5|>=3.7,<=3.7.5.4|>=3.8,<=3.8.4.3|>=3.9,<=3.9.8.1|>=3.10,<=3.10.1|>=4,<=4.3", "getkirby/kirby": "<=2.5.12", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -3655,8 +3981,9 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6.0.0-beta1,<4.6.9", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.10", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/post-install": "<=1.0.4", "ibexa/solr": ">=4.5,<4.5.4", @@ -3675,9 +4002,11 @@ "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", "in2code/ipandlanguageredirect": "<5.1.2", "in2code/lux": "<17.6.1|>=18,<24.0.2", + "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.4.1", "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", + "ipl/web": "<0.10.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", @@ -3703,21 +4032,24 @@ "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", "khodakhah/nodcms": "<=3", - "kimai/kimai": "<2.16", + "kimai/kimai": "<=2.20.1", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", "knplabs/knp-snappy": "<=1.4.2", "kohana/core": "<3.3.3", - "krayin/laravel-crm": "<1.2.2", + "krayin/laravel-crm": "<=1.3", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", + "lara-zeus/artemis": ">=1,<=1.0.6", + "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", "laravel/laravel": ">=5.4,<5.4.22", + "laravel/reverb": "<1.4", "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=9|==10.1", @@ -3730,26 +4062,30 @@ "librenms/librenms": "<2017.08.18", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", - "limesurvey/limesurvey": "<3.27.19", + "limesurvey/limesurvey": "<6.5.12", "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": ">2.2.4,<2.2.6|>=3.3.5,<3.4.9", + "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.5.2", "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", "luyadev/yii-helpers": "<1.2.1", - "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch8|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch6|==2.4.7", + "maestroerror/php-heic-to-jpg": "<1.0.5", + "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch10|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch8|>=2.4.7.0-beta1,<2.4.7.0-patch3", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", "magneto/core": "<1.9.4.4-dev", "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", - "mantisbt/mantisbt": "<2.26.2", + "mantisbt/mantisbt": "<=2.26.3", "marcwillmann/turn": "<0.3.3", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.4.12|>=5.0.0.0-alpha,<5.0.4", + "mautic/core": "<4.4.13|>=5,<5.1.1", + "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "maximebf/debugbar": "<1.19", "mdanter/ecc": "<2", - "mediawiki/core": "<1.36.2", + "mediawiki/cargo": "<3.6.1", + "mediawiki/core": "<1.39.5|==1.40", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "melisplatform/melis-asset-manager": "<5.0.1", @@ -3760,7 +4096,7 @@ "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", "microsoft/microsoft-graph-beta": "<2.0.1", "microsoft/microsoft-graph-core": "<2.0.2", - "microweber/microweber": "<=2.0.4", + "microweber/microweber": "<=2.0.16", "mikehaertl/php-shellcommand": "<1.6.1", "miniorange/miniorange-saml": "<1.4.3", "mittwald/typo3_forum": "<1.2.1", @@ -3769,7 +4105,7 @@ "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.3.5|>=4.4.0.0-beta,<4.4.1", + "moodle/moodle": "<4.3.6|>=4.4.0.0-beta,<4.4.2", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", @@ -3782,6 +4118,7 @@ "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", "namshi/jose": "<2.2", + "nategood/httpful": "<1", "neoan3-apps/template": "<1.1.1", "neorazorx/facturascripts": "<2022.04", "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", @@ -3804,16 +4141,16 @@ "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "october/backend": "<1.1.2", "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", - "october/october": "<=3.4.4", + "october/october": "<=3.6.4", "october/rain": "<1.0.472|>=1.1,<1.1.2", "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.15", "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.9|>=4", + "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.5", + "openmage/magento-lts": "<20.10.1", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", @@ -3823,6 +4160,7 @@ "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", "oxid-esales/oxideshop-ce": "<4.5", "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", "packbackbooks/lti-1-3-php-library": "<5", @@ -3846,7 +4184,7 @@ "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpbb/phpbb": "<3.3.11", "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", @@ -3854,8 +4192,8 @@ "phpmyadmin/phpmyadmin": "<5.2.1", "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5", "phpoffice/common": "<0.2.9", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.16", + "phpoffice/phpexcel": "<1.8.1", + "phpoffice/phpspreadsheet": "<1.29.2|>=2,<2.1.1|>=2.2,<2.3", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -3864,9 +4202,10 @@ "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<=1.4.2", + "pimcore/admin-ui-classic-bundle": "<1.5.4", "pimcore/customer-management-framework-bundle": "<4.0.6", "pimcore/data-hub": "<1.2.4", + "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", @@ -3888,15 +4227,16 @@ "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", "privatebin/privatebin": "<1.4|>=1.5,<1.7.4", - "processwire/processwire": "<=3.0.210", + "processwire/processwire": "<=3.0.229", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.11.6", + "pterodactyl/panel": "<1.11.8", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", "pusher/pusher-php-server": "<2.2.1", "pwweb/laravel-core": "<=0.3.6.0-beta", + "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", "pyrocms/pyrocms": "<=3.9.1", "qcubed/qcubed": "<=3.1.1", "quickapps/cms": "<=2.0.0.0-beta2", @@ -3907,7 +4247,7 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<=5.15.1", + "redaxo/source": "<=5.17.1", "remdex/livehelperchat": "<4.29", "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", @@ -3924,8 +4264,8 @@ "serluck/phpwhois": "<=4.2.6", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<=1.2", - "shopware/core": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", - "shopware/platform": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", + "shopware/core": "<=6.5.8.12|>=6.6,<=6.6.5", + "shopware/platform": "<=6.5.8.12|>=6.6,<=6.6.5", "shopware/production": "<=6.3.5.2", "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", @@ -3937,11 +4277,12 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.13.39|>=5,<5.1.11", + "silverstripe/framework": "<5.2.16", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/reports": "<5.2.3", "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.6.1", @@ -3962,7 +4303,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<6.4.2", + "snipe/snipe-it": "<7.0.10", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spatie/browsershot": "<3.57.4", @@ -3972,14 +4313,15 @@ "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<24.05.1", + "starcitizentools/citizen-skin": ">=2.6.3,<2.31", "statamic/cms": "<4.46|>=5.3,<5.6.2", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<2.1.62", + "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", + "sulu/sulu": "<1.6.44|>=2,<2.5.21|>=2.6,<2.6.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "swag/paypal": "<5.4.4", @@ -3990,7 +4332,7 @@ "sylius/grid-bundle": "<1.10.1", "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2|>=1.12.0.0-alpha1,<1.12.16|>=1.13.0.0-alpha1,<1.13.1", + "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", "symbiote/silverstripe-multivaluefield": ">=3,<3.1", "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", "symbiote/silverstripe-seed": "<6.0.3", @@ -4001,7 +4343,8 @@ "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-client": ">=4.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", @@ -4009,20 +4352,22 @@ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/symfony": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/webhook": ">=6.3,<6.3.8", @@ -4046,17 +4391,18 @@ "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tobiasbg/tablepress": "<=2.0.0.0-RC1", - "topthink/framework": "<6.0.17|>=6.1,<6.1.5|>=8,<8.0.4", + "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", "torrentpier/torrentpier": "<=2.4.3", "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", - "tribalsystems/zenario": "<9.5.60602", + "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", - "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", + "twig/twig": "<3.11.2|>=3.12,<3.14.1", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1", "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", @@ -4073,6 +4419,7 @@ "ua-parser/uap-php": "<3.8", "uasoft-indonesia/badaso": "<=2.9.7", "unisharp/laravel-filemanager": "<2.6.4", + "unopim/unopim": "<0.1.4", "userfrosting/userfrosting": ">=0.3.1,<4.6.3", "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", "uvdesk/community-skeleton": "<=1.1.1", @@ -4106,6 +4453,7 @@ "winter/wn-dusk-plugin": "<2.1", "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", + "wireui/wireui": "<1.19.3|>=2,<2.1.3", "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", "wp-cli/wp-cli": ">=0.12,<2.5", "wp-graphql/wp-graphql": "<=1.14.5", @@ -4117,7 +4465,7 @@ "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<4.1", + "yeswiki/yeswiki": "<=4.4.4", "yetiforce/yetiforce-crm": "<=6.4", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", @@ -4208,7 +4556,7 @@ "type": "tidelift" } ], - "time": "2024-07-15T18:05:53+00:00" + "time": "2024-11-07T19:04:57+00:00" } ], "aliases": [], diff --git a/css/main.css b/css/main.css index 1e75f17d..a3db6830 100644 --- a/css/main.css +++ b/css/main.css @@ -168,3 +168,47 @@ margin-left: 16px; color: inherit; } + +/* CodeMirror */ +.codeMirrorContainer { + margin-block-start: 6px; + text-align: left; +} + +.prettifyButton { + margin-block-start: 10px; +} + +.codeMirrorContainer * .cm-content { + border-radius: 0 !important; + border: none !important; +} +.codeMirrorContainer * .cm-editor { + outline: none !important; +} +.codeMirrorContainer.light > .vue-codemirror { + border: 1px dotted silver; +} +.codeMirrorContainer.dark > .vue-codemirror { + border: 1px dotted grey; +} + +/* value text color */ +.codeMirrorContainer.light * .cm-content *::selection { + color: inherit !important; + background-color: #add6ff80 !important; +} +.codeMirrorContainer.dark * .cm-content *::selection { + color: inherit !important; + background-color: #add6ff26 !important; +} + +/* value text color */ +.codeMirrorContainer.dark :deep(.ͼ2 .cm-activeLine) { + background-color: #add6ff26; +} + +/* text cursor */ +.codeMirrorContainer :deep(.cm-content) * { + cursor: text !important; +} diff --git a/docs/developers/aan-de-slag-met-development.md b/docs/developers/aan-de-slag-met-development.md index c7b9e541..0e316832 100644 --- a/docs/developers/aan-de-slag-met-development.md +++ b/docs/developers/aan-de-slag-met-development.md @@ -117,6 +117,18 @@ De commando's om deze linter in de CLI te gebruiken zijn [hier te vinden](https: De ontwikkeling van de API wordt bijgehouden met de documentatietool [Stoplight.io](https://stoplight.io/), die automatisch een [OpenAPI Specificatie (OAS)](https://www.noraonline.nl/wiki/FS:Openapi-specification) genereert uit de documentatie. De Stoplight voor OpenCatalogi is [hier](https://conduction.stoplight.io/docs/open-catalogi/6yuj08rgf7w44-open-catalogi-api) te vinden. +### Authenticatie + +Een applicatie kan op twee manieren authenticeren bij de OpenCatalogi API (voor het bewerken van gegevens). + +#### Basic Auth + +Hiervoor worden de username en wachtwoord van de gebruiker in Nextcloud meegegeven in een Basic Auth structuur (waarin de volgende header volgt: `Authorization: Basic {{base64(username:password)}}`). + +#### OAuth + +OAuth is een veiliger en uitgebreider authenticatiemethode die wij aanraden voor productieomgevingen. Lees hiervoor de [documentatie](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/oauth2.html) voor het inregelen van OAuth van Nextcloud + ## Frontend Development ### Storage en Typing diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index d692f5ff..ffb8160a 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -16,6 +16,7 @@ use OCP\IAppConfig; use OCP\IRequest; use OCP\IUserSession; +use OCP\IURLGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Symfony\Component\Uid\Uuid; @@ -37,6 +38,7 @@ class AttachmentsController extends Controller * @param FileService $fileService The file service * @param IUserSession $userSession The user session * @param ObjectService $objectService The object service + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct ( @@ -46,7 +48,8 @@ public function __construct private readonly AttachmentMapper $attachmentMapper, private readonly FileService $fileService, private readonly IUserSession $userSession, - private readonly ObjectService $objectService + private readonly ObjectService $objectService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -129,6 +132,17 @@ public function create(ObjectService $objectService): JSONResponse // Save the new attachment object. $object = $this->objectService->saveObject('attachment', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.attachments.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('attachment', $object); + } + // Return the created object as a JSON response. return new JSONResponse($object); } @@ -153,6 +167,9 @@ public function update(string|int $id, ObjectService $objectService): JSONRespon // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.attachments.show', ['id' => $data['id']])); + // Save the updated attachment object $object = $this->objectService->saveObject('attachment', $data); diff --git a/lib/Controller/CatalogiController.php b/lib/Controller/CatalogiController.php index 002e7fea..23d43adf 100644 --- a/lib/Controller/CatalogiController.php +++ b/lib/Controller/CatalogiController.php @@ -13,6 +13,7 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\IAppConfig; use OCP\IRequest; +use OCP\IURLGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -33,6 +34,7 @@ class CatalogiController extends Controller * @param ObjectService $objectService The object service * @param DirectoryService $directoryService The directory service * @param BroadcastService $broadcastService The broadcast service + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct( $appName, @@ -41,7 +43,8 @@ public function __construct( private readonly CatalogMapper $catalogMapper, private readonly ObjectService $objectService, private readonly DirectoryService $directoryService, - private readonly BroadcastService $broadcastService + private readonly BroadcastService $broadcastService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -114,6 +117,17 @@ public function create(ObjectService $objectService): JSONResponse // Save the new catalog object $object = $this->objectService->saveObject('catalog', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.catalogs.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('catalog', $object); + } + // Update all external directories $this->broadcastService->broadcast(); @@ -142,6 +156,9 @@ public function update(string|int $id, ObjectService $objectService): JSONRespon // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.catalogs.show', ['id' => $data['id']])); + // Save the updated catalog object $object = $this->objectService->saveObject('catalog', $data); diff --git a/lib/Controller/DirectoryController.php b/lib/Controller/DirectoryController.php index 0c456f15..4b6218db 100644 --- a/lib/Controller/DirectoryController.php +++ b/lib/Controller/DirectoryController.php @@ -6,6 +6,7 @@ use OCA\OpenCatalogi\Db\ListingMapper; use OCA\OpenCatalogi\Service\DirectoryService; use OCA\OpenCatalogi\Service\ObjectService; +use OCA\OpenCatalogi\Exception\DirectoryUrlException; use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -75,25 +76,17 @@ public function update(): JSONResponse { // Get the URL from the request parameters $url = $this->request->getParam('directory'); - - // http://directory.opencatalogi.nl - // Check if the URL parameter is provided - if (empty($url) === true) { - return new JSONResponse(['error' => 'directory parameter is required'], 400); - } - - // Check if URL contains 'local' and throw exception if it does - if (str_contains(strtolower($url), 'local')) { - return new JSONResponse(['error' => 'Local URLs are not allowed'], 400); - } - - // Validate the URL - if (!filter_var($url, FILTER_VALIDATE_URL)) { - return new JSONResponse(['error' => 'Invalid URL provided'], 400); - } // Sync the external directory with the provided URL - $data = $this->directoryService->syncExternalDirectory($url); + try { + $data = $this->directoryService->syncExternalDirectory($url); + } catch (DirectoryUrlException $exception) { + if($exception->getMessage() === 'URL is required') { + $exception->setMessage('Property "directory" is required'); + } + + return new JSONResponse(data: ['message' => $exception->getMessage()], statusCode: 400); + } // Return JSON response with the sync result return new JSONResponse($data); diff --git a/lib/Controller/ListingsController.php b/lib/Controller/ListingsController.php index ba24d12f..450a6166 100644 --- a/lib/Controller/ListingsController.php +++ b/lib/Controller/ListingsController.php @@ -6,6 +6,7 @@ use OCA\OpenCatalogi\Db\ListingMapper; use OCA\OpenCatalogi\Service\ObjectService; use OCA\OpenCatalogi\Service\DirectoryService; +use OCA\OpenCatalogi\Exception\DirectoryUrlException; use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -186,13 +187,16 @@ public function add(): JSONResponse // Get the URL parameter from the request $url = $this->request->getParam('url'); - // Check if the URL parameter is provided - if (empty($url) === true) { - return new JSONResponse(['error' => 'URL parameter is required'], 400); - } - // Add the new listing using the provided URL - $result = $this->directoryService->syncExternalDirectory($url); + try { + $result = $this->directoryService->syncExternalDirectory($url); + } catch (DirectoryUrlException $exception) { + if($exception->getMessage() === 'URL is required') { + $exception->setMessage('Property "url" is required'); + } + + return new JSONResponse(data: ['message' => $exception->getMessage()], statusCode: 400); + } // Return the result as a JSON response return new JSONResponse(['success' => $result]); diff --git a/lib/Controller/OrganizationsController.php b/lib/Controller/OrganizationsController.php index e4865f42..9ee1f6c1 100644 --- a/lib/Controller/OrganizationsController.php +++ b/lib/Controller/OrganizationsController.php @@ -10,7 +10,7 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\IAppConfig; use OCP\IRequest; - +use OCP\IURLGenerator; /** * Class OrganizationsController * @@ -26,13 +26,15 @@ class OrganizationsController extends Controller * @param IAppConfig $config The app configuration * @param OrganizationMapper $organizationMapper The organization mapper * @param ObjectService $objectService The object service + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct( $appName, IRequest $request, private readonly IAppConfig $config, private readonly OrganizationMapper $organizationMapper, - private readonly ObjectService $objectService + private readonly ObjectService $objectService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -109,6 +111,17 @@ public function create(): JSONResponse // Save the new organization object $object = $this->objectService->saveObject('organization', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.organizations.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('organization', $object); + } + // Return the created object as a JSON response return new JSONResponse($object); } @@ -130,6 +143,9 @@ public function update(string|int $id): JSONResponse // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.organizations.show', ['id' => $data['id']])); + // Save the updated organization object $object = $this->objectService->saveObject('organization', $data); diff --git a/lib/Controller/PagesController.php b/lib/Controller/PagesController.php new file mode 100644 index 00000000..d10ef7d5 --- /dev/null +++ b/lib/Controller/PagesController.php @@ -0,0 +1,145 @@ +request->getParams(); + + // Fetch page objects based on filters and order + $data = $this->objectService->getResultArrayForRequest('page', $requestParams); + + // Return JSON response + return new JSONResponse($data); + } + + /** + * Retrieve a specific page by its ID. + * + * @param string|int $id The ID of the page to retrieve + * @return JSONResponse JSON response containing the requested page + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function show(string|int $id): JSONResponse + { + // Fetch the page object by its ID + $object = $this->objectService->getObject('page', $id); + + // Return the page as a JSON response + return new JSONResponse($object); + } + + /** + * Create a new page. + * + * @return JSONResponse The response containing the created page object. + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function create(): JSONResponse + { + // Get all parameters from the request + $data = $this->request->getParams(); + + // Remove the 'id' field if it exists, as we're creating a new object + unset($data['id']); + + // Save the new page object + $object = $this->objectService->saveObject('page', $data); + + // Return the created object as a JSON response + return new JSONResponse($object); + } + + /** + * Update an existing page. + * + * @param string|int $id The ID of the page to update. + * @return JSONResponse The response containing the updated page object. + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function update(string|int $id): JSONResponse + { + // Get all parameters from the request + $data = $this->request->getParams(); + + // Ensure the ID in the data matches the ID in the URL + $data['id'] = $id; + + // Save the updated page object + $object = $this->objectService->saveObject('page', $data); + + // Return the updated object as a JSON response + return new JSONResponse($object); + } + + /** + * Delete a page. + * + * @param string|int $id The ID of the page to delete. + * @return JSONResponse The response indicating the result of the deletion. + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function destroy(string|int $id): JSONResponse + { + // Delete the page object + $result = $this->objectService->deleteObject('page', $id); + + // Return the result as a JSON response + return new JSONResponse(['success' => $result], $result === true ? '200' : '404'); + } +} diff --git a/lib/Controller/PublicationTypesController.php b/lib/Controller/PublicationTypesController.php index eba92db1..ee90e82f 100644 --- a/lib/Controller/PublicationTypesController.php +++ b/lib/Controller/PublicationTypesController.php @@ -11,7 +11,7 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\IAppConfig; use OCP\IRequest; - +use OCP\IURLGenerator; /** * Class PublicationTypesController * @@ -29,6 +29,7 @@ class PublicationTypesController extends Controller * @param ObjectService $objectService The object service * @param DirectoryService $directoryService The directory service * @param BroadcastService $broadcastService The broadcast service + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct( $appName, @@ -37,7 +38,8 @@ public function __construct( private readonly PublicationTypeMapper $publicationTypeMapper, private readonly ObjectService $objectService, private readonly DirectoryService $directoryService, - private readonly BroadcastService $broadcastService + private readonly BroadcastService $broadcastService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -118,6 +120,17 @@ public function create(): JSONResponse // Save the new publication type object $object = $this->objectService->saveObject('publicationType', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.publicationTypes.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('publicationType', $object); + } + // Update all external directories $this->broadcastService->broadcast(); @@ -142,6 +155,10 @@ public function update(string|int $id): JSONResponse // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.publicationTypes.show', ['id' => $data['id']])); + + // Save the updated publication type object $object = $this->objectService->saveObject('publicationType', $data); @@ -169,7 +186,7 @@ public function destroy(string|int $id): JSONResponse // Return the result as a JSON response return new JSONResponse(['success' => $result], $result === true ? '200' : '404'); } - + /** * Synchronize or delete a publication type based on listing status * diff --git a/lib/Controller/PublicationsController.php b/lib/Controller/PublicationsController.php index 47a1c8de..5a770793 100644 --- a/lib/Controller/PublicationsController.php +++ b/lib/Controller/PublicationsController.php @@ -24,6 +24,7 @@ use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\IAppConfig; use OCP\IRequest; +use OCP\IURLGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Symfony\Component\Uid\Uuid; @@ -49,6 +50,8 @@ class PublicationsController extends Controller * @param FileService $fileService The file service * @param DownloadService $downloadService The download service * @param ObjectService $objectService The object service + * @param IURLGenerator $urlGenerator The URL generator + * */ public function __construct ( @@ -59,7 +62,8 @@ public function __construct private readonly IAppConfig $config, private readonly FileService $fileService, private readonly DownloadService $downloadService, - private ObjectService $objectService + private readonly ObjectService $objectService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -197,6 +201,17 @@ public function create(ObjectService $objectService): JSONResponse // Save the new publication object $object = $this->objectService->saveObject('publication', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.publications.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('publication', $object); + } + // Return the created object as a JSON response return new JSONResponse($object); } @@ -221,6 +236,9 @@ public function update(string|int $id, ObjectService $objectService): JSONRespon // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.publications.show', ['id' => $data['id']])); + // Save the updated publication object $object = $this->objectService->saveObject('publication', $data); diff --git a/lib/Controller/SearchController.php b/lib/Controller/SearchController.php index a911980e..b315d1d4 100644 --- a/lib/Controller/SearchController.php +++ b/lib/Controller/SearchController.php @@ -277,4 +277,73 @@ public function theme(string|int $themeId): JSONResponse return new JSONResponse($object); } + /** + * Return all pages. + * + * @CORS + * @PublicPage + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse The Response containing all pages. + */ + public function pages(): JSONResponse + { + // Get all page objects with request parameters + $objects = $this->objectService->getResultArrayForRequest(objectType: 'page', requestParams: $this->request->getParams()); + + // Format dates for each result + $formattedResults = array_map(function($object) { + // Format created_at if it exists + if (isset($object['created_at'])) { + $created = new \DateTime($object['created_at']); + $object['created_at'] = $created->format('Y-m-d\TH:i:s.u\Z'); + } + // Format updated_at if it exists + if (isset($object['updated_at'])) { + $updated = new \DateTime($object['updated_at']); + $object['updated_at'] = $updated->format('Y-m-d\TH:i:s.u\Z'); + } + return $object; + }, $objects['results']); + + // Prepare the response data with formatted dates + $data = [ + 'data' => $formattedResults + ]; + + return new JSONResponse($data); + } + + /** + * Return a specific page by slug. + * + * @CORS + * @PublicPage + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $pageSlug The slug of the page + * @return JSONResponse The Response containing the requested page + * @throws GuzzleException + */ + public function page(string $pageSlug): JSONResponse + { + // Get the page object by slug + $object = $this->objectService->getObject('page', $pageSlug); + + // Format the date fields to match required format + if (isset($object['created_at'])) { + $created = new \DateTime($object['created_at']); + $object['created_at'] = $created->format('Y-m-d\TH:i:s.u\Z'); + } + if (isset($object['updated_at'])) { + $updated = new \DateTime($object['updated_at']); + $object['updated_at'] = $updated->format('Y-m-d\TH:i:s.u\Z'); + } + + return new JSONResponse($object); + } + + } diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 8da08328..5d975be3 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -45,7 +45,7 @@ public function index(): JSONResponse { // Initialize the data array $data = []; - $data['objectTypes'] = ['attachment', 'catalog', 'listing', 'publicationtype', 'organization', 'publication', 'theme']; + $data['objectTypes'] = ['attachment', 'catalog', 'listing', 'publicationtype', 'organization', 'publication', 'theme', 'page']; $data['openRegisters'] = false; $data['availableRegisters'] = []; @@ -78,7 +78,10 @@ public function index(): JSONResponse 'publication_register' => '', 'theme_source' => 'internal', 'theme_schema' => '', - 'theme_register' => '' + 'theme_register' => '', + 'page_source' => 'internal', + 'page_schema' => '', + 'page_register' => '', ]; // Get the current values for the object types from the configuration diff --git a/lib/Controller/ThemesController.php b/lib/Controller/ThemesController.php index 341d5292..b5233943 100644 --- a/lib/Controller/ThemesController.php +++ b/lib/Controller/ThemesController.php @@ -11,7 +11,7 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\IAppConfig; use OCP\IRequest; - +use OCP\IURLGenerator; /** * Class ThemesController * @@ -27,6 +27,7 @@ class ThemesController extends Controller * @param ThemeMapper $themeMapper The theme mapper for database operations * @param IAppConfig $config The app configuration * @param ObjectService $objectService The service for handling object operations + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct ( @@ -34,7 +35,8 @@ public function __construct IRequest $request, private readonly ThemeMapper $themeMapper, private readonly IAppConfig $config, - private readonly ObjectService $objectService + private readonly ObjectService $objectService, + private readonly IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); @@ -97,6 +99,17 @@ public function create(): JSONResponse // Save the new theme object $object = $this->objectService->saveObject('theme', $data); + // If object is a class change it to array + if (is_object($object) === true) { + $object = $object->jsonSerialize(); + } + + // If we do not have an uri, we need to generate one + if (isset($object['uri']) === false) { + $object['uri'] = $this->urlGenerator->getAbsoluteURL($$this->urlGenerator->linkToRoute('openCatalogi.themes.show', ['id' => $object['id']])); + $object = $this->objectService->saveObject('theme', $object); + } + // Return the created object as a JSON response return new JSONResponse($object); } @@ -118,6 +131,9 @@ public function update(string|int $id): JSONResponse // Ensure the ID in the data matches the ID in the URL $data['id'] = $id; + // If we do not have an uri, we need to generate one + $data['uri'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openCatalogi.themes.show', ['id' => $data['id']])); + // Save the updated theme object $object = $this->objectService->saveObject('theme', $data); diff --git a/lib/Db/Attachment.php b/lib/Db/Attachment.php index dd55e64a..5aa72062 100644 --- a/lib/Db/Attachment.php +++ b/lib/Db/Attachment.php @@ -9,6 +9,7 @@ class Attachment extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $reference = null; protected ?string $title = null; @@ -32,6 +33,7 @@ class Attachment extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'reference', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); @@ -96,6 +98,7 @@ public function jsonSerialize(): array $array = [ 'id' => $this->id, 'uuid' => $this->uuid, + 'uri' => $this->uri, 'version' => $this->version, 'reference' => $this->reference, 'title' => $this->title, diff --git a/lib/Db/AttachmentMapper.php b/lib/Db/AttachmentMapper.php index f5eec661..9ecd4b8a 100644 --- a/lib/Db/AttachmentMapper.php +++ b/lib/Db/AttachmentMapper.php @@ -9,6 +9,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -25,8 +26,9 @@ class AttachmentMapper extends QBMapper * Constructor for AttachmentMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ - public function __construct(IDBConnection $db) + public function __construct(IDBConnection $db, IURLGenerator $urlGenerator) { parent::__construct($db, tableName: 'ocat_attachments'); } @@ -105,6 +107,8 @@ public function findAll(int $limit = null, int $offset = null, array $filters = public function createFromArray(array $object): Attachment { $attachment = new Attachment(); + + // Hydrate the attachment with the new data $attachment->hydrate(object: $object); // Set uuid if not provided @@ -112,6 +116,9 @@ public function createFromArray(array $object): Attachment $attachment->setUuid(Uuid::v4()); } + // Set the uri + $attachment->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.attachments.show', ['id' => $attachment->getUuid()]))); + return $this->insert(entity: $attachment); } @@ -126,7 +133,7 @@ public function createFromArray(array $object): Attachment * @throws DoesNotExistException If the entity is not found * @throws MultipleObjectsReturnedException|\OCP\DB\Exception If multiple entities are found */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): Attachment + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): Attachment { $attachment = $this->find($id); // Fallback to create if the attachment does not exist @@ -135,8 +142,12 @@ public function updateFromArray(int $id, array $object, bool $updateVersion = tr return $this->createFromArray($object); } + // Hydrate the attachment with the new data $attachment->hydrate($object); + // Set the uri + $attachment->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.attachments.show', ['id' => $attachment->getUuid()]))); + if ($updateVersion === true) { // Update the version $version = explode('.', $attachment->getVersion()); diff --git a/lib/Db/Catalog.php b/lib/Db/Catalog.php index bc24ca9d..20aed8db 100644 --- a/lib/Db/Catalog.php +++ b/lib/Db/Catalog.php @@ -9,6 +9,7 @@ class Catalog extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $title = null; protected ?string $summary = null; @@ -23,6 +24,7 @@ class Catalog extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'summary', type: 'string'); @@ -78,6 +80,7 @@ public function jsonSerialize(): array { $array = [ 'id' => $this->id, + 'uri' => $this->uri, 'uuid' => $this->uuid, 'version' => $this->version, 'title' => $this->title, diff --git a/lib/Db/CatalogMapper.php b/lib/Db/CatalogMapper.php index 825597de..6e924fe0 100644 --- a/lib/Db/CatalogMapper.php +++ b/lib/Db/CatalogMapper.php @@ -7,6 +7,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -23,8 +24,9 @@ class CatalogMapper extends QBMapper * Constructor for CatalogMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ - public function __construct(IDBConnection $db) + public function __construct(IDBConnection $db, IURLGenerator $urlGenerator) { parent::__construct($db, tableName: 'ocat_catalogi'); } @@ -128,6 +130,9 @@ public function createFromArray(array $object): Catalog $catalog->setUuid(Uuid::v4()); } + // Set the uri + $catalog->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.catalogs.show', ['id' => $catalog->getUuid()]))); + return $this->insert(entity: $catalog); } @@ -140,17 +145,21 @@ public function createFromArray(array $object): Catalog * * @return Catalog The updated Catalog entity */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): Catalog + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): Catalog { - $catalog = $this->find($id); + $catalog = $this->find($id); // Fallback to create if the catalog does not exist if ($catalog === null) { $object['uuid'] = $id; return $this->createFromArray($object); } + // Hydrate the catalog with the new data $catalog->hydrate($object); + // Set the uri + $catalog->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.catalogs.show', ['id' => $catalog->getUuid()]))); + if ($updateVersion === true) { // Update the version $version = explode('.', $catalog->getVersion()); diff --git a/lib/Db/ListingMapper.php b/lib/Db/ListingMapper.php index 21a4300d..05ddab36 100644 --- a/lib/Db/ListingMapper.php +++ b/lib/Db/ListingMapper.php @@ -234,7 +234,7 @@ public function createFromArray(array $object): Listing * @return Listing The updated Listing entity * @throws Exception */ - public function updateFromArray(int|string $id, array $object, bool $updateVersion = true): Listing + public function updateFromArray(int|string $id, array $object, bool $updateVersion = true, bool $patch = false): Listing { $listing = $this->find($id); // Fallback to create if the listing does not exist diff --git a/lib/Db/Organization.php b/lib/Db/Organization.php index 8d227c7a..9e46735e 100644 --- a/lib/Db/Organization.php +++ b/lib/Db/Organization.php @@ -9,6 +9,7 @@ class Organization extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $title = null; protected ?string $summary = null; @@ -24,6 +25,7 @@ class Organization extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'summary', type: 'string'); @@ -79,6 +81,7 @@ public function jsonSerialize(): array $array = [ 'id' => $this->id, 'uuid' => $this->uuid, + 'uri' => $this->uri, 'version' => $this->version, 'title' => $this->title, 'summary' => $this->summary, diff --git a/lib/Db/OrganizationMapper.php b/lib/Db/OrganizationMapper.php index c83a859c..ee2b638b 100644 --- a/lib/Db/OrganizationMapper.php +++ b/lib/Db/OrganizationMapper.php @@ -7,6 +7,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -23,6 +24,7 @@ class OrganizationMapper extends QBMapper * Constructor for OrganizationMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ public function __construct(IDBConnection $db) { @@ -123,6 +125,10 @@ public function createFromArray(array $object): Organization if ($organization->getUuid() === null) { $organization->setUuid(Uuid::v4()); } + + // Set the uri + $organization->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.organizations.show', ['id' => $organization->getUuid()]))); + return $this->insert(entity: $organization); } @@ -135,7 +141,7 @@ public function createFromArray(array $object): Organization * * @return Organization The updated Organization entity */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): Organization + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): Organization { $organization = $this->find($id); // Fallback to create if the organization does not exist @@ -144,8 +150,12 @@ public function updateFromArray(int $id, array $object, bool $updateVersion = tr return $this->createFromArray($object); } + // Hydrate the organization with the new data $organization->hydrate($object); + // Set the uri + $organization->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.organizations.show', ['id' => $organization->getUuid()]))); + if ($updateVersion === true) { // Update the version $version = explode('.', $organization->getVersion()); diff --git a/lib/Db/Page.php b/lib/Db/Page.php new file mode 100644 index 00000000..6ccc281f --- /dev/null +++ b/lib/Db/Page.php @@ -0,0 +1,115 @@ +addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'version', type: 'string'); + $this->addType(fieldName: 'name', type: 'string'); + $this->addType(fieldName: 'slug', type: 'string'); + $this->addType(fieldName: 'contents', type: 'json'); + $this->addType(fieldName: 'created', type: 'datetime'); + $this->addType(fieldName: 'updated', type: 'datetime'); + } + + /** + * Get array of JSON field names + * + * @return array List of field names that are JSON type + */ + public function getJsonFields(): array + { + return array_keys( + array_filter($this->getFieldTypes(), function ($field) { + return $field === 'json'; + }) + ); + } + + /** + * Hydrate the entity from an array of data + * + * @param array $object Data to hydrate from + * @return self + */ + public function hydrate(array $object): self + { + $jsonFields = $this->getJsonFields(); + + // Remove any fields that start with an underscore + // These are typically internal fields that shouldn't be updated directly + foreach ($object as $key => $value) { + if (str_starts_with($key, '_')) { + unset($object[$key]); + } + } + + foreach ($object as $key => $value) { + if (in_array($key, $jsonFields) === true && $value === []) { + $value = null; + } + + $method = 'set'.ucfirst($key); + + try { + $this->$method($value); + } catch (\Exception $exception) { + } + } + + return $this; + } + + /** + * Serialize the entity to JSON + * + * @return array Serialized data + */ + public function jsonSerialize(): array + { + $array = [ + 'id' => $this->id, + 'uuid' => $this->uuid, + 'version' => $this->version, + 'name' => $this->name, + 'slug' => $this->slug, + 'contents' => $this->contents, + 'created_at' => $this->created?->format('c'), + 'updated_at' => $this->updated?->format('c') + ]; + + $jsonFields = $this->getJsonFields(); + + foreach ($array as $key => $value) { + if (in_array($key, $jsonFields) === true && $value === null) { + $array[$key] = []; + } + } + + return $array; + } +} diff --git a/lib/Db/PageMapper.php b/lib/Db/PageMapper.php new file mode 100644 index 00000000..b3793c01 --- /dev/null +++ b/lib/Db/PageMapper.php @@ -0,0 +1,151 @@ +db->getQueryBuilder(); + + $qb->select('*') + ->from('ocat_pages') + ->where($qb->expr()->orX( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)), + $qb->expr()->eq('uuid', $qb->createNamedParameter($id, IQueryBuilder::PARAM_STR)), + $qb->expr()->eq('slug', $qb->createNamedParameter($id, IQueryBuilder::PARAM_STR)) + )); + + try { + return $this->findEntity($qb); + } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { + return null; + } + } + + /** + * Find multiple Pages by their IDs or UUIDs + * + * @param array $ids An array of IDs or UUIDs + * @return array An array of found Page entities + */ + public function findMultiple(array $ids): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from('ocat_pages') + ->where($qb->expr()->orX( + $qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)), + $qb->expr()->in('uuid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_STR_ARRAY)) + )); + + return $this->findEntities(query: $qb); + } + + /** + * Find all Pages with optional limit and offset + * + * @param int|null $limit Maximum number of results to return + * @param int|null $offset Number of results to skip + * @return array An array of all found Page entities + */ + public function findAll(int $limit = null, int $offset = null, array $filters = [], array $sort = [], ?string $search = null): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from('ocat_pages') + ->setMaxResults($limit) + ->setFirstResult($offset); + + return $this->findEntities(query: $qb); + } + + /** + * Create a new Page from an array of data + * + * @param array $object An array of Page data + * @return Page The newly created Page entity + */ + public function createFromArray(array $object): Page + { + $page = new Page(); + $page->hydrate(object: $object); + + // Set uuid if not provided + if ($page->getUuid() === null) { + $page->setUuid(Uuid::v4()); + } + + // Generate slug from name if not provided + if ($page->getSlug() === null && $page->getName() !== null) { + // Convert to lowercase and replace spaces with dashes + $slug = strtolower($page->getName()); + $slug = preg_replace('/[^a-z0-9-]/', '-', $slug); + $slug = preg_replace('/-+/', '-', $slug); + $slug = trim($slug, '-'); + $page->setSlug($slug); + } + + return $this->insert(entity: $page); + } + + /** + * Update an existing Page from an array of data + * + * @param int $id The ID of the Page to update + * @param array $object An array of updated Page data + * @return Page The updated Page entity + * @throws DoesNotExistException If the entity is not found + * @throws MultipleObjectsReturnedException|\OCP\DB\Exception If multiple entities are found + */ + public function updateFromArray(int $id, array $object): Page + { + $page = $this->find($id); + // Fallback to create if the page does not exist + if ($page === null) { + $object['uuid'] = $id; + return $this->createFromArray($object); + } + + $page->hydrate($object); + + return $this->update($page); + } +} diff --git a/lib/Db/Publication.php b/lib/Db/Publication.php index 4b3d3e49..f82a17ec 100644 --- a/lib/Db/Publication.php +++ b/lib/Db/Publication.php @@ -9,6 +9,7 @@ class Publication extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $title = null; protected ?string $reference = null; @@ -39,6 +40,7 @@ class Publication extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'reference', type: 'string'); @@ -125,6 +127,7 @@ public function jsonSerialize(): array { $array = [ 'id' => $this->id, + 'uri' => $this->uri, 'uuid' => $this->uuid, 'version' => $this->version, 'title' => $this->title, diff --git a/lib/Db/PublicationMapper.php b/lib/Db/PublicationMapper.php index c3ced4f0..47c48d2f 100644 --- a/lib/Db/PublicationMapper.php +++ b/lib/Db/PublicationMapper.php @@ -8,6 +8,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\Types; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -24,8 +25,9 @@ class PublicationMapper extends QBMapper * Constructor for PublicationMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ - public function __construct(IDBConnection $db) + public function __construct(IDBConnection $db, IURLGenerator $urlGenerator) { parent::__construct($db, tableName: 'ocat_publications'); } @@ -212,6 +214,9 @@ public function createFromArray(array $object): Publication $publication->setUuid(Uuid::v4()); } + // Set the uri + $publication->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.publications.show', ['id' => $publication->getUuid()]))); + return $this->insert(entity: $publication); } @@ -224,7 +229,7 @@ public function createFromArray(array $object): Publication * * @return Publication The updated Publication entity */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): Publication + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): Publication { $publication = $this->find(id: $id); // Fallback to create if the publication does not exist @@ -233,8 +238,12 @@ public function updateFromArray(int $id, array $object, bool $updateVersion = tr return $this->createFromArray($object); } + // Hydrate the publication with the new data $publication->hydrate(object: $object); + // Set the uri + $publication->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.publications.show', ['id' => $publication->getUuid()]))); + if ($updateVersion === true) { // Update the version $version = explode('.', $publication->getVersion()); diff --git a/lib/Db/PublicationType.php b/lib/Db/PublicationType.php index 510a1e6c..dd729b82 100644 --- a/lib/Db/PublicationType.php +++ b/lib/Db/PublicationType.php @@ -5,23 +5,26 @@ use DateTime; use JsonSerializable; use OCP\AppFramework\Db\Entity; +use OCP\IURLGenerator; class PublicationType extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $title = null; protected ?string $description = null; - protected ?array $required = null; - protected ?array $properties = null; + protected ?array $required = []; + protected ?array $properties = []; protected ?string $source = null; protected ?string $summary = null; - protected ?array $archive = null; + protected ?array $archive = []; protected ?DateTime $updated = null; protected ?DateTime $created = null; public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'description', type: 'string'); @@ -47,14 +50,6 @@ public function hydrate(array $object): self { $jsonFields = $this->getJsonFields(); - // Remove any fields that start with an underscore - // These are typically internal fields that shouldn't be updated directly - foreach ($object as $key => $value) { - if (str_starts_with($key, '_')) { - unset($object[$key]); - } - } - foreach ($object as $key => $value) { if (in_array($key, $jsonFields) === true && $value === []) { $value = null; @@ -72,38 +67,34 @@ public function hydrate(array $object): self return $this; } + /** + * Serializes the schema to an array + * + * @return array + */ public function jsonSerialize(): array { - $properties = []; - foreach ($this->properties ?? [] as $key => $property) { - $properties[$key] = $property; - if (isset($property['type']) === false) { - $properties[$key] = $property; - continue; - } - switch ($property['format'] ?? '') { - case 'string': - case 'array': - $properties[$key]['default'] = (string) ($property['default'] ?? ''); - break; - case 'int': - case 'integer': - case 'number': - $properties[$key]['default'] = (int) ($property['default'] ?? 0); - break; - case 'bool': - $properties[$key]['default'] = (bool) ($property['default'] ?? false); - break; + $required = $this->required ?? []; + $properties = []; + if (isset($this->properties) === true) { + foreach ($this->properties as $key => $property) { + $title = $property['title'] ?? $key; + if ($property['required'] === true && in_array($title, $required) === false) { + $required[] = $title; + } + + $properties[$title] = $property; } } $array = [ 'id' => $this->id, + 'uri' => $this->uri, 'uuid' => $this->uuid, 'version' => $this->version, 'title' => $this->title, 'description' => $this->description, - 'required' => $this->required, + 'required' => $required, 'properties' => $properties, 'source' => $this->source, 'summary' => $this->summary, @@ -122,4 +113,38 @@ public function jsonSerialize(): array return $array; } + + /** + * Generate a JSON-Schema definition for the data field of a publication. + * + * @param IURLGenerator $urlGenerator An URL generator to generate the identifier of the schema. + * + * @return object The JSON-Schema object defining the data field of a publication. + */ + public function getSchemaObject(IURLGenerator $urlGenerator): object + { + $data = $this->jsonSerialize(); + unset($data['id'], $data['uuid'], $data['summary'], $data['archive'], $data['source'], + $data['updated'], $data['created']); + + $data['type'] = 'object'; + + // required on properties will break the validator, only have it set on object level + // array_filter is used so that empty string or 0 validation rules are removed, so we dont validate what we didnt set + if (isset($data['properties']) === true && empty($data['properties']) === false) { + foreach ($data['properties'] as $key => $property) { + $title = $property['title'] ?? $key; + $data['properties'][$title] = array_filter($property); + if (array_key_exists('required', $data['properties'][$key])) { + unset($data['properties'][$key]['required']); + } + } + } + + // Validator needs this specific $schema + $data['$schema'] = 'https://json-schema.org/draft/2020-12/schema'; + $data['$id'] = $urlGenerator->getAbsoluteURL($urlGenerator->linkToRoute('opencatalogi.publication_types.show', ['id' => $this->getUuid()])); + + return json_decode(json_encode($data)); + } } diff --git a/lib/Db/PublicationTypeMapper.php b/lib/Db/PublicationTypeMapper.php index 7a2cc336..1e1e5fc4 100644 --- a/lib/Db/PublicationTypeMapper.php +++ b/lib/Db/PublicationTypeMapper.php @@ -9,6 +9,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -23,8 +24,9 @@ class PublicationTypeMapper extends QBMapper * Constructor for PublicationTypeMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ - public function __construct(IDBConnection $db) + public function __construct(IDBConnection $db, IURLGenerator $urlGenerator) { parent::__construct($db, tableName: 'ocat_publication_types'); } @@ -141,6 +143,10 @@ public function createFromArray(array $object): PublicationType if ($publicationType->getUuid() === null) { $publicationType->setUuid(Uuid::v4()); } + + // Set the uri + $publicationType->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.publication_types.show', ['id' => $publicationType->getUuid()]))); + return $this->insert(entity: $publicationType); } @@ -155,7 +161,7 @@ public function createFromArray(array $object): PublicationType * @throws DoesNotExistException If the entity is not found * @throws MultipleObjectsReturnedException|\OCP\DB\Exception If multiple entities are found */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): PublicationType + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): PublicationType { $publicationType = $this->find($id); // Fallback to create if the publication type does not exist @@ -164,8 +170,12 @@ public function updateFromArray(int $id, array $object, bool $updateVersion = tr return $this->createFromArray($object); } + // Hydrate the publication type with the new data $publicationType->hydrate($object); + // Set the uri + $publicationType->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.publication_types.show', ['id' => $publicationType->getUuid()]))); + if ($updateVersion === true) { // Update the version $version = explode('.', $publicationType->getVersion()); diff --git a/lib/Db/Theme.php b/lib/Db/Theme.php index 881fdf95..8451c217 100644 --- a/lib/Db/Theme.php +++ b/lib/Db/Theme.php @@ -9,6 +9,7 @@ class Theme extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $uri = null; protected ?string $version = '0.0.1'; protected ?string $title = null; protected ?string $summary = null; @@ -19,6 +20,7 @@ class Theme extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); + $this->addType(fieldName: 'uri', type: 'string'); $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'summary', type: 'string'); @@ -70,6 +72,7 @@ public function jsonSerialize(): array { $array = [ 'id' => $this->id, + 'uri' => $this->uri, 'uuid' => $this->uuid, 'version' => $this->version, 'title' => $this->title, diff --git a/lib/Db/ThemeMapper.php b/lib/Db/ThemeMapper.php index 272159ea..d1aa975a 100644 --- a/lib/Db/ThemeMapper.php +++ b/lib/Db/ThemeMapper.php @@ -9,6 +9,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\IURLGenerator; use Symfony\Component\Uid\Uuid; /** @@ -25,8 +26,9 @@ class ThemeMapper extends QBMapper * Constructor for ThemeMapper * * @param IDBConnection $db The database connection + * @param IURLGenerator $urlGenerator The URL generator */ - public function __construct(IDBConnection $db) + public function __construct(IDBConnection $db, IURLGenerator $urlGenerator) { parent::__construct($db, tableName: 'ocat_themes'); } @@ -127,6 +129,10 @@ public function createFromArray(array $object): Theme if ($theme->getUuid() === null) { $theme->setUuid(Uuid::v4()); } + + // Set the uri + $theme->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.themes.show', ['id' => $theme->getUuid()]))); + return $this->insert(entity: $theme); } @@ -141,7 +147,7 @@ public function createFromArray(array $object): Theme * @throws DoesNotExistException If the entity is not found * @throws MultipleObjectsReturnedException|\OCP\DB\Exception If multiple entities are found */ - public function updateFromArray(int $id, array $object, bool $updateVersion = true): Theme + public function updateFromArray(int $id, array $object, bool $updateVersion = true, bool $patch = false): Theme { $theme = $this->find($id); // Fallback to create if the theme does not exist @@ -150,7 +156,12 @@ public function updateFromArray(int $id, array $object, bool $updateVersion = tr return $this->createFromArray($object); } + // Hydrate the theme with the new data $theme->hydrate($object); + + // Set the uri + $theme->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('opencatalogi.themes.show', ['id' => $theme->getUuid()]))); + if ($updateVersion === true) { // Update the version diff --git a/lib/Exception/DirectoryUrlException.php b/lib/Exception/DirectoryUrlException.php new file mode 100644 index 00000000..07e636d0 --- /dev/null +++ b/lib/Exception/DirectoryUrlException.php @@ -0,0 +1,14 @@ +message = $message; + } + +} diff --git a/lib/Migration/Version6Date20241129151236.php b/lib/Migration/Version6Date20241129151236.php new file mode 100644 index 00000000..16192cb8 --- /dev/null +++ b/lib/Migration/Version6Date20241129151236.php @@ -0,0 +1,74 @@ +hasTable(tableName: 'ocat_pages') === false) { + $table = $schema->createTable(tableName: 'ocat_pages'); + + // Primary key and identifier columns + $table->addColumn(name: 'id', typeName: Types::BIGINT, options: ['autoincrement' => true, 'notnull' => true, 'length' => 4]); + $table->addColumn(name: 'uuid', typeName: Types::STRING, options: ['notnull' => true, 'length' => 255]); + $table->addColumn(name: 'version', typeName: Types::STRING, options: ['notnull' => true, 'length' => 255, 'default' => '0.0.1']); + + // Meta columns + $table->addColumn(name: 'name', typeName: Types::STRING, options: ['notnull' => true, 'length' => 255]); + $table->addColumn(name: 'slug', typeName: Types::STRING, options: ['notnull' => true, 'length' => 255]); + $table->addColumn(name: 'contents', typeName: Types::JSON, options: ['notnull' => false]); + $table->addColumn(name: 'updated', typeName: Types::DATETIME, options: ['notnull' => true, 'default' => 'CURRENT_TIMESTAMP']); + $table->addColumn(name: 'created', typeName: Types::DATETIME, options: ['notnull' => true, 'default' => 'CURRENT_TIMESTAMP']); + + // Keys and indexes + $table->setPrimaryKey(columnNames: ['id']); + $table->addIndex(['uuid'], 'ocat_pages_uuid_index'); + $table->addIndex(['slug'], 'ocat_pages_slug_index'); + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Migration/Version6Date20241208222530.php b/lib/Migration/Version6Date20241208222530.php new file mode 100644 index 00000000..33ecb6c5 --- /dev/null +++ b/lib/Migration/Version6Date20241208222530.php @@ -0,0 +1,139 @@ +getTable('ocat_attachments'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + } + + // Update catalogi table + $table = $schema->getTable('ocat_catalogi'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + } + + // Update organizations table + $table = $schema->getTable('ocat_organizations'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + } + + // Update publications table + $table = $schema->getTable('ocat_publications'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + } + + // Update publication types table + $table = $schema->getTable('ocat_publication_types'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + if (!$table->hasIndex('ocat_publication_uuid_index')) { + $table->addIndex(['uuid'], 'ocat_publication_uuid_index'); + } + } + + // Update themes table + $table = $schema->getTable('ocat_themes'); + if ($table->hasColumn('uri') === false) { + $table->addColumn( + name: 'uri', + typeName: Types::STRING, + options: [ + 'notnull' => true, + 'length' => 255 + ] + )->setDefault(''); + if (!$table->hasIndex('ocat_themes_uuid_index')) { + $table->addIndex(['uuid'], 'ocat_themes_uuid_index'); + } + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Service/BroadcastService.php b/lib/Service/BroadcastService.php index f39b6f29..95b3ac11 100644 --- a/lib/Service/BroadcastService.php +++ b/lib/Service/BroadcastService.php @@ -15,7 +15,10 @@ use OCA\OpenCatalogi\Service\ObjectService; /** - * Service class for handling directory-related operations + * Service for broadcasting this OpenCatalogi directory to other instances. + * + * Provides functionality to notify external instances about this directory + * through HTTP POST requests, either to a specific URL or to all known directories. */ class BroadcastService { @@ -73,10 +76,10 @@ public function broadcast(?string $url = null): void { 'directory' => $directoryUrl ] ]); - + // Log successful broadcast \OC::$server->getLogger()->info('Successfully broadcasted to ' . $hook); - + } catch (\Exception $e) { // Throw a warning since broadcasting failure shouldn't break the application flow // but we still want to notify about the issue diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index fa3833a6..05521f3f 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -4,22 +4,32 @@ use DateTime; use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Exception\ServerException; use OCA\OpenCatalogi\Db\Catalog; use OCA\OpenCatalogi\Db\CatalogMapper; use OCA\OpenCatalogi\Db\Listing; use OCA\OpenCatalogi\Db\ListingMapper; use OCA\OpenCatalogi\Service\BroadcastService; +use OCA\OpenCatalogi\Exception\DirectoryUrlException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\AppFramework\Http\JSONResponse; use OCP\IAppConfig; use OCP\IURLGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Uid\Uuid; /** - * Service class for handling directory-related operations + * Service for managing and synchronizing directories and listings. + * + * This service facilitates operations related to directories, catalogs, and listings. + * It supports synchronization with external directories, validation and updates + * of listings, and integration with publication types. */ class DirectoryService { @@ -40,7 +50,7 @@ class DirectoryService * @param ObjectService $objectService Object service for handling objects * @param CatalogMapper $catalogMapper Mapper for catalog objects * @param ListingMapper $listingMapper Mapper for listing objects - * @param BroadcastService $broadcastService Broadcast service for broadcasting + * @param BroadcastService $broadcastService Broadcast service for broadcasting */ public function __construct( private readonly IURLGenerator $urlGenerator, @@ -48,7 +58,7 @@ public function __construct( private readonly ObjectService $objectService, private readonly CatalogMapper $catalogMapper, private readonly ListingMapper $listingMapper, - private readonly BroadcastService $broadcastService + private readonly BroadcastService $broadcastService, ) { $this->client = new Client([]); @@ -87,7 +97,7 @@ private function getDirectoryFromListing(Listing|array $listing): array // $listing['id'] = $listing['uuid']; // Remove unneeded fields - unset($listing['status'], $listing['lastSync'], $listing['default'], $listing['available'], $listing['catalog'], $listing['statusCode'], + unset($listing['status'], $listing['lastSync'], $listing['default'], $listing['available'], $listing['statusCode'], // $listing['uuid'], //@todo this breaks stuff when trying to find and update a listing $listing['hash']); @@ -153,6 +163,7 @@ private function getDirectoryFromCatalog(Catalog|array $catalog): array // Add the search and directory urls $catalog['search'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("opencatalogi.search.index")); $catalog['directory'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("opencatalogi.directory.index")); + $catalog['catalog'] = $catalog['id']; // Process publication types if (isset($catalog['publicationTypes']) && is_array($catalog['publicationTypes'])) { @@ -228,16 +239,20 @@ public function doCronSync(): array { // Extract unique directory URLs // Get unique directories from listings $uniqueDirectories = array_unique(array_column($listings, 'directory')); - + // Add default OpenCatalogi directory if not already present $defaultDirectory = 'https://directory.opencatalogi.nl/apps/opencatalogi/api/directory'; if (!in_array($defaultDirectory, $uniqueDirectories)) { $uniqueDirectories[] = $defaultDirectory; - } + } // Sync each unique directory foreach ($uniqueDirectories as $directoryUrl) { - $result = $this->syncExternalDirectory($directoryUrl); + try { + $result = $this->syncExternalDirectory($directoryUrl); + } catch (DirectoryUrlException $exception) { + continue; + } $results = array_merge_recursive($results, $result); } @@ -252,7 +267,7 @@ public function doCronSync(): array { */ public function validateExternalListing(array $listing): bool { - if (empty($listing['id']) || !Uuid::isValid($listing['id'])) { + if (empty($listing['catalog']) === true || Uuid::isValid($listing['catalog']) === false) { return false; } @@ -269,7 +284,7 @@ public function validateExternalListing(array $listing): bool * @return array The updated listing * @throws DoesNotExistException|MultipleObjectsReturnedException|ContainerExceptionInterface|NotFoundExceptionInterface */ - public function updateListing(array $newListing, array $oldListing): array{ + public function updateListing(array $newListing, array $oldListing): array{ // Let's see if these changed by checking them against the hash $newHash = hash('sha256', json_encode($newListing)); $oldHash = hash('sha256', json_encode($oldListing)); @@ -283,7 +298,36 @@ public function updateListing(array $newListing, array $oldListing): array{ return $newListing->jsonSerialize(); } - + /** + * Checks if the URL complies to basic rules. + * + * @param string $url The url to check. + * @return void + * @throws DirectoryUrlException Thrown if the url is invalid. + */ + private function checkConditions(string $url): void + { + if (empty($url) === true) { + throw new DirectoryUrlException('URL is required'); + } + + // Check if URL contains the base url of this instance. + if (str_contains(haystack: strtolower($url), needle: $this->urlGenerator->getBaseUrl()) === true) { + throw new DirectoryUrlException('Cannot load current directory'); + } + + // Check if URL contains 'local' and throw exception if it does + if (str_contains(strtolower($url), 'local') === true) { + throw new DirectoryUrlException('Local urls are not allowed'); + } + + // Validate the URL + if (filter_var($url, FILTER_VALIDATE_URL) === false) { + throw new DirectoryUrlException('Invalid URL provided'); + } + } + + /** * Synchronize with an external directory * @@ -292,25 +336,32 @@ public function updateListing(array $newListing, array $oldListing): array{ * @return array An array containing synchronization results * @throws DoesNotExistException|MultipleObjectsReturnedException|ContainerExceptionInterface|NotFoundExceptionInterface * @throws GuzzleException|\OCP\DB\Exception + * @throws DirectoryUrlException */ public function syncExternalDirectory(string $url): array { // Log successful broadcast \OC::$server->getLogger()->info('Synchronizing directory with ' . $url); + $this->checkConditions($url); + try { + $checkUrls[] = $url; // Get the directory data $result = $this->client->get($url); // Fallback to the /api/directory endpoint if the result is not JSON if (str_contains($result->getHeader('Content-Type')[0], 'application/json') === false) { + + $checkUrls[] = $url.'/index.php/apps/opencatalogi/api/directory'; $url = rtrim($url, '/').'/apps/opencatalogi/api/directory'; $result = $this->client->get($url); + $checkUrls[] = $url; } - } catch (\GuzzleHttp\Exception\ClientException $e) { + } catch (ClientException|RequestException|ServerException $e) { // If we get a 404, the directory no longer exists if ($e->getResponse()->getStatusCode() === 404) { - // Delete all listings for this directory since it no longer exists + // Delete all listings for this directory since it no longer exists $this->deleteListingsByDirectory('listing', $url); throw new \Exception('Directory no longer exists at ' . $url); } @@ -322,10 +373,7 @@ public function syncExternalDirectory(string $url): array // Get all current listings for this directory $currentListings = $this->objectService->getObjects( - objectType: 'listing', - filters: [ - 'directory'=>$url, - ] + objectType: 'listing' ); // Remove any listings without a catalog ID from the database @@ -342,17 +390,20 @@ public function syncExternalDirectory(string $url): array // array_column() with null as second parameter returns complete array entries // This will return complete listing objects indexed by their catalog ID $oldListings = array_column( - $currentListings, + $currentListings, null, // null returns complete array entries rather than a specific column 'catalog' // Index by catalog ID ); + $oldListingDirectories = array_unique(array: array_column(array: $currentListings, column_key: 'directory')); + // Initialize arrays to store results $addedListings = []; $updatedListings = []; $invalidListings = []; $foundDirectories = []; $removedListings = []; + $discoveredDirectories = []; // Process each new listing foreach ($newListings as $listing) { @@ -362,13 +413,22 @@ public function syncExternalDirectory(string $url): array continue; } + if (in_array(needle: $listing['directory'], haystack: $checkUrls) === false + && in_array(needle: $listing['directory'], haystack: $oldListingDirectories) === false + ) { + $discoveredDirectories[] = $listing['directory']; + + continue; + } else if (in_array(needle: $listing['directory'], haystack: $checkUrls) === false) { + continue; + } + // Check if we already have this listing by looking up its catalog ID in the oldListings array - $oldListing = $oldListings[$listing['id']] ?? null; + $oldListing = $oldListings[$listing['catalog']] ?? null; // If no existing listing found, prepare the new listing data if ($oldListing === null) { $listing['hash'] = hash('sha256', json_encode($listing)); - $listing['catalog'] = $listing['id']; unset($listing['id']); } else { // Update existing listing @@ -394,10 +454,14 @@ public function syncExternalDirectory(string $url): array } // Lets inform our new friends that we exist - foreach($foundDirectories as $foundDirectory){ + foreach ($foundDirectories as $foundDirectory){ $this->broadcastService->broadcast($foundDirectory); } + foreach ($discoveredDirectories as $discoveredDirectory) { + $this->syncExternalDirectory($discoveredDirectory); + } + // Return the results return [ 'invalidListings' => $invalidListings, @@ -420,7 +484,7 @@ private function deleteListingsByDirectory(string $directoryUrl): void { ] ); // Delete all listings - foreach ($currentListings as $listing) { + foreach ($currentListings as $listing) { $this->objectService->deleteObject('listing', $listing['id']); } } diff --git a/lib/Service/DownloadService.php b/lib/Service/DownloadService.php index 7d582b9a..6376e3b7 100644 --- a/lib/Service/DownloadService.php +++ b/lib/Service/DownloadService.php @@ -17,7 +17,11 @@ use Exception; /** - * Service class for handling download-related operations + * Service for managing download-related operations. + * + * Provides functionality to create and manage publication files and archives, including + * generating PDFs and ZIP files containing metadata and attachments, and storing files + * in NextCloud. */ class DownloadService { diff --git a/lib/Service/ElasticSearchClientAdapter.php b/lib/Service/ElasticSearchClientAdapter.php index 83efcb6c..1ef01041 100644 --- a/lib/Service/ElasticSearchClientAdapter.php +++ b/lib/Service/ElasticSearchClientAdapter.php @@ -5,7 +5,10 @@ use Elastic\Elasticsearch\Client; /** - * Adapter class for Elasticsearch client operations + * Adapter for Elasticsearch client operations. + * + * Provides a wrapper around the Elasticsearch client to facilitate search, indexing, + * retrieval, updating, and deletion of documents. */ class ElasticSearchClientAdapter { @@ -83,4 +86,4 @@ public function delete(array $params) // Delete the document and return the response return $this->client->delete($params); } -} \ No newline at end of file +} diff --git a/lib/Service/ElasticSearchService.php b/lib/Service/ElasticSearchService.php index 73afa1fe..d721e670 100644 --- a/lib/Service/ElasticSearchService.php +++ b/lib/Service/ElasticSearchService.php @@ -8,6 +8,13 @@ use Elastic\Elasticsearch\ClientBuilder; use Symfony\Component\Uid\Uuid; +/** + * Service for managing interactions with Elasticsearch. + * + * Provides functionality for indexing, updating, deleting, and searching objects in Elasticsearch, + * as well as processing and formatting query results and aggregations. + */ + class ElasticSearchService { diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 4d2fe177..70ec13e2 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -29,8 +29,12 @@ use ZipArchive; /** - * Service class for handling file operations in OpenCatalogi + * Service for handling file operations in OpenCatalogi. + * + * Provides functionalities for managing files and folders in NextCloud, creating and managing + * share links, handling uploaded files, generating PDF and ZIP files, and managing temporary files. */ + class FileService { /** diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index f0688c0b..d237e181 100644 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -12,6 +12,7 @@ use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\IURLGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Symfony\Component\Uid\Uuid; @@ -19,6 +20,7 @@ use OCP\IAppConfig; // Import mappers use OCA\OpenCatalogi\Db\AttachmentMapper; +use OCA\OpenCatalogi\Db\PageMapper; use OCA\OpenCatalogi\Db\CatalogMapper; use OCA\OpenCatalogi\Db\ListingMapper; use OCA\OpenCatalogi\Db\PublicationTypeMapper; @@ -27,13 +29,19 @@ use OCA\OpenCatalogi\Db\ThemeMapper; /** - * Service class for handling object-related operations + * Service for handling object-related operations. + * + * Provides functionality for retrieving, saving, updating, and deleting objects, + * as well as extending entities with related data and managing object mappings. */ + class ObjectService { /** @var string $appName The name of the app */ private string $appName; + private ValidationService $validationService; + /** * Constructor for ObjectService. * @@ -44,6 +52,7 @@ class ObjectService * @param OrganizationMapper $organizationMapper Mapper for organizations * @param PublicationMapper $publicationMapper Mapper for publications * @param ThemeMapper $themeMapper Mapper for themes + * @param PageMapper $pageMapper Mapper for pages * @param ContainerInterface $container Container for dependency injection * @param IAppManager $appManager App manager interface * @param IAppConfig $config App configuration interface @@ -56,11 +65,15 @@ public function __construct( private OrganizationMapper $organizationMapper, private PublicationMapper $publicationMapper, private ThemeMapper $themeMapper, + private PageMapper $pageMapper, private ContainerInterface $container, private readonly IAppManager $appManager, private readonly IAppConfig $config, + IURLGenerator $urlGenerator, ) { $this->appName = 'opencatalogi'; + + $this->validationService = new ValidationService(objectService: $this, urlGenerator: $urlGenerator); } /** @@ -106,6 +119,7 @@ private function getMapper(string $objectType): mixed 'organization' => $this->organizationMapper, 'publication' => $this->publicationMapper, 'theme' => $this->themeMapper, + 'page' => $this->pageMapper, default => throw new InvalidArgumentException("Unknown object type: $objectType"), }; } @@ -298,11 +312,16 @@ public function getAllObjects(string $objectType, ?int $limit = null, ?int $offs */ public function saveObject(string $objectType, array $object, bool $updateVersion = true): mixed { + if ($objectType === 'publication') { + $object = $this->validationService->validatePublication($object); + } + // Get the appropriate mapper for the object type $mapper = $this->getMapper($objectType); + // If the object has an id, update it; otherwise, create a new object if (isset($object['id']) === true) { - return $mapper->updateFromArray($object['id'], $object, $updateVersion); + return $mapper->updateFromArray($object['id'], $object, $updateVersion, patch: true); } else { return $mapper->createFromArray($object); diff --git a/lib/Service/SearchService.php b/lib/Service/SearchService.php index 9ecfdafd..0a0b6704 100644 --- a/lib/Service/SearchService.php +++ b/lib/Service/SearchService.php @@ -8,10 +8,13 @@ use Symfony\Component\Uid\Uuid; /** - * Class SearchService - * - * This service handles search operations and related functionalities. + * Service for managing search operations and related functionalities. + * + * Provides methods for performing search queries, merging search results and aggregations, + * and creating database-specific filters and sort parameters. Handles both local and + * distributed search queries across multiple directories. */ + class SearchService { /** @var Client */ @@ -25,7 +28,7 @@ class SearchService /** * SearchService constructor. - * + * * @param ElasticSearchService $elasticService * @param DirectoryService $directoryService * @param IURLGenerator $urlGenerator @@ -40,7 +43,7 @@ public function __construct( /** * Merge facets from existing and new aggregations. - * + * * @param array $existingAggregation * @param array $newAggregation * @return array Merged facets @@ -75,7 +78,7 @@ public function mergeFacets(array $existingAggregation, array $newAggregation): /** * Merge existing and new aggregations. - * + * * @param array|null $existingAggregations * @param array|null $newAggregations * @return array Merged aggregations @@ -98,7 +101,7 @@ private function mergeAggregations(?array $existingAggregations, ?array $newAggr /** * Comparison function for sorting result arrays. - * + * * @param array $a * @param array $b * @return int @@ -110,7 +113,7 @@ public function sortResultArray(array $a, array $b): int /** * Perform a search operation. - * + * * @param array $parameters Search parameters * @param array $elasticConfig Elasticsearch configuration * @param array $dbConfig Database configuration diff --git a/lib/Service/ValidationService.php b/lib/Service/ValidationService.php index fd58b2b3..8c36e6b7 100644 --- a/lib/Service/ValidationService.php +++ b/lib/Service/ValidationService.php @@ -3,118 +3,93 @@ namespace OCA\OpenCatalogi\Service; use OCA\OpenCatalogi\Db\CatalogMapper; +use OCA\OpenCatalogi\Db\Publication; +use OCA\OpenCatalogi\Db\PublicationType; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\IAppConfig; +use OCP\IURLGenerator; +use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\Validator; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Component\Uid\Uuid; /** - * Class ValidationService + * Service for validating catalogs and publications. * - * This service handles validation of catalogs and publications. + * Provides methods to validate publications against the schema defined in their associated + * publication types, ensuring data consistency and integrity. Handles default values and + * reports validation errors. */ + class ValidationService { - /** - * @var string The name of the application. - */ - private string $appName; - /** - * @var array The current MongoDB Config. - */ - private array $mongodbConfig; - - /** - * ValidationService constructor. - * - * @param IAppConfig $config The application config - * @param CatalogMapper $catalogMapper The catalog mapper. - * @param ObjectService $objectService The object service. - */ public function __construct( - private readonly IAppConfig $config, - private readonly CatalogMapper $catalogMapper, private readonly ObjectService $objectService, - ) { - $this->appName = 'opencatalogi'; - - // Initialize MongoDB configuration - $this->mongodbConfig = [ - 'base_uri' => $this->config->getValueString(app: $this->appName, key: 'mongodbLocation'), - 'headers' => ['api-key' => $this->config->getValueString(app: $this->appName, key: 'mongodbKey')], - 'mongodbCluster' => $this->config->getValueString(app: $this->appName, key:'mongodbCluster') - ]; - } - - /** - * Get the MongoDB configuration. - * - * @return array The mongodb config. - */ - public function getMongodbConfig(): array + private readonly IURLGenerator $urlGenerator, + ) { - return $this->mongodbConfig; } /** - * Fetches a catalog from either the local database or mongodb + * Validate a publication to the definition defined in the PublicationType. + * + * @param Publication $publication The publication to validate. * - * @param string $id The id of the catalog to be fetched. - * @return array The JSON Serialised catalog. + * @return array The validated publication. * - * @throws OCSNotFoundException If the catalog is not found. - * @throws \GuzzleHttp\Exception\GuzzleException + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ - public function getCatalog (string $id): array + public function validatePublication(array $publication): array { - // Check if MongoDB storage is enabled - if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') !== false - && $this->config->getValueString(app: $this->appName, key: 'mongoStorage') === '1' - ) { - $filter = ['id' => $id, '_schema' => 'catalog']; - - try { - return $this->objectService->findObject(filters: $filter, config: $this->getMongodbConfig()); - } catch (OCSNotFoundException $exception) { - throw new OCSNotFoundException(message: 'Catalog not found for id: ' . $id); - } + if (isset($publication['publicationType']) === false) { + return $publication; } - // If MongoDB storage is not enabled, fetch from local database - return $this->catalogMapper->find(id: $id)->jsonSerialize(); - } + $publicationTypeId = $publication['publicationType']; + $publicationType = $this->objectService->getObject(objectType: 'publicationType', id: $publicationTypeId); - /** - * Validates a publication against the rules set for the publication. - * - * @param array $publication The publication to be validated. - * @return array The publication after it has been validated. - * - * @throws OCSBadRequestException Thrown if the object does not validate - * @throws OCSNotFoundException Thrown if the catalog is not found - */ - public function validatePublication(array $publication): array - { - // Check for required fields - $requiredFields = ['catalogi', 'publicationType']; - foreach ($requiredFields as $field) { - if (isset($publication[$field]) === false) { - throw new OCSBadRequestException(message: $field . ' is required but not given.'); + $publicationType = (new PublicationType())->hydrate($publicationType); + + if (Uuid::isValid($publicationTypeId) === true) { + $publicationType->setUuid($publicationTypeId); + } + + $validator = new Validator(); + $validator->setMaxErrors(100); + + if (empty($publicationType->getProperties()) === true) { + return $publication; + } + + + // Check for default values, and only set the property if the property is empty + foreach ($publicationType->getProperties() as $property) { + if (isset($property['default']) === true && empty($property['default']) === false && (isset($publication['data'][$property['title']]) === false || empty($publication['data'][$property['title']]) === true)) { + $publication['data'][$property['title']] = $property['default']; } } - $catalog = $publication['catalogi']; - $publicationType = $publication['publicationType']; + $result = $validator->validate(data: (object) json_decode(json_encode($publication['data'])), schema: $publicationType->getSchemaObject($this->urlGenerator)); - try { - $catalog = $this->getCatalog($catalog); - } catch (OCSNotFoundException $exception) { - throw new OCSNotFoundException(message: $exception->getMessage()); - } + $publication['validation'] = [ + 'errors' => [], + 'valid' => true + ]; - // Check if the given publicationType is present in the catalog - if (in_array(needle: $publicationType, haystack: $catalog['publicationType']) === false) { - throw new OCSBadRequestException(message: 'Given publicationType object not present in catalog'); + if ($result->hasError()) { + $errors = (new ErrorFormatter())->format($result->error()); + foreach ($errors as $error) { + $publication['validation']['errors'][] = $error[0]; + } + $publication['validation']['valid'] = false; } return $publication; diff --git a/lib/Templates/publication.html.twig b/lib/Templates/publication.html.twig new file mode 100644 index 00000000..cd43ad73 --- /dev/null +++ b/lib/Templates/publication.html.twig @@ -0,0 +1,163 @@ +

Publicatie {{ publication.title }}

+ + + +{% if publication.catalogi|default %} +

Catalogi

+ + + + + + {% if publication.catalogi.summary|default %} + + + + + {% endif %} + {% if publication.catalogi.description|default %} + + + + + {% endif %} + {# {% if publication.catalogi.organisation|default %}#} + {# #} + {# #} + {# #} + {# #} + {# {% endif %}#} +
Titel: {{ publication.catalogi.title }}
Samenvatting: {{ publication.catalogi.summary }}
Beschrijving: {{ publication.catalogi.description }}
Organisatie: {{ publication.catalogi.organisation }}
+{% endif %} + +{% if publication.metaData|default %} +

Publicatie Type

+ + + + + + {% if publication.metaData.version|default %} + + + + + {% endif %} + {% if publication.metaData.description|default %} + + + + + {% endif %} + {# {% if publication.metaData.required|default %}#} + {# #} + {# #} + {# #} + {# #} + {# {% endif %}#} +
Titel: {{ publication.metaData.title }}
Versie: {{ publication.metaData.version }}
Beschrijving: {{ publication.metaData.description }}
Vereisten: {{ publication.metaData.required }}
+{% endif %} + +
+ + + {% if publication.reference|default %} + + + + + {% endif %} + {% if publication.summary|default %} + + + + + {% endif %} + {% if publication.description|default %} + + + + + {% endif %} + {% if publication.category|default %} + + + + + {% endif %} + {% if publication.portal|default %} + + + + + {% endif %} + {% if publication.image|default %} + + + + + {% endif %} + {# {% if publication.themes|default %}#} + {# #} + {# #} + {# #} + {# #} + {# {% endif %}#} + {% if publication.featured is defined %} + + + + + {% endif %} + {% if publication.license|default %} + + + + + {% endif %} + {% if publication.status|default %} + + + + + {% endif %} + {% if publication.published|default %} + + + + + {% endif %} + {% if publication.modified|default %} + + + + + {% endif %} +
Referentie: {{ publication.reference }}
Samenvatting: {{ publication.summary }}
Beschrijving: {{ publication.description }}
Categorie: {{ publication.category }}
Portal: {{ publication.portal }}
Foto: {{ publication.image }}
Thema's: {{ publication.themes }}
Uitgelicht: {% if publication.featured == true %}Ja{% else %}Nee{% endif %}
Licentie: {{ publication.license }}
Status: {{ publication.status }}
Gepubliceerd: {{ publication.published | date("d-m-Y H:i") }}
Gewijzigd: {{ publication.modified | date("d-m-Y H:i") }}
+ +{% if publication.data|default %} + +

Eigenschappen

+ + + + + + {% for key, value in publication.data %} + + + + + {% endfor %} +
NaamData
{{ key }}{{ value }}
+{% endif %} diff --git a/package-lock.json b/package-lock.json index 13151b64..3842f7fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "dependencies": { + "@codemirror/lang-json": "^6.0.1", "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@nextcloud/axios": "^2.5.0", @@ -17,6 +18,7 @@ "@nextcloud/l10n": "^3.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/vue": "^8.17.0", + "@uiw/codemirror-theme-vscode": "^4.23.6", "@vueuse/core": "^11.0.1", "apexcharts": "^3.52.0", "axios": "^1.7.4", @@ -32,6 +34,7 @@ "validator": "^13.12.0", "vue": "^2.7.14", "vue-apexcharts": "^1.6.2", + "vue-codemirror6": "^1.3.8", "vue-loader": "^15.11.1 <16.0.0", "vue-loading-overlay": "^3.4.3", "vue-material-design-icons": "^5.3.0", @@ -1942,6 +1945,99 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz", + "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", + "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.6", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.6.tgz", + "integrity": "sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", + "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", + "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz", + "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", @@ -3295,6 +3391,41 @@ "license": "MIT", "peer": true }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@linusborg/vue-simple-portal": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz", @@ -5186,6 +5317,37 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/codemirror-theme-vscode": { + "version": "4.23.6", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.23.6.tgz", + "integrity": "sha512-xUo1ic+Kk5hnv5gy+cXU12GZVSnDjic8s8weKq8loPHF1dSR1e6gkKVIKZRnvoOZ302taKRk7phWpBUaWIuKQg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.23.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.23.6", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.6.tgz", + "integrity": "sha512-0dpuLQW+V6zrKvfvor/eo71V3tpr2L2Hsu8QZAdtSzksjWABxTOzH3ShaBRxCEsrz6sU9sa9o7ShwBMMDz59bQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -7634,6 +7796,21 @@ "node": ">= 0.12.0" } }, + "node_modules/codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", @@ -8169,6 +8346,12 @@ "node": ">=8" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -20729,6 +20912,12 @@ "webpack": "^5.0.0" } }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -22884,6 +23073,54 @@ "apexcharts": "^3.26.0" } }, + "node_modules/vue-codemirror6": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.8.tgz", + "integrity": "sha512-pCOzKzBBSFKi/SjUg+XGranV1vt+8S22z56BES/OeZtmyuj2M0CE0aczYS8qbTWNnKcuJcI5FRDHzVXy2v2Htg==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.7.1", + "@codemirror/language": "^6.10.3", + "@codemirror/lint": "^6.8.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.35.0", + "codemirror": "^6.0.1", + "style-mod": "^4.1.2", + "vue-demi": "latest" + }, + "engines": { + "pnpm": ">=9.14.2" + }, + "peerDependencies": { + "vue": "^2.7.14 || ^3.4" + } + }, + "node_modules/vue-codemirror6/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-color": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz", @@ -23137,6 +23374,12 @@ "vue": "^2.5.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index fdccad01..f7809035 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "extends @nextcloud/browserslist-config" ], "dependencies": { + "@codemirror/lang-json": "^6.0.1", "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@nextcloud/axios": "^2.5.0", @@ -28,6 +29,7 @@ "@nextcloud/l10n": "^3.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/vue": "^8.17.0", + "@uiw/codemirror-theme-vscode": "^4.23.6", "@vueuse/core": "^11.0.1", "apexcharts": "^3.52.0", "axios": "^1.7.4", @@ -43,6 +45,7 @@ "validator": "^13.12.0", "vue": "^2.7.14", "vue-apexcharts": "^1.6.2", + "vue-codemirror6": "^1.3.8", "vue-loader": "^15.11.1 <16.0.0", "vue-loading-overlay": "^3.4.3", "vue-material-design-icons": "^5.3.0", diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..eb935f62 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,154 @@ + + + The coding standard for PHP_CodeSniffer itself, for more config -> for https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options. + + README.md + src + + + + + + + + error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + error + + + + + error + + + + + tests/bootstrap\.php + + + + + tests/Core/Tokenizer/StableCommentWhitespaceWinTest\.php + + + diff --git a/src/entities/index.js b/src/entities/index.js index 2f4bb056..b5726d33 100644 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -7,3 +7,4 @@ export * from './organization/index.js' export * from './publication/index.js' export * from './theme/index.js' export * from './publicationType/index.js' +export * from './page/index.js' \ No newline at end of file diff --git a/src/entities/listing/listing.mock.ts b/src/entities/listing/listing.mock.ts index a90adb17..1e9917db 100644 --- a/src/entities/listing/listing.mock.ts +++ b/src/entities/listing/listing.mock.ts @@ -26,6 +26,7 @@ export const mockListingsData = (): TListing[] => [ tooi: 'string', rsin: 'string', pki: 'string', + image: 'string', }, }, { @@ -52,6 +53,7 @@ export const mockListingsData = (): TListing[] => [ tooi: 'string', rsin: 'string', pki: 'string', + image: 'string', }, }, { diff --git a/src/entities/organization/organization.mock.ts b/src/entities/organization/organization.mock.ts index d2a0fd20..ae84f2f6 100644 --- a/src/entities/organization/organization.mock.ts +++ b/src/entities/organization/organization.mock.ts @@ -11,6 +11,7 @@ export const mockOrganizationData = (): TOrganization[] => [ tooi: '7843432', rsin: '827342654', pki: '543573424', + image: '', }, { id: '2', @@ -21,6 +22,7 @@ export const mockOrganizationData = (): TOrganization[] => [ tooi: '', rsin: '', pki: '', + image: '', }, { // invalid data id: '3', @@ -31,6 +33,7 @@ export const mockOrganizationData = (): TOrganization[] => [ tooi: '5435', rsin: '54', pki: '6565', + image: '', }, ] diff --git a/src/entities/organization/organization.ts b/src/entities/organization/organization.ts index f8fd86cc..90d1f3b9 100644 --- a/src/entities/organization/organization.ts +++ b/src/entities/organization/organization.ts @@ -11,6 +11,7 @@ export class Organization implements TOrganization { public tooi: string public rsin: string public pki: string + public image: string constructor(data: TOrganization) { this.hydrate(data) @@ -26,6 +27,7 @@ export class Organization implements TOrganization { this.tooi = data?.tooi || '' this.rsin = data?.rsin || '' this.pki = data?.pki || '' + this.image = data?.image || '' } /* istanbul ignore next */ @@ -42,6 +44,7 @@ export class Organization implements TOrganization { tooi: z.string().regex(/^\d{1,}$/, 'is niet een geldige TOOI nummer').or(z.literal('')), rsin: z.string().regex(/^\d{9}$/, 'is niet een geldige RSIN nummer').or(z.literal('')), pki: z.string().regex(/^\d{1,}$/, 'is niet een geldige PKI nummer').or(z.literal('')), + image: z.string(), }) const result = schema.safeParse({ diff --git a/src/entities/organization/organization.types.ts b/src/entities/organization/organization.types.ts index 4d9b5b09..2f4480a4 100644 --- a/src/entities/organization/organization.types.ts +++ b/src/entities/organization/organization.types.ts @@ -7,4 +7,5 @@ export type TOrganization = { tooi: string rsin: string pki: string + image: string } diff --git a/src/entities/page/index.js b/src/entities/page/index.js new file mode 100644 index 00000000..b496e408 --- /dev/null +++ b/src/entities/page/index.js @@ -0,0 +1,4 @@ +export * from './page.ts' +export * from './page.types.ts' +export * from './page.mock.ts' + diff --git a/src/entities/page/page.mock.ts b/src/entities/page/page.mock.ts new file mode 100644 index 00000000..347adfc9 --- /dev/null +++ b/src/entities/page/page.mock.ts @@ -0,0 +1,43 @@ +import { Page } from './page' +import { TPage } from './page.types' + +/** + * Mock data function that returns an array of page data objects + * Used for testing and development purposes + */ +export const mockPageData = (): TPage[] => [ + { // full data + id: '1', + uuid: '123e4567-e89b-12d3-a456-426614174000', + name: 'Test Page', + slug: 'test-page', + contents: '{}', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + // @ts-expect-error -- expected missing contents + { // partial data + id: '2', + uuid: '123e4567-e89b-12d3-a456-426614174001', + name: 'Another Page', + slug: 'another-page', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { // invalid data + id: '3', + uuid: '123e4567-e89b-12d3-a456-426614174002', + name: '', + slug: '', + contents: '{}', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, +] + +/** + * Creates an array of Page instances from provided data or default mock data + * @param data Optional array of page data to convert to Page instances + * @returns Array of Page instances + */ +export const mockPage = (data: TPage[] = mockPageData()): TPage[] => data.map(item => new Page(item)) diff --git a/src/entities/page/page.spec.ts b/src/entities/page/page.spec.ts new file mode 100644 index 00000000..fa342192 --- /dev/null +++ b/src/entities/page/page.spec.ts @@ -0,0 +1,47 @@ +/* eslint-disable no-console */ +import { Page } from './page' +import { mockPage } from './page.mock' + +describe('Page Store', () => { + it('create Page entity with full data', () => { + const page = new Page(mockPage()[0]) + + expect(page).toBeInstanceOf(Page) + expect(page).toEqual(mockPage()[0]) + expect(page.uuid).toBe(mockPage()[0].uuid) + expect(page.name).toBe(mockPage()[0].name) + expect(page.slug).toBe(mockPage()[0].slug) + expect(page.contents).toEqual(mockPage()[0].contents) + expect(page.createdAt).toBe(mockPage()[0].createdAt) + expect(page.updatedAt).toBe(mockPage()[0].updatedAt) + + expect(page.validate().success).toBe(true) + }) + + it('create Page entity with partial data', () => { + const page = new Page(mockPage()[1]) + + expect(page).toBeInstanceOf(Page) + expect(page.id).toBe(mockPage()[1].id) + expect(page.uuid).toBe(mockPage()[1].uuid) + expect(page.name).toBe(mockPage()[1].name) + expect(page.slug).toBe(mockPage()[1].slug) + expect(page.contents).toBeNull() + expect(page.createdAt).toBe(mockPage()[1].createdAt) + expect(page.updatedAt).toBe(mockPage()[1].updatedAt) + + expect(page.validate().success).toBe(true) + }) + + it('create Page entity with falsy data', () => { + const page = new Page(mockPage()[2]) + + expect(page).toBeInstanceOf(Page) + expect(page).toEqual(mockPage()[2]) + expect(page.uuid).toBe(mockPage()[2].uuid) + expect(page.name).toBe('') + expect(page.contents).toBeNull() + + expect(page.validate().success).toBe(false) + }) +}) diff --git a/src/entities/page/page.ts b/src/entities/page/page.ts new file mode 100644 index 00000000..a58e1d78 --- /dev/null +++ b/src/entities/page/page.ts @@ -0,0 +1,61 @@ +import { SafeParseReturnType, z } from 'zod' +import { TPage } from './page.types' + +/** + * Page class representing a page entity with validation + * Implements the TPage interface for type safety + */ +export class Page implements TPage { + + public id: string + public uuid: string + public name: string + public slug: string + public contents: string + public createdAt: string + public updatedAt: string + + /** + * Creates a new Page instance + * @param data Initial page data conforming to TPage interface + */ + constructor(data: TPage) { + this.hydrate(data) + } + + /* istanbul ignore next */ // Jest does not recognize the code coverage of these 2 methods + /** + * Hydrates the page object with provided data + * @param data Page data to populate the instance + */ + private hydrate(data: TPage) { + this.id = data?.id?.toString() || '' + this.uuid = data?.uuid || '' + this.name = data?.name || '' + this.slug = data?.slug || '' + this.contents = data?.contents || '{}' + this.createdAt = data?.createdAt || '' + this.updatedAt = data?.updatedAt || '' + } + + /* istanbul ignore next */ + /** + * Validates the page data against a schema + * @return SafeParseReturnType containing validation result + */ + public validate(): SafeParseReturnType { + // Schema validation for page data + const schema = z.object({ + name: z.string().min(1, 'naam is verplicht'), + slug: z.string().min(1, 'slug is verplicht'), + contents: z.string().min(1, 'inhoud is verplicht'), + }) + + const result = schema.safeParse({ + ...this, + }) + + return result + } + +} diff --git a/src/entities/page/page.types.ts b/src/entities/page/page.types.ts new file mode 100644 index 00000000..b005d72c --- /dev/null +++ b/src/entities/page/page.types.ts @@ -0,0 +1,13 @@ +/** + * Type definition for a Page object + * Represents the structure of a page with content and metadata + */ +export type TPage = { + id: string // Unique identifier for the page + uuid: string // Unique identifier for the page + name: string // Title/heading of the page + contents: string // JSON object, Main content/body of the page - can contain any type of content + slug: string // URL-friendly version of the title + createdAt: string // Creation timestamp + updatedAt: string // Last update timestamp +} diff --git a/src/entities/publication/publication.mock.ts b/src/entities/publication/publication.mock.ts index 5e717bc0..890cbdda 100644 --- a/src/entities/publication/publication.mock.ts +++ b/src/entities/publication/publication.mock.ts @@ -15,6 +15,7 @@ export const mockPublicationsData = (): TPublication[] => [ publicationType: '4', published: '2024-09-04T12:36:39Z', modified: '2024-09-04T12:36:39Z', + organization: '1', featured: true, data: { key: 'anyvalue', @@ -60,6 +61,7 @@ export const mockPublicationsData = (): TPublication[] => [ published: '2024-09-04T12:36:39Z', modified: '2024-09-04T12:36:39Z', featured: true, + organization: '1', data: { type: '', }, diff --git a/src/entities/publication/publication.ts b/src/entities/publication/publication.ts index dee7e62f..2e5bd360 100644 --- a/src/entities/publication/publication.ts +++ b/src/entities/publication/publication.ts @@ -21,6 +21,7 @@ export class Publication implements TPublication { public attachments: number[] public attachmentCount: number public themes: string[] + public organization: string public data: Record public anonymization: { @@ -60,6 +61,7 @@ export class Publication implements TPublication { this.description = data.description || '' this.reference = data.reference || '' this.image = data.image || '' + this.organization = data.organization || '' this.category = data.category || '' this.portal = data.portal || '' this.featured = (typeof data.featured === 'boolean' && data.featured) @@ -113,6 +115,7 @@ export class Publication implements TPublication { portal: z.string().url('is niet een url').or(z.literal('')), featured: z.boolean(), schema: z.string(), + organization: z.string(), status: z.enum(['Concept', 'Published', 'Withdrawn', 'Archived', 'Revised', 'Rejected']), attachments: z.union([z.string(), z.number()]).array(), attachmentCount: z.number(), diff --git a/src/entities/publication/publication.types.ts b/src/entities/publication/publication.types.ts index 52a80a94..a99f6194 100644 --- a/src/entities/publication/publication.types.ts +++ b/src/entities/publication/publication.types.ts @@ -17,6 +17,7 @@ export type TPublication = { status: 'Concept' | 'Published' | 'Withdrawn' | 'Archived' | 'Revised' | 'Rejected' attachments: number[] attachmentCount: number + organization: string themes: string[] data: Record anonymization: { diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index 498e7bf8..94482e76 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -25,11 +25,12 @@ import { publicationStore } from './../store/store.js' + + diff --git a/src/modals/organization/AddOrganizationModal.vue b/src/modals/organization/AddOrganizationModal.vue index b42652da..8776de83 100644 --- a/src/modals/organization/AddOrganizationModal.vue +++ b/src/modals/organization/AddOrganizationModal.vue @@ -64,6 +64,12 @@ import { navigationStore, organizationStore } from '../../store/store.js' :value.sync="organization.pki" :error="!!inputValidation.fieldErrors?.['pki']" :helper-text="inputValidation.fieldErrors?.['pki']?.[0]" /> + + +import { navigationStore, pageStore } from '../../store/store.js' +import { getTheme } from '../../services/getTheme.js' + + + + + + + diff --git a/src/modals/page/EditPageModal.vue b/src/modals/page/EditPageModal.vue new file mode 100644 index 00000000..c1d45ce3 --- /dev/null +++ b/src/modals/page/EditPageModal.vue @@ -0,0 +1,207 @@ + + + + + + diff --git a/src/modals/publication/AddPublicationModal.vue b/src/modals/publication/AddPublicationModal.vue index 5a9e2910..c5252d27 100644 --- a/src/modals/publication/AddPublicationModal.vue +++ b/src/modals/publication/AddPublicationModal.vue @@ -1,5 +1,5 @@