diff --git a/appinfo/routes.php b/appinfo/routes.php index a14b912e..e58f75ba 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -15,6 +15,7 @@ ['name' => 'metadata#page', 'url' => '/metadata', 'verb' => 'GET'], ['name' => 'publications#page', 'url' => '/publications', 'verb' => 'GET'], ['name' => 'publications#attachments', 'url' => '/api/publications/{id}/attachments', 'verb' => 'GET', 'requirements' => ['id' => '.+']], + ['name' => 'publications#download', 'url' => '/api/publications/{id}/download', 'verb' => 'GET', 'requirements' => ['id' => '.+']], ['name' => 'catalogi#page', 'url' => '/catalogi', 'verb' => 'GET'], ['name' => 'search#index', 'url' => '/search', 'verb' => 'GET'], ['name' => 'search#index', 'url' => '/api/search', 'verb' => 'GET'], diff --git a/composer.json b/composer.json index b24de24d..2a4dc0fa 100644 --- a/composer.json +++ b/composer.json @@ -40,11 +40,13 @@ }, "require": { "php": "^8.1", + "ext-zip": "*", "adbario/php-dot-notation": "^3.3.0", "bamarni/composer-bin-plugin": "^1.8", "elasticsearch/elasticsearch": "^v8.14.0", - "adbario/php-dot-notation": "^3.3.0", "guzzlehttp/guzzle": "^7.0", + "mpdf/mpdf": "^8.2", + "symfony/twig-bundle": "^6.4", "symfony/uid": "^6.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index d995fb24..7284d015 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": "c36605d341f3e840f8189a55ad611884", + "content-hash": "93ceb09bbb9b85fb7d77d68d6d6eefe1", "packages": [ { "name": "adbario/php-dot-notation", @@ -555,6 +555,289 @@ ], "time": "2023-12-03T20:05:35+00:00" }, + { + "name": "mpdf/mpdf", + "version": "v8.2.4", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/9e3ff91606fed11cd58a130eabaaf60e56fdda88", + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "mpdf/psr-http-message-shim": "^1.0 || ^2.0", + "mpdf/psr-log-aware-trait": "^2.0 || ^3.0", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^1.3.0", + "mpdf/qrcode": "^1.1.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "~2.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "support": { + "docs": "http://mpdf.github.io", + "issues": "https://github.com/mpdf/mpdf/issues", + "source": "https://github.com/mpdf/mpdf" + }, + "funding": [ + { + "url": "https://www.paypal.me/mpdf", + "type": "custom" + } + ], + "time": "2024-06-14T16:06:41+00:00" + }, + { + "name": "mpdf/psr-http-message-shim", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-http-message-shim.git", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f", + "shasum": "" + }, + "require": { + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrHttpMessageShim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + }, + { + "name": "Nigel Cunningham", + "email": "nigel.cunningham@technocrat.com.au" + } + ], + "description": "Shim to allow support of different psr/message versions.", + "support": { + "issues": "https://github.com/mpdf/psr-http-message-shim/issues", + "source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1" + }, + "time": "2023-10-02T14:34:03+00:00" + }, + { + "name": "mpdf/psr-log-aware-trait", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-log-aware-trait.git", + "reference": "7a077416e8f39eb626dee4246e0af99dd9ace275" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/7a077416e8f39eb626dee4246e0af99dd9ace275", + "reference": "7a077416e8f39eb626dee4246e0af99dd9ace275", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrLogAwareTrait\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + } + ], + "description": "Trait to allow support of different psr/log versions.", + "support": { + "issues": "https://github.com/mpdf/psr-log-aware-trait/issues", + "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v2.0.0" + }, + "time": "2023-05-03T06:18:28+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-http/discovery", "version": "1.19.4", @@ -744,32 +1027,31 @@ "time": "2024-03-15T13:55:21+00:00" }, { - "name": "psr/http-client", - "version": "1.0.3", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Client\\": "src/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -782,36 +1064,37 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "http", - "http-client", - "psr", - "psr-18" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "source": "https://github.com/php-fig/http-client" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2023-09-23T14:17:50+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "psr/http-factory", - "version": "1.1.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" + "php": ">=7.2.0" }, "type": "library", "extra": { @@ -821,7 +1104,7 @@ }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Psr\\EventDispatcher\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -831,51 +1114,48 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "description": "Standard interfaces for event handling.", "keywords": [ - "factory", - "http", - "message", + "events", "psr", - "psr-17", - "psr-7", - "request", - "response" + "psr-14" ], "support": { - "source": "https://github.com/php-fig/http-factory" + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" }, - "time": "2024-04-15T12:06:14+00:00" + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "psr/http-message", - "version": "2.0", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -888,7 +1168,113 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for HTTP messages", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", "homepage": "https://github.com/php-fig/http-message", "keywords": [ "http", @@ -998,36 +1384,41 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "name": "setasign/fpdi", + "version": "v2.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "url": "https://github.com/Setasign/FPDI.git", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", "shasum": "" }, "require": { - "php": ">=8.1" + "ext-zlib": "*", + "php": "^5.6 || ^7.0 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "conflict": { + "setasign/tfpdf": "<1.31" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." }, + "type": "library", "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "setasign\\Fpdi\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1035,72 +1426,74 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.6.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2023-12-11T16:03:32+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "name": "symfony/config", + "version": "v6.4.8", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "url": "https://github.com/symfony/config.git", + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, - "provide": { - "ext-uuid": "*" + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, - "suggest": { - "ext-uuid": "For best performance" + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Uuid\\": "" - } + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1108,24 +1501,18 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for uuid functions", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "uuid" - ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/config/tree/v6.4.8" }, "funding": [ { @@ -1141,33 +1528,49 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { - "name": "symfony/uid", - "version": "v6.4.8", + "name": "symfony/dependency-injection", + "version": "v6.4.10", "source": { "type": "git", - "url": "https://github.com/symfony/uid.git", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "5caf9c5f6085f13b27d70a236b776c07e4a1c3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5caf9c5f6085f13b27d70a236b776c07e4a1c3eb", + "reference": "5caf9c5f6085f13b27d70a236b776c07e4a1c3eb", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/polyfill-uuid": "^1.15" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Uid\\": "" + "Symfony\\Component\\DependencyInjection\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1179,27 +1582,18 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to generate and represent UIDs", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", - "keywords": [ - "UID", - "ulid", - "uuid" - ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.8" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.10" }, "funding": [ { @@ -1215,76 +1609,1506 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" - } - ], - "packages-dev": [ + "time": "2024-07-26T07:32:07+00:00" + }, { - "name": "nextcloud/ocp", - "version": "dev-stable29", + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", "source": { "type": "git", - "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/65b6744fca5d4b3c366754295e5cb0680a580c51", - "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { - "php": "~8.0 || ~8.1 || ~8.2 || ~8.3", - "psr/clock": "^1.0", - "psr/container": "^2.0.2", - "psr/event-dispatcher": "^1.0", - "psr/log": "^1.1.4" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-stable29": "29.0.0-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, + "autoload": { + "files": [ + "function.php" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "AGPL-3.0-or-later" + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:30:32+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T09:49:33+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "117f1f20a7ade7bcea28b861fb79160a21a1e37b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/117f1f20a7ade7bcea28b861fb79160a21a1e37b", + "reference": "117f1f20a7ade7bcea28b861fb79160a21a1e37b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.3" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:36:27+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "147e0daf618d7575b5007055340d09aece5cf068" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/147e0daf618d7575b5007055340d09aece5cf068", + "reference": "147e0daf618d7575b5007055340d09aece5cf068", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T14:52:04+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:35:24+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v6.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9bcb26445b9d4ef1087c389234bf33fb00e10ea6", + "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.2", + "symfony/serializer": "<6.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-21T16:04:15+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" + }, + "require-dev": { + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], "authors": [ { - "name": "Christoph Wurst", - "email": "christoph@winzerhof-wurst.at" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Composer package containing Nextcloud's public API (classes, interfaces)", + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], "support": { - "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/stable29" + "source": "https://github.com/symfony/uid/tree/v6.4.8" }, - "time": "2024-07-11T00:37:34+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" }, { - "name": "psr/clock", - "version": "1.0.0", + "name": "symfony/var-dumper", + "version": "v6.4.10", "source": { "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "a71cc3374f5fb9759da1961d28c452373b343dd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a71cc3374f5fb9759da1961d28c452373b343dd4", + "reference": "a71cc3374f5fb9759da1961d28c452373b343dd4", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "autoload": { + "files": [ + "Resources/functions/dump.php" + ], "psr-4": { - "Psr\\Clock\\": "src/" - } + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1292,104 +3116,263 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" + "debug", + "dump" ], "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.10" }, - "time": "2022-11-25T14:36:26+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:30:32+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "symfony/var-exporter", + "version": "v6.4.9", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/symfony/var-exporter.git", + "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", + "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } + ], + "time": "2024-06-24T15:53:56+00:00" + }, + { + "name": "twig/twig", + "version": "v3.11.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "shasum": "" }, + "require": { + "php": ">=7.2.5", + "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": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { - "Psr\\Container\\": "src/" + "Twig\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "templating" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.11.0" }, - "time": "2021-11-05T16:47:00+00:00" - }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-08-08T16:15:16+00:00" + } + ], + "packages-dev": [ { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "nextcloud/ocp", + "version": "dev-stable29", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/nextcloud-deps/ocp.git", + "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/65b6744fca5d4b3c366754295e5cb0680a580c51", + "reference": "65b6744fca5d4b3c366754295e5cb0680a580c51", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": "~8.0 || ~8.1 || ~8.2 || ~8.3", + "psr/clock": "^1.0", + "psr/container": "^2.0.2", + "psr/event-dispatcher": "^1.0", + "psr/log": "^1.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-stable29": "29.0.0-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" } + ], + "description": "Composer package containing Nextcloud's public API (classes, interfaces)", + "support": { + "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" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\EventDispatcher\\": "src/" + "Psr\\Clock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1399,20 +3382,23 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Standard interfaces for event handling.", + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "events", + "clock", + "now", "psr", - "psr-14" + "psr-20", + "time" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2019-01-08T18:20:26+00:00" + "time": "2022-11-25T14:36:26+00:00" }, { "name": "roave/security-advisories", @@ -2234,7 +4220,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.1" + "php": "^8.1", + "ext-zip": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/css/main.css b/css/main.css index 43c4a467..9a25a17a 100644 --- a/css/main.css +++ b/css/main.css @@ -122,3 +122,34 @@ color: var(--color-error); } + +/* File drag and drop */ + +.filesListDragDropNotice{ + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 113px; + margin: 0; + user-select: none; + color: var(--color-text-maxcontrast); + background-color: var(--color-main-background); + border-color: #000; +} + +.filesListDragDropNoticeWrapper{ + display: flex; + align-items: center; + justify-content: center; + height: 15vh; + max-height: 70%; + padding: 0 5vw; + border: 2px var(--color-border-dark) dashed; + border-radius: var(--border-radius-large); +} + +.filesListDragDropNoticeTitle{ + margin-left: 16px; + color: inherit; +} diff --git a/docs/.gitbook/assets/Metadata_modal.png b/docs/.gitbook/assets/Metadata_modal.png new file mode 100644 index 00000000..15e00a34 Binary files /dev/null and b/docs/.gitbook/assets/Metadata_modal.png differ diff --git a/docs/.gitbook/assets/Screenshot 2024-08-08 113242.png b/docs/.gitbook/assets/Screenshot 2024-08-08 113242.png new file mode 100644 index 00000000..73388011 Binary files /dev/null and b/docs/.gitbook/assets/Screenshot 2024-08-08 113242.png differ diff --git a/docs/.gitbook/assets/Screenshot 2024-08-08 142036.png b/docs/.gitbook/assets/Screenshot 2024-08-08 142036.png new file mode 100644 index 00000000..03ba470a Binary files /dev/null and b/docs/.gitbook/assets/Screenshot 2024-08-08 142036.png differ diff --git a/docs/.gitbook/assets/bijlage_toevoegen.png b/docs/.gitbook/assets/bijlage_toevoegen.png new file mode 100644 index 00000000..dd356668 Binary files /dev/null and b/docs/.gitbook/assets/bijlage_toevoegen.png differ diff --git a/docs/.gitbook/assets/bijlage_toevoegen_actieknop.png b/docs/.gitbook/assets/bijlage_toevoegen_actieknop.png new file mode 100644 index 00000000..c697705c Binary files /dev/null and b/docs/.gitbook/assets/bijlage_toevoegen_actieknop.png differ diff --git a/docs/.gitbook/assets/bijlage_toevoegen_drie_bolletjes.png b/docs/.gitbook/assets/bijlage_toevoegen_drie_bolletjes.png new file mode 100644 index 00000000..a33e4c50 Binary files /dev/null and b/docs/.gitbook/assets/bijlage_toevoegen_drie_bolletjes.png differ diff --git a/docs/.gitbook/assets/bijlage_toevoegen_modal.png b/docs/.gitbook/assets/bijlage_toevoegen_modal.png new file mode 100644 index 00000000..d734b54f Binary files /dev/null and b/docs/.gitbook/assets/bijlage_toevoegen_modal.png differ diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png new file mode 100644 index 00000000..cce857e5 Binary files /dev/null and b/docs/.gitbook/assets/image (1).png differ diff --git a/docs/.gitbook/assets/image (2).png b/docs/.gitbook/assets/image (2).png new file mode 100644 index 00000000..e811a485 Binary files /dev/null and b/docs/.gitbook/assets/image (2).png differ diff --git a/docs/.gitbook/assets/image.png b/docs/.gitbook/assets/image.png new file mode 100644 index 00000000..0b00a48c Binary files /dev/null and b/docs/.gitbook/assets/image.png differ diff --git a/docs/.gitbook/assets/metadata-settings.png b/docs/.gitbook/assets/metadata-settings.png new file mode 100644 index 00000000..6aff7d44 Binary files /dev/null and b/docs/.gitbook/assets/metadata-settings.png differ diff --git a/docs/.gitbook/assets/publicatie_modal.png b/docs/.gitbook/assets/publicatie_modal.png new file mode 100644 index 00000000..b675d97a Binary files /dev/null and b/docs/.gitbook/assets/publicatie_modal.png differ diff --git a/docs/.gitbook/assets/publicatie_mogelijkheden.png b/docs/.gitbook/assets/publicatie_mogelijkheden.png new file mode 100644 index 00000000..0b412c2d Binary files /dev/null and b/docs/.gitbook/assets/publicatie_mogelijkheden.png differ diff --git a/docs/.gitbook/assets/publicatie_toevoegen_modal.png b/docs/.gitbook/assets/publicatie_toevoegen_modal.png new file mode 100644 index 00000000..d45b2acc Binary files /dev/null and b/docs/.gitbook/assets/publicatie_toevoegen_modal.png differ diff --git a/docs/README.md b/docs/README.md index 0efb6c7f..14317ca5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,23 +3,24 @@ description: >- Welkom bij de gebruikersdocumentatie voor de OpenCatalogi Nextcloud App. Veel succes met het gebruik van de app. -coverY: 0 ---------- +---------------------------------- # Welkom +*** + Deze documentatie richt zich op het gebruik van onze beheerapplicatie, speciaal ontworpen voor het beheren van publicaties en catalogi binnen het federatief netwerk. De OpenCatalogi Nextcloud App is een eenvoudig te installeren: -* [**Quickstart**](installatie/instructies.md)voor een test/demo-omgeving +* [**Quickstart**](installatie/instructies.md) voor een test/demo-omgeving * [**Quickstart** ](developers/installatie-van-nextcloud-development-omgeving.md)voor een development-omgeving Onze app ondersteunt de Common Ground-aanpak, waardoor je snel toegang hebt tot bestaande IT-oplossingen die je kunt hergebruiken om de ontwikkeltijd te verkorten en de kosten te verlagen. In deze gids vind je stapsgewijze instructies, nuttige tips en best practices om je te helpen bij het optimaal beheren van je federatief netwerk, zoals publicaties of softwarecomponenten. Deze documentatie is bedoeld voor diverse doelgroepen: -* **Gebruikers:** iedereen die wilt delen binnen het netwerk. +* **Gebruikers:** iedereen die wil delen binnen het netwerk. * **Developers:** Ontwikkelaars die bijdragen aan de OpenCatalogi-projecten en behoefte hebben aan gedetailleerde technische informatie en API-documentatie. -* **Beheerders:** Professionals die verantwoordelijk zijn voor het beheren en onderhouden van het federatief netwerk voor publicaites en componenten. +* **Beheerders:** Professionals die verantwoordelijk zijn voor het beheren en onderhouden van het federatief netwerk voor publicaties en componenten. Voor meer informatie over OpenCatalogi en onze gemeenschappelijke inspanningen, bezoek onze [documentatie-pagina](https://documentatie.opencatalogi.nl) of de officiële website op [OpenCatalogi.nl](https://opencatalogi.nl). diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 33c123b8..13d66e28 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -24,4 +24,4 @@ * [SAAS en Dashboarding](installatie/saas.md) * [On-Prem server](installatie/on-prem-server.md) * [Systeemeisen voor Nextcloud](installatie/systeemeisen-voor-nextcloud.md) -* [Veel gestelde vragen](f-a-g.md) +* [Veel gestelde vragen](veel-gestelde-vragen.md) diff --git a/docs/assets/Opencatalogi CRUD.postman_collection.json b/docs/assets/Opencatalogi CRUD.postman_collection.json new file mode 100644 index 00000000..b843d3f5 --- /dev/null +++ b/docs/assets/Opencatalogi CRUD.postman_collection.json @@ -0,0 +1,553 @@ +{ + "info": { + "_postman_id": "f1d14358-ccc2-4650-9851-e46e2fb666e5", + "name": "Opencatalogi CRUD", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "9365542", + "_collection_link": "https://conduction.postman.co/workspace/Gateway~7f74e723-2263-4ee5-855f-a2ea9cce0681/collection/9365542-f1d14358-ccc2-4650-9851-e46e2fb666e5?action=share&source=collection_link&creator=9365542" + }, + "item": [ + { + "name": "Search", + "item": [ + { + "name": "General Search", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/search", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "search" + ], + "query": [ + { + "key": "_search", + "value": "bijzondere bijstand", + "description": "General search query", + "disabled": true + }, + { + "key": "_queries[]", + "value": "data.status", + "description": "Define fields that should be returned with the facets (example field to be replaced)", + "disabled": true + }, + { + "key": "title", + "value": "Kubus bijzondere bijstand", + "description": "Filter on specific fields.", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get specific object", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/search/fa393c4e-3fc2-4787-ab43-fd58ce190fb4", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "search", + "fa393c4e-3fc2-4787-ab43-fd58ce190fb4" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Directory", + "item": [ + { + "name": "List directory", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/directory", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "directory" + ] + } + }, + "response": [] + }, + { + "name": "Create Directory", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Test\",\r\n \"summary\": \"Test directory for federation\",\r\n \"description\": \"Test directory for federation\",\r\n \"search\": \"https://api.common-gateway.commonground.nu/api/search\",\r\n \"directory\": \"https://eo9d4l3y6q4y2vt.m.pipedream.net\",\r\n \"metadata\": [\r\n \"http://example.com\"\r\n ],\r\n \"status\": \"200\",\r\n \"lastSync\": \"2019-08-24T14:15:22Z\",\r\n \"default\": false,\r\n \"available\": true,\r\n \"_schema\": \"directory\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/directory", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "directory" + ] + } + }, + "response": [] + }, + { + "name": "Get Directory", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/directory/96e12db1-897b-41e1-a08d-acb4e2be040b", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "directory", + "96e12db1-897b-41e1-a08d-acb4e2be040b" + ] + } + }, + "response": [] + }, + { + "name": "Delete Directory", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{environment}}/api/directory/96e12db1-897b-41e1-a08d-acb4e2be040b", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "directory", + "96e12db1-897b-41e1-a08d-acb4e2be040b" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Publication", + "item": [ + { + "name": "List publications", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/publications", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications" + ] + } + }, + "response": [] + }, + { + "name": "Create Publication", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Woningoppervlaktes\",\r\n \"reference\": \"test1234\",\r\n \"description\": \"Woningoppervlaktes geeft het gebruiksoppervlakte aan woningen per gebied, geclassificeerd ten behoeve van het bepalen van de benodigde parkeercapaciteit.\",\r\n \"summary\": \"Woningoppervlaktes geeft het gebruiksoppervlakte aan woningen per gebied, geclassificeerd ten behoeve van het bepalen van de benodigde parkeercapaciteit.\",\r\n \"catalogi\": \"7a048bfd-210f-4e93-a1e8-5aa9261740b7\",\r\n \"metaData\": \"468f440f-7af0-453a-8d5f-ffe644ab0673\",\r\n \"organization\": null,\r\n \"data\": {\r\n \"id\": \"33f88aa9-6ac0-4f6c-967e-ecf787fd6a3d\",\r\n \"reference\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"title\": \"Input voor OpenCatalogi\",\r\n \"summary\": \"Dit is een selectie van high-value datasets in DCAT-AP 2.0 standaard x\",\r\n \"category\": \"Dataset\",\r\n \"portal\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"published\": \"2020-04-07\",\r\n \"modified\": \"2020-12-29\",\r\n \"featured\": false,\r\n \"schema\": \"https:\\/\\/openwoo.app\\/schemas\\/metadata.dcat_catalog.schema.json\",\r\n \"status\": \"published\",\r\n \"license\": \"CC0 1.0\",\r\n \"attachments\": [\r\n {\r\n \"id\": \"ba9e5f64-f6ee-4c62-99bd-e9176372f4c2\",\r\n \"title\": \"woningoppervlaktes feature layer\",\r\n \"description\": \"ESRI feature layer met woningoppervlaktes per TIR-buurt en per TIR-blok.\",\r\n \"license\": \"notspecified\",\r\n \"type\": \"API\",\r\n \"published\": \"24-12-2020\",\r\n \"modified\": \"30 december 2020, 11:55 (UTC+01:00)\",\r\n \"accessURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\",\r\n \"downloadURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\"\r\n }\r\n ],\r\n \"attachmentCount\": 1,\r\n \"themes\": [\r\n \"SODA\",\r\n \"kennisloods\",\r\n \"mobiliteit\",\r\n \"oppervlakte\",\r\n \"oppervlaktes\",\r\n \"parkeercapaciteit\",\r\n \"parkeren\",\r\n \"soda verblijfsobject\",\r\n \"verblijfsobjecten\",\r\n \"woning\",\r\n \"woningen\",\r\n \"woningoppervlakte\",\r\n \"woningoppervlaktes\"\r\n ],\r\n \"data\": {\r\n \"spatial\": \"[55500,428647,101033,447000]\",\r\n \"contactPoint\": {\r\n \"name\": \"gemeente Rotterdam, Stadsontwikkeling, SODA\",\r\n \"email\": \"dataSO@rotterdam.nl\"\r\n },\r\n \"qualifiedAttribution\": {\r\n \"responsible\": {\r\n \"name\": \"gemeente Rotterdam, Stadsontwikkeling, SODA\",\r\n \"email\": \"dataSO@rotterdam.nl\"\r\n },\r\n \"role\": {\r\n \"name\": \"beheerder\"\r\n }\r\n },\r\n \"accrualPeriodicity\": \"onregelmatig\"\r\n },\r\n \"anonymization\": {\r\n \"anonymized\": true\r\n },\r\n \"language\": {\r\n \"code\": \"nl-nl\",\r\n \"level\": \"A1\"\r\n }\r\n },\r\n \"attachments\": [\r\n {\r\n \"id\": \"ba9e5f64-f6ee-4c62-99bd-e9176372f4c2\",\r\n \"title\": \"woningoppervlaktes feature layer\",\r\n \"description\": \"ESRI feature layer met woningoppervlaktes per TIR-buurt en per TIR-blok.\",\r\n \"license\": \"notspecified\",\r\n \"type\": \"API\",\r\n \"published\": \"24-12-2020\",\r\n \"modified\": \"30 december 2020, 11:55 (UTC+01:00)\",\r\n \"accessURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\",\r\n \"downloadURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\"\r\n }\r\n ],\r\n \"attachmentCount\": 1,\r\n \"license\": \"notspecified\",\r\n \"modified\": \"2020-12-29\",\r\n \"publicationDate\": \"2020-04-07\",\r\n \"status\": \"published\",\r\n \"featured\": false,\r\n \"portal\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"category\": \"Dataset\",\r\n \"image\": \"https:\\/\\/dev.opencatalogi.nl\\/static\\/logo_OpenCatalogi-8b1b0a001c3f37dae4d3f69b5964ec72.png\",\r\n \"schema\": \"publications\",\r\n \"themes\": null,\r\n \"anonymization\": null,\r\n \"languageObject\": null\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/publications", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications" + ] + } + }, + "response": [] + }, + { + "name": "Get Publication", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Update Publication", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Test woningoppervlakte\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Delete Directory", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{environment}}/api/publications/3", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "3" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Attachment", + "item": [ + { + "name": "List publications", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/publications", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications" + ] + } + }, + "response": [] + }, + { + "name": "Create Publication", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Woningoppervlaktes\",\r\n \"reference\": \"test1234\",\r\n \"description\": \"Woningoppervlaktes geeft het gebruiksoppervlakte aan woningen per gebied, geclassificeerd ten behoeve van het bepalen van de benodigde parkeercapaciteit.\",\r\n \"summary\": \"Woningoppervlaktes geeft het gebruiksoppervlakte aan woningen per gebied, geclassificeerd ten behoeve van het bepalen van de benodigde parkeercapaciteit.\",\r\n \"catalogi\": \"7a048bfd-210f-4e93-a1e8-5aa9261740b7\",\r\n \"metaData\": \"468f440f-7af0-453a-8d5f-ffe644ab0673\",\r\n \"organization\": null,\r\n \"data\": {\r\n \"id\": \"33f88aa9-6ac0-4f6c-967e-ecf787fd6a3d\",\r\n \"reference\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"title\": \"Input voor OpenCatalogi\",\r\n \"summary\": \"Dit is een selectie van high-value datasets in DCAT-AP 2.0 standaard x\",\r\n \"category\": \"Dataset\",\r\n \"portal\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"published\": \"2020-04-07\",\r\n \"modified\": \"2020-12-29\",\r\n \"featured\": false,\r\n \"schema\": \"https:\\/\\/openwoo.app\\/schemas\\/metadata.dcat_catalog.schema.json\",\r\n \"status\": \"published\",\r\n \"license\": \"CC0 1.0\",\r\n \"attachments\": [\r\n {\r\n \"id\": \"ba9e5f64-f6ee-4c62-99bd-e9176372f4c2\",\r\n \"title\": \"woningoppervlaktes feature layer\",\r\n \"description\": \"ESRI feature layer met woningoppervlaktes per TIR-buurt en per TIR-blok.\",\r\n \"license\": \"notspecified\",\r\n \"type\": \"API\",\r\n \"published\": \"24-12-2020\",\r\n \"modified\": \"30 december 2020, 11:55 (UTC+01:00)\",\r\n \"accessURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\",\r\n \"downloadURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\"\r\n }\r\n ],\r\n \"attachmentCount\": 1,\r\n \"themes\": [\r\n \"SODA\",\r\n \"kennisloods\",\r\n \"mobiliteit\",\r\n \"oppervlakte\",\r\n \"oppervlaktes\",\r\n \"parkeercapaciteit\",\r\n \"parkeren\",\r\n \"soda verblijfsobject\",\r\n \"verblijfsobjecten\",\r\n \"woning\",\r\n \"woningen\",\r\n \"woningoppervlakte\",\r\n \"woningoppervlaktes\"\r\n ],\r\n \"data\": {\r\n \"spatial\": \"[55500,428647,101033,447000]\",\r\n \"contactPoint\": {\r\n \"name\": \"gemeente Rotterdam, Stadsontwikkeling, SODA\",\r\n \"email\": \"dataSO@rotterdam.nl\"\r\n },\r\n \"qualifiedAttribution\": {\r\n \"responsible\": {\r\n \"name\": \"gemeente Rotterdam, Stadsontwikkeling, SODA\",\r\n \"email\": \"dataSO@rotterdam.nl\"\r\n },\r\n \"role\": {\r\n \"name\": \"beheerder\"\r\n }\r\n },\r\n \"accrualPeriodicity\": \"onregelmatig\"\r\n },\r\n \"anonymization\": {\r\n \"anonymized\": true\r\n },\r\n \"language\": {\r\n \"code\": \"nl-nl\",\r\n \"level\": \"A1\"\r\n }\r\n },\r\n \"attachments\": [\r\n {\r\n \"id\": \"ba9e5f64-f6ee-4c62-99bd-e9176372f4c2\",\r\n \"title\": \"woningoppervlaktes feature layer\",\r\n \"description\": \"ESRI feature layer met woningoppervlaktes per TIR-buurt en per TIR-blok.\",\r\n \"license\": \"notspecified\",\r\n \"type\": \"API\",\r\n \"published\": \"24-12-2020\",\r\n \"modified\": \"30 december 2020, 11:55 (UTC+01:00)\",\r\n \"accessURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\",\r\n \"downloadURL\": \"https:\\/\\/services.arcgis.com\\/zP1tGdLpGvt2qNJ6\\/arcgis\\/rest\\/services\\/Woningoppervlaktes\\/FeatureServer\"\r\n }\r\n ],\r\n \"attachmentCount\": 1,\r\n \"license\": \"notspecified\",\r\n \"modified\": \"2020-12-29\",\r\n \"publicationDate\": \"2020-04-07\",\r\n \"status\": \"published\",\r\n \"featured\": false,\r\n \"portal\": \"https:\\/\\/catalogus-rotterdam.dataplatform.nl\\/dataset\\/voorlopige-energielabels-met-bag-kenmerken\",\r\n \"category\": \"Dataset\",\r\n \"image\": \"https:\\/\\/dev.opencatalogi.nl\\/static\\/logo_OpenCatalogi-8b1b0a001c3f37dae4d3f69b5964ec72.png\",\r\n \"schema\": \"publications\",\r\n \"themes\": null,\r\n \"anonymization\": null,\r\n \"languageObject\": null\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/publications", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications" + ] + } + }, + "response": [] + }, + { + "name": "Get Publication", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Update Publication", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Test woningoppervlakte\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Delete Directory", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{environment}}/api/publications/3", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "3" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Catalogi", + "item": [ + { + "name": "List Catalogi", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/catalogi", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "catalogi" + ] + } + }, + "response": [] + }, + { + "name": "Create Catalogi", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"DCAT\",\r\n \"reference\": \"test1234\",\r\n \"description\": \"Test voor DCAT objecten.\",\r\n \"summary\": \"Test voor DCAT objecten.\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/catalogi", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "catalogi" + ] + } + }, + "response": [] + }, + { + "name": "Get Publication", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Update Publication", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"Test woningoppervlakte\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{environment}}/api/publications/1", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "publications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Delete Catalog", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{environment}}/api/directory/96e12db1-897b-41e1-a08d-acb4e2be040b", + "host": [ + "{{environment}}" + ], + "path": [ + "api", + "directory", + "96e12db1-897b-41e1-a08d-acb4e2be040b" + ] + } + }, + "response": [] + } + ] + } + ], + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "admin", + "type": "string" + }, + { + "key": "username", + "value": "admin", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "environment", + "value": "http://nextcloud.local/index.php/apps/opencatalogi", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/docs/beheerders/README.md b/docs/beheerders/README.md index 2f76a5ce..6afccf02 100644 --- a/docs/beheerders/README.md +++ b/docs/beheerders/README.md @@ -1 +1,13 @@ -# beheerders +*** + +description: >- +De rol van een "Beheerder" binnen het platform is gericht op het beheren van +de publicaties, catalogi en metadata, evenals de autorisatie van gebruikers. +De verantwoordelijkheden van de beheerder: +------------------------------------------ + +# Beheerders + +* **Beheer van Publicaties:** Beheerders hebben toegang tot een overzicht van alle (concept-)publicaties, het bewerken van publicatie, verwijderen en (de)publiceren. +* **Metadata en Thema's Beheren:** Beheerders kunnen het metadatamodel configureren, extra informatiecategorieën en thema's toevoegen en beheren. Ze kunnen deze koppelen aan publicaties voor beter beheer en toegang. +* **Logging en Monitoring:** diff --git a/docs/beheerders/catalogi.md b/docs/beheerders/catalogi.md index 0d82a73b..44751e22 100644 --- a/docs/beheerders/catalogi.md +++ b/docs/beheerders/catalogi.md @@ -12,7 +12,7 @@ De organisatie-eigen catalogi (waartoe een gebruiker toegang heeft) zijn opgenom ## Catalogi beheren -Catalogi kunnen worden beheerd via het menu-item Instellingen -> Catalogi. +Catalogi kunnen worden beheerd via het menu-item Instellingen -> Catalogi en dan de drie bolletjes te selecten voor de opties. * **Configuratie**: Onder de configuratie van een catalogus kan worden aangegeven * Of deze actief is (anders wordt de catalogus niet getoond in het navigatiemenu en de zoekresultaten) diff --git a/docs/developers/README.md b/docs/developers/README.md index 4296a157..6130c59a 100644 --- a/docs/developers/README.md +++ b/docs/developers/README.md @@ -1 +1,11 @@ -# developers +*** + +description: >- +De rol van een "Developer" binnen het publicatieplatform is voornamelijk +gericht om bij te dragen aan de ontwikkeling aan de codebase of om +aanpassingen te kunnen testen of demonstreren. +---------------------------------------------- + +# Developers + +In dit hoofdstuk worden de verschillende manieren gegeven om een test- of demo-omgevingen op te starten. diff --git a/docs/developers/aan-de-slag-met-development.md b/docs/developers/aan-de-slag-met-development.md index 78d61f16..841aa293 100644 --- a/docs/developers/aan-de-slag-met-development.md +++ b/docs/developers/aan-de-slag-met-development.md @@ -89,7 +89,11 @@ Als onderdeel van de CI/CD-straat voeren we een aantal tests uit, hiermee handha ### Voor de kwaliteit van de code maken we gebruik van linters -Voor frontend is dat: +Voorzowel de frontend als de backend geldt dat het aantal acceptabele errors 0 is. + +#### Frontend + +Voor frontend gebruiken we ESLint, de installatiehandleiding is [hier](https://www.npmjs.com/package/eslint) te vinden. Het commando om ESLint uit te voeren. ESLint is voornamelijk een linter, met enige format-functionaliteit. ```cli npm run lint @@ -97,19 +101,22 @@ npm run lint ![alt text](npm_lint.png) -Voor de backend is dat: +#### Backend + +Voor de backend gebruiken we PHP Code Sniffer. [Zie hier](https://dev.to/xxzeroxx/phpcs-php-code-sniffer-59f4) de handleiding voor de installatiemogelijkheden. PHP-code sniffer bestaat uit een linter ( `phpcs)` en een formatter(`phpcbf`). De formatter werkt hetzelfde als de linter en kan soms aardig wat errors wegwerken. De regels voor zowel de linter als de formatter zijn te vinden in `phpcs.xml` in de root van de applicatie. ```cli phpcs [filename] +phpcbf [filename] ``` -Hiervoor moet php code sniffer geïnstalleerd zijn. [Zie hier](https://dev.to/xxzeroxx/phpcs-php-code-sniffer-59f4) de handleiding ervoor +## Voor stabiliteit gebruiken we unit tests -Voor beide geldt dat het aantal acceptabele errors 0 is. +Voor beide geldt dat minimale test coverage 80% is, en het aantal acceptabele errors 0. -## Voor stabilliteit gebruiken we unit tests +#### Frontend -Voor frontend is dat: +Voor het uitvoeren van de unit tests gebruiken we aan de frontend Jest. Indien je deze nog moet installeren of meer erover wilt weten, kijk dat [hier](https://www.npmjs.com/package/jest). Het uit te voeren commando is: ```cli npm run test-coverage @@ -117,14 +124,14 @@ npm run test-coverage ![alt text](npm_test.png) -Voor de backend is dat: +#### Backend: + +Voor het uitvoeren van de unit tests gebruiken we aan de backend PHPunit. Indien je deze nog moet installeren of meer erover wilt weten, kijk dan [hier](https://docs.phpunit.de/en/11.3/). Het uit te voeren commando is: ```cli phpunit ``` -Voor beide geldt dat minimale test coverage 80% is, en het aantal acceptabele errors 0. - > **NOTE 1** We volgen de Nextcloud wijze voor unit testing, zie hier voor [de details](https://docs.nextcloud.com/server/latest/developer_manual/server/unit-testing.html), maar dit komt neer op [phpunit](https://docs.phpunit.de/en/11.3/index.html) en de juist configuratie van `phpunit.xml`en de `bootstrap.php`. Een voorbeeld van deze files zijn te vinden in de `root` van de applicatie (`phpunit.xml`) en de `/tests/unit`(`bootstrap.php`). Er zijn veel mogelijkheden om het jezelf makkelijk te maken, zoals een percentageoverzicht in de terminal. Het commando dat wij gebruiken is : `XDEBUG_MODE=coverage phpunit --bootstrap ./tests/bootstrap.php --configuration phpunit.xml --coverage-html ./coverage --coverage-text | tee coverage.txt` @@ -133,7 +140,7 @@ Voor beide geldt dat minimale test coverage 80% is, en het aantal acceptabele er ## Voor veiligheid gebruiken we dependency scanning -Voor frontend is dat: +#### Frontend: ```cli npm audit @@ -141,7 +148,7 @@ npm audit ![alt text](npm_audit.png) -Voor de backend is dat: +#### Backend ```cli composer audit @@ -150,3 +157,44 @@ composer audit ![alt text](composer_audit.png) Voor beide geldt dat het aantal acceptabele critical vulnerabilities in *production packadges* 0 is. + +### Gebruikersdocumentatie + +We gebruiken Gitbook voor de gebruikersdocumentatie. Features binnen de app zouden zo veel mogelijk direct moeten doorverwijzen naar deze documentatie. + +Ook voor de documentatie wordt een linter gebruikt namelijk [remarklint](https://github.com/remarkjs/remark-lint). + +De commando's om deze linter in de CLI te gebruiken zijn [hier te vinden](https://github.com/remarkjs/remark-lint?tab=readme-ov-file#what-is-this) voor een uitgebreide output in de terminal. + +## API Development + +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. + +## Frontend Development + +### Storage en Typing + +Om gegevens deelbaar te maken tussen de verschillende Vue-componenten maken we gebruik van [statemanagement](https://vuejs.org/guide/scaling-up/state-management) waarbij we het Action, State, View patroon van Vue zelf volgen. Omdat de applicatie ingewikkeld begint te worden stappen we daarbij over van [simple state management](https://vuejs.org/guide/scaling-up/state-management#simple-state-management-with-reactivity-api) naar [Pinia](https://pinia.vuejs.org/), de door Vue zelf geadviseerde opvolger van [Vuex](https://vuejs.org/guide/scaling-up/state-management#pinia). + +Daarnaast gebruiken we Typescript voor het definiëren van entities. + +### Modals + +* Er mag altijd slechts één modal actief zijn. +* Modals moeten abstract en overal bereikbaar zijn. +* Modals moeten geplaatst worden in de map src/modals. +* Modals moeten getriggerd worden via de state (zodat knoppen die modal openen overal plaatsbaar zijn). +* Modals moeten geïmporteerd worden via `/src/modals/Modals.vue`. + +### Views + +* Views moeten dezelfde bestandsnaam hebben als de geëxporteerde naam en een correlatie hebben met de map waarin het bestand zich bevindt. +* Bijvoorbeeld, als het bestand een detailpagina is en het zich in de map `publications` bevindt, moet het bestand de naam `PublicationDetail.vue` hebben. + +## Documentatie + +Het is goed om bij development kennnis te nemen/hebben van de volgende gebruikte Nextcloud onderdelen: + +* [Icons](https://pictogrammers.com/library/mdi/) +* [Layout](https://docs.nextcloud.com/server/latest/developer_manual/design/layout.html)- +* [Componenten](https://nextcloud-vue-components.netlify.app/) diff --git a/docs/developers/feature_flow.puml b/docs/developers/feature_flow.puml index 82ef4e57..e900e630 100644 --- a/docs/developers/feature_flow.puml +++ b/docs/developers/feature_flow.puml @@ -23,7 +23,7 @@ alt Feature-aanvraag goedgekeurd end note Ontwikkelingspartij -> Ontwikkelingspartij: Forkt de codebase - Ontwikkelingspartij -> Ontwikkelingspartij: Bouwt de feature + Ontwikkelingspartij -> Ontwikkelingspartij: Bouwt de feature op de fork Ontwikkelingspartij -> Beheerderspartij: Maakt PR met verwijzing naar het issue-nummer note right of Beheerderspartij Code wordt bij voorkeur terug geleverd aan de centrale codebase diff --git a/docs/gebruikers/README.md b/docs/gebruikers/README.md index def29e73..8544890f 100644 --- a/docs/gebruikers/README.md +++ b/docs/gebruikers/README.md @@ -1 +1,13 @@ +*** + +description: >- +Een "Gebruiker" binnen het Publicatieplatform heeft de primaire taak om +publicaties te creëren en te bewerken. De rol van de gebruiker omvat de +volgende verantwoordelijkheden en mogelijkheden: +------------------------------------------------ + # Gebruikers + +* **Creëren van Publicaties:** Gebruikers kunnen nieuwe publicaties aanmaken, afhankelijk van hun autorisaties. Ze kunnen kiezen namens welke organisatie of onderdeel ze publiceren en de juiste informatiecategorieën selecteren. Documenten kunnen worden geüpload en metadata ingevuld. +* **Bewerken van Publicaties:** Gebruikers hebben toegang tot een overzicht van hun eigen (concept-)publicaties. Ze kunnen deze filteren, sorteren en wijzigen. Gebruikers kunnen concept-publicaties definitief maken en gepubliceerde documenten intrekken naar concept-status. +* **Downloaden van Publicaties:** Gebruikers kunnen gepubliceerde documenten en bijbehorende metadata downloaden. diff --git a/docs/gebruikers/dashboard.md b/docs/gebruikers/dashboard.md index 7bbe2df1..f7594bf7 100644 --- a/docs/gebruikers/dashboard.md +++ b/docs/gebruikers/dashboard.md @@ -2,14 +2,18 @@ description: >- Deze handleiding gaat ervan uit dat de gebruiker beschikt tot een werkende -OpenCatalogi-Nextcloud app. ---------------------------- +OpenCatalogi-Nextcloud app +-------------------------- # Dashboard +*** + +## Dashboard + Op het dashboard van OpenCatalogi vindt u handige informatie die je meteen verder helpt en in staat stelt de juiste keuzes te maken. Aan de linkerkant tref je een aantal overzichtsstatistieken en aan de rechterkant een sidebar met daarin de mogelijkheid om direct actie te ondernemen. -## Statistieken +### Statistieken 1. **Zoekverkeer** Het aantal zoekvragen dat er afgelopen maand aan jouw index (geheel van catalogi) is gesteld. 2. **Metadata** De verdeling over metadata-types van jouw publicaties @@ -18,7 +22,7 @@ Op het dashboard van OpenCatalogi vindt u handige informatie die je meteen verde ![app menu](../assets/oc_dashboard.png) -## Sidebar +### Sidebar Vanuit de sidebar heb je toegang tot 3 tabbladen diff --git a/docs/gebruikers/publicaties.md b/docs/gebruikers/publicaties.md index 341ed986..1c621746 100644 --- a/docs/gebruikers/publicaties.md +++ b/docs/gebruikers/publicaties.md @@ -1,6 +1,6 @@ # Publicaties -> Publicaties zijn onderdeel van de (Open Catalogi Standaard)\[] en gebaseerd op het [publication object](https://conduction.stoplight.io/docs/open-catalogi/9bebd6bf4fe35-publication). Publicaties kennen eigenschappen zo als gedefineerd in een publicaite type en kunnen worden gekopeld aan bijlagen +Publicaties zijn onderdeel van de [Open Catalogi Standaard](https://github.com/OpenCatalogi/.github/blob/main/docs/Standaard.md) en gebaseerd op het [publication object](https://conduction.stoplight.io/docs/open-catalogi/9bebd6bf4fe35-publication). Publicaties kennen eigenschappen zoals gedefinieerd in een publicatietype en kunnen worden gekoppeld aan bijlagen Een publicatie representeerd iets wat je wilt publiceren, het beschrijft de handeling van publiceren en de spelregels waaronder iets gepubliceerd wordt.het is een soort "verpakking" of "omhulsel" dat zowel de kerngegevens (data) als aanvullende informatie over die gegevens (metadata) bevat. @@ -14,29 +14,27 @@ Publicaties zijn altijd onderdeel van een collectie in de vorm van een [catalogu Publicaties kunnen worden toegevoegd via: -* De publicatie toeveogen knop boven aan het hoofd menu (links) -* Een catalogus geselecteerd in het hoofd menu (via het hamburger menu achter de zoekbalk) -* Een catalogus detail pagina +* De publicatie toevoegen knop boven aan het hoofd menu (links) +* Een catalogus geselecteerd in het hoofdmenu (via het hamburgermenu achter de zoekbalk) +* Een catalogus detailpagina -Een publicatie leeft altijd binnen één catalogus en word gedefineerd door één publicatie type. Omdat catalogi bepalen welke publicatie typen beschickbaar zijn voor die catalogi moet er eerst een catalogus worden gekozen voordat er een metadata type kan worden gekozen. Daarmee word de volgorde bij het aanmaken van een publicatie: +Een publicatie leeft altijd binnen één catalogus en wordt gedefinieerd door één publicatietype. Omdat catalogi bepalen welke publicatietypen beschikbaar zijn voor die catalogi moet er eerst een catalogus worden gekozen voordat er een metadatatype kan worden gekozen. Daarmee wordt de volgorde bij het aanmaken van een publicatie: 1. Catalogus kiezen (indien niet opgestart vanuit een specifieke catalogus) 2. Publicatietype kiezen -3. Publicatie detalis aanvullen +3. Publicatiedetails aanvullen -Eigenschapen en bijlagen kunnen worden toegevoegd nadat de publicatie is toegevoegd. +Eigenschappen en bijlagen kunnen worden toegevoegd nadat de publicatie is toegevoegd. ## Publicaties beheren -De gebruikersbeheerinterface werkt intuïtief. Aan de linkerkant van de pagina bevindt zich een overzicht van catalogi. Met de blauwe knop bovenaan kun je een publicatie aanmaken. Dit opent een modal genaamd "Publicatie toevoegen". +De gebruikersbeheerinterface werkt intuïtief. Aan de linkerkant van de pagina bevindt zich een overzicht van catalogi. Met de blauwe knop bovenaan kun je een publicatie aanmaken. Dit opent een modal genaamd "Publicatie toevoegen". Er wordt eerst gevraagd aan welke catalogus deze behoort en welke publicatietype het heeft (metadata) Hieronder is een voorbeeld van een ingevulde modal voor het aanmaken van een Woo-publicatie. -
+
-

Het eerste gedeelte - gegevens over de publicatie

- -

het tweede gedeelte - het aanwijzen van de catalogi

+

De publicatiemodal

@@ -64,4 +62,20 @@ Naast een bestand kan een bijlage (per verwijzing) bijvoorbeeld ook een website Een tweede manier om informatie op te nemen in een publicaite is via eigenschappen. Eigenschappen zijn voor gedefineerde opties (via [publicatie type](../beheerders/metadata.md)) waar een waarde aan kan worden toegekend. +## Bijlagen + ![alt text](image-2.png) + +Publicaties hebben vaak bijlagen, zoals een verslag of een besluit. Deze zijn eenvoudig toe te voegen door op de Actie-knop te klikken bij een geselecteerde publicatie, of de drie bolletjes naast een publicatie. Dit opent de Bijlage toevoegen modal. + +
+ +

bijlage toevoegen via drie bolletjes

+ +

bijlage toevoegen via de actie-knop

+ +
+ +In de `Bijlage toevoegen`-modal worden er gevraagd om een aantal velden. Er zijn twee mogelijkheden een bijlage toe te voegen. De eerste manier is via een `Toegangs URL`. Dit zorgt ervoor dat het bestand vanuit een andere plek automatisch gedownload wordt. Een `Titel` is dan verplicht. + +De tweede manier is door zelf een bestand up te loaden. De bestandsnaam wordt dan meegegeven. diff --git a/docs/gebruikers/tutorial.md b/docs/gebruikers/tutorial.md index 8a90e4f8..35708faf 100644 --- a/docs/gebruikers/tutorial.md +++ b/docs/gebruikers/tutorial.md @@ -1,14 +1,14 @@ # Tutorial 1. Zorg dat je Nextcloud hebt geïnstalleerd [link naar installatiehandleiding](https://cloud.nextcloud.com/s/iyNGp8ryWxc7Efa?path=%2F1%20Setting%20up%20a%20development%20environment). -2. De OpenCatalogi code is de `apps-extra`-folder hebt. [link naar de app toevoegen](../developers/installatie-van-nextcloud-development-omgeving.md) +2. De OpenCatalogi code is de `apps-extra`-folder hebt. [link naar de app ](../developers/installatie-van-nextcloud-development-omgeving.md)code 3. Zorg dat je de app geactiveerd hebt [link naar configuratie](../developers/de-opencatalogi-app-toevoegen-aan-nextcloud.md). -Nadat de app succesvol is geconfigureerd, vindt u deze terug in het app-menu van Nextcloud. Klik op het 'open catalogi'-icoon om de app te openen. De tutorial gaat uit van een lege installatie. +Nadat de app succesvol is geconfigureerd, vind je deze terug in het app-menu van Nextcloud. Klik op het 'OpenCatalogi'-icoon om de app te openen. De tutorial gaat uit van een lege installatie. -![app menu](<../assets/nc_app_menu (1).png>) +![OpenCatalogi-logo in het app menu](<../assets/nc_app_menu (1).png>) -## Directory (moet) +## Directory Een OpenCatalogi-installatie is bedoeld om onderdeel te zijn van een federatief netwerk van catalogi. Deze kunt u terugvinden onder Instellingen -> Directory. In de directory staan alle bij deze installatie bekende catalogi die zich in andere installaties bevinden. OpenCatalogi-installaties wisselen onderling hun directory uit, dus om onderdeel te worden van het federatieve netwerk moet er minimaal één andere catalogus bekend zijn. @@ -22,40 +22,86 @@ Catalogi worden gedefinieerd aan de hand van de plek waar ze leven op het intern ![directory toevoegen formulier](../assets/oc_directory_toevoegen_form.png) -## Zoeken (moet) +## Zoeken Nadat je de app geïnstalleerd hebt, kun je het zoekscherm gebruiken om te zoeken naar gegevens bij andere organisaties. Ga hiervoor naar het menu-item 'Zoeken'. -Aan de linkerkant worden gevonden publicaties weergegeven, aan de rechterkant treft u in tabbladen zoekmiddelen met daarin: +Aan de linkerkant worden de gevonden publicaties weergegeven, terwijl aan de rechterkant in tabbladen de zoekmiddelen beschikbaar zijn: * **Zoeken**: Het algemene zoekveld en de zoekknop. -* **Catalogi**: Hierin kunt u aangeven in welke voor uw organisatie actieve catalogi u wilt zoeken. -* **Metadata**: Hierin kunt u aangeven naar welke voor uw organisatie actieve metadatatypes u wilt zoeken. +* **Catalogi**: Hierin kun je aangeven in welke actieve catalogi je wilt zoeken die relevant zijn voor jouw organisatie. +* **Metadata**: Hierin kun je aangeven naar welke actieve metadatatypes je wilt zoeken die relevant zijn voor jouw organisatie. -### Catalogus (moet) +### Catalogus Publicaties worden in OpenCatalogi onderverdeeld in catalogi. -Stukje tekst met screenshots over het aanmaken van catalogi. +Het aanmaken van een eigen catalogus kan eenvoudig door te navigeren naar `Instellingen` en te kiezen voor Catalogi. Aan de linkerzijde zie je de knop `Catalogi toevoegen`. Voor het aanmaken van een Catalogus is alleen een `titel` verplicht. -Het aanmaken van een catalogus wordt gedaan in de `Instellingen`. +
-* Maak een catalogus aan. +

Het overzicht voor Instellingen en de aanmaken-knop

-## Metadata (moet) +

De modal voor metadata, zoals titel en samenvatting

-Stukje tekst met screenshots over het aanmaken van metadata. +
-* Maak een metadatabeschrijving aan. -* Voeg aan de metadatabeschrijving een aantal eigenschappen toe. +## Metadatabeschrijving -## Publicatie (moet) +Metadata beschrijft een publicatie. Er wordt er informatie meegegeven over het publicatietype. -Stukje tekst met screenshots over het aanmaken van publicaties. -Stukje tekst met screenshots over het toevoegen van gegevens aan publicaties. -Stukje tekst met screenshots over het toevoegen van bestanden aan publicaties. +Het aanmaken van een eigen catalogus kan eenvoudig door te navigeren naar `Instellingen` en te kiezen voor `Metadata`. Aan de linkerzijde zie je de knop `Metadatabeschrijving toevoegen`. Voor het aanmaken van `Metadata` is alleen een `titel` verplicht. -## Delen (moet) +
-Stukje tekst met screenshots over het publiceren van publicaties. -Stukje tekst over het terugtrekken van een publicatie. +

Selecteren van Metadata onder "Instellingen"

+ +

Modal voor het toevoegen van Metadatabeschrijvingen

+ +
+ +## Publicatie + +Aan de linkerkant bevindt zich een navigatiebalk met verschillende menuopties. Bovenaan de navigatiebalk is een blauwe knop zichtbaar met de tekst `Publicatie Aanmaken`. + +In het hoofdgedeelte van het scherm, aan de rechterkant, is er een melding die aangeeft dat er nog geen publicatie is geselecteerd, met de tekst `Geen publicatie`. Onder deze melding staat een blauwe knop met de tekst `Publicatie toevoegen`. + +Bovenaan het hoofdgedeelte is er een zoekbalk met een vergrootglas-icoon en een tekstveld om zoekopdrachten in te voeren. Rechts naast de zoekbalk is er een icoon met drie verticale puntjes. + +Het scherm is gebruiksvriendelijk ontworpen en biedt eenvoudige navigatie en duidelijke opties voor het aanmaken en toevoegen van publicaties.\ +\ +Bij het aanmaken van een publicatie zijn een aantal zaken van belang. Een publicatie behoort altijd tot een catalogus, dus die moet geselecteerd worden, net als metadata. + +
+ +

De drie manieren met een hoek

+ +

Modal voor het oevoegen van publicaties

+ +
+ +## Bijlagen toevoegen + +Sommige publicaites hebben een bijlagen, zoals bijvoorbeeld voor convenanten het geval is. Een bijlage toevoegen aan een publicatie wordt gedaan door te klikken op de `Actie`-knop die rechtsboven te vinden is bij het klikken op een publicatie. Een van die opties is `Bijlage toevoegen`. Dit zorgt voor het tonen van de `Bijlage toevoegen`-modal. Hier kan via `url` of door een bestand toe te voegen vanaf de harde schrijf. + +
+ +

Overzicht actie-knop publicaties

+ +

Modal voor het toevoegen van bijlagen

+ +
+ +## Delen + +Stukje tekst met screenshots over het publiceren van publicaties. Stukje tekst over het terugtrekken van een publicatie.\ +\ +Na het aanmaken van een publicatie is een publicatie makkelijk te delen (publiceren) door deze openbaar te maken. Dit kan door naar de drie bolletjes te gaan naast de publicatie en te kiezen voor `publiceren`. \ +\ +Op dezelfde plek is de optie `depubliceren` te vinden. Dit is gebeurd in het onderstaande screenshot, waar ook de status `retracted` te zien is. + +
+ +

Overzicht actiemenu voor het (de)publiceren

+ +
diff --git a/docs/installatie/instructies.md b/docs/installatie/instructies.md index 9ebca127..b8e32380 100644 --- a/docs/installatie/instructies.md +++ b/docs/installatie/instructies.md @@ -10,20 +10,22 @@ Volg deze stappen om Nextcloud eenvoudig te installeren en te gebruiken: * We hebben gekozen voor TheGoodCloud, zonder enige voorkeur. * Je ontvangt een activatiemail, dit kan tot 6 uur duren. Zoals altijd, check ook de spambox. * Eenmaal ingelogd ziet het scherm er zo uit:\ - \ - ![](<../.gitbook/assets/image (4) (1).png>) \\ + \\ + +

the good cloud welkom

2. **Nextcloud-app installeren via de appstore**: Let op! Je hebt hier een admin-account voor nodig. Dit werkt mogelijk niet met trail-versies. * Navigeer naar jouw profiel log, rechts in de Nextcloud app. Klik erop en kies "Apps" . -\ -![](<../.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1).png>) +\\ + +

App-menu

3. **OpenCatalogi installeren**: -* Nadat je bent ingelogd in de Nextcloud-app, ga naar de app store binnen de Nextcloud-omgeving. +* Nadat je bent ingelogd in de Nextcloud-app, ga naar de appstore binnen de Nextcloud-omgeving. * Zoek naar "OpenCatalogi" en installeer de app. * Volg de instructies om OpenCatalogi te configureren en te gebruiken. diff --git a/docs/installatie/logging.md b/docs/installatie/logging.md index 526a66c6..1e08c3e7 100644 --- a/docs/installatie/logging.md +++ b/docs/installatie/logging.md @@ -1,28 +1,28 @@ -# Logging +# Audit en logging -Voor logging maken we gebruik van de ingeboude admin\_audit systemetiek van next cloud, meer daarover kan je [hier](https://docs.nextcloud.com/server/29/admin_manual/configuration_server/logging_configuration.html#admin-audit-log) vinden. +Voor logging maken we gebruik van de ingebouwde admin\_audit systematiek van next cloud, meer daarover kan je [hier](https://docs.nextcloud.com/server/29/admin_manual/configuration_server/logging_configuration.html#admin-audit-log) vinden. ## System logging -Als audit trails aanstaan worden automatisch alle systeem fouten gelogd, die kunnen vervolgens worden ingezien met [log reader](https://github.com/nextcloud/logreader) (admin->logging) +Als audit trails aanstaan worden automatisch alle systeemfouten gelogd, die kunnen vervolgens worden ingezien met [log reader](https://github.com/nextcloud/logreader) (admin->logging) ![alt text](image.png) -## Change loging +## Change logging -Wijziging's (pogingen) worden gelogd via api calls. Dit vanwege twee reden: +Wijzigings- (pogingen) worden gelogd via API calls. Dit vanwege twee reden: 1. Zo loggen we alle pogingen, ongeacht of ze via de functioneel beheer omgeving of een specifieke afhandel applicatie zijn gemaakt 2. We loggen pogingen, dus ook mislukte wijzigingen (bijvoorbeeld vanwege foutieve invoer of rechten) worden bijgehouden -Deze logs zijn generiek in te zien via [log reader](https://github.com/nextcloud/logreader) of speciefiek via de functioneel beheer interface. +Deze logs zijn generiek in te zien via [log reader](https://github.com/nextcloud/logreader) of specifiek via de functioneel beheer interface. ## Security logging -Foutieve inlogpogingen, overmatige bevragingen, ongeldige invoer etc worden allemaal weggeschreven naar de logs zijn daarin dus terug te vinden via log reader of te exporteren naar een dashboard dat meerdere installaties volgt. +Foutieve inlogpogingen, overmatige bevragingen, ongeldige invoer etc. worden allemaal weggeschreven naar de logs zijn daarin dus terug te vinden via log reader of te exporteren naar een dashboard dat meerdere installaties volgt. ## Via Loki, Prometheus en Grafana -We raden sterk aan om op saas omgevingen gebruik te maken van dashboard om (verdacht) gedrag van gebruiker te volgen naar de algemene gezondheid van de installatie. Dit is zeker raadsaam binnen [saas omgevingen](saas.md) waarbij er doorgaan gebruik wordt gemaakt van één installatie per tenant (klant). Overzicht houden wordt dan snel moelijk tot onmogenlijk en op performance en security wil je pro actief acteren. +We raden sterk aan om op SaaS-omgevingen gebruik te maken van dashboard om (verdacht) gedrag van gebruiker te volgen naar de algemene gezondheid van de installatie. Dit is zeker raadzaam binnen S[aaS-omgevingen](saas.md) waarbij er doorgaan gebruik wordt gemaakt van één installatie per tenant (klant). Overzicht houden wordt dan snel moeilijk tot onmogelijk en op performance en security wil je proactief acteren. -Vanuit de nextcloud community is er een mooie [tutorial](https://okxo.de/monitor-your-nextcloud-logs-for-suspicious-activities/) beschickbaar over hoe je de nextcloud audit trails kan overbrengen naar je grafana dashboard zodat je zicht hebt op (bijvoorbeeld) mislukte inlog pogingen. +Vanuit de Nextcloud-community is er een mooie [tutorial](https://okxo.de/monitor-your-nextcloud-logs-for-suspicious-activities/) beschikbaar over hoe je de Nextcloud-audit trails kan overbrengen naar je Grafana-dashboard zodat je zicht hebt op (bijvoorbeeld) mislukte inlog pogingen. diff --git a/docs/installatie/on-prem-server.md b/docs/installatie/on-prem-server.md index f00533c0..bd2bf3cc 100644 --- a/docs/installatie/on-prem-server.md +++ b/docs/installatie/on-prem-server.md @@ -7,12 +7,16 @@ is zeker niet nodig voor het gebruik van de OpenCatalogi Nextcloud-app # On-Prem server +*** + +## On-Prem server + Hoewel niet elke (semi-)overheidsinstelling een eigen Nextcloud-server nodig heeft, is het voor sommigen een aantrekkelijke optie vanwege specifieke voordelen. Deze instellingen kunnen onder andere zijn: 1. **Overheidsinstanties**: Organisaties die strikte regelgeving omtrent gegevensbeveiliging en -opslag moeten naleven. 2. **Organisaties die verdere configuratie vereisen**: Bedrijven en instellingen die specifieke configuraties en integraties nodig hebben om aan hun unieke operationele eisen te voldoen. -#### Voordelen van een eigen Nextcloud-server +**Voordelen van een eigen Nextcloud-server** 1. **Gegevenscontrole en -beveiliging**: Volledige controle over waar gegevens worden opgeslagen en wie er toegang toe heeft, wat essentieel is voor het beschermen van gevoelige informatie. 2. **Aanpasbaarheid**: Mogelijkheid om de Nextcloud-omgeving aan te passen aan specifieke behoeften en te integreren met andere systemen die de organisatie gebruikt. @@ -20,7 +24,7 @@ Hoewel niet elke (semi-)overheidsinstelling een eigen Nextcloud-server nodig hee 4. **Offline toegang**: Toegang tot gegevens en applicaties, zelfs zonder internetverbinding, wat belangrijk kan zijn in noodgevallen of afgelegen locaties. 5. **Compliantie**: Voldoen aan wettelijke en sectorale eisen voor gegevensopslag en -beveiliging, zoals de AVG (GDPR) in Europa. -#### Systeemeisen voor een Nextcloud-server +**Systeemeisen voor een Nextcloud-server** De systeemeisen voor het draaien van een eigen Nextcloud-server kunnen variëren afhankelijk van de schaal en het gebruik. Enkele basisvereisten zijn: diff --git a/docs/installatie/saas.md b/docs/installatie/saas.md index bf7473c5..856e6273 100644 --- a/docs/installatie/saas.md +++ b/docs/installatie/saas.md @@ -1,10 +1,10 @@ # SAAS en Dashboarding -De open catalogi nextcloud app is ontworpen om als SAAS dienst te worden aangeboden aan overheden, +De OpenCatalogi-Nextcloud app is ontworpen om als SaaS-dienst te worden aangeboden aan overheden, -## Multy tenancy by installion +## Multi-tenancy by installion -Meerdere tenants in apparte namespaces +Meerdere tenants in aparte namespaces # Dashboarding diff --git a/docs/installatie/systeemeisen-voor-nextcloud.md b/docs/installatie/systeemeisen-voor-nextcloud.md index 00bd5ce8..a311ac40 100644 --- a/docs/installatie/systeemeisen-voor-nextcloud.md +++ b/docs/installatie/systeemeisen-voor-nextcloud.md @@ -8,6 +8,10 @@ functionaliteit. # Systeemeisen voor Nextcloud +*** + +## Systeemeisen voor Nextcloud + **Desktop Client** * **Besturingssysteem:** @@ -46,6 +50,6 @@ functionaliteit. * **Netwerk:** Actieve internetverbinding om toegang te krijgen tot Nextcloud server en synchronisatie uit te voeren. * **Veiligheid:** Gebruik van HTTPS wordt aanbevolen voor veilige gegevensoverdracht. -#### Opmerkingen +**Opmerkingen** Het wordt aanbevolen om regelmatig de laatste updates van het besturingssysteem en de Nextcloud-app te installeren voor optimale prestaties en veiligheid. diff --git a/docs/veel-gestelde-vragen.md b/docs/veel-gestelde-vragen.md new file mode 100644 index 00000000..92a26148 --- /dev/null +++ b/docs/veel-gestelde-vragen.md @@ -0,0 +1 @@ +# Veel gestelde vragen diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index c7776c8c..b845a2b7 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -164,7 +164,7 @@ private function checkUploadedFile(): JSONResponse|array return new JSONResponse(data: ['error' => 'Please upload a file using key "_file" or give a "downloadUrl"'], statusCode: 400); } - // Check for upload errors + // Check for upload errors. if ($uploadedFile['error'] !== UPLOAD_ERR_OK) { return new JSONResponse(data: ['error' => 'File upload error: '.$uploadedFile['error']], statusCode: 400); } @@ -209,14 +209,17 @@ private function checkRequestBody(array $data): JSONResponse|array */ private function handleFile(array $uploadedFile): JSONResponse|string { - // Create the Attachments folder and the Publication specific folder. - $this->fileService->createFolder(folderPath: 'Attachments'); - $publicationFolder = '(' . $this->request->getHeader('Publication-Id') . ') ' - . $this->request->getHeader('Publication-Title'); - $this->fileService->createFolder(folderPath: "Attachments/$publicationFolder"); - - // Save the uploaded file - $filePath = "Attachments/$publicationFolder/" . $uploadedFile['name']; // Add a file version to the file name? + // Create the Publicaties folder, the Publication specific folder and the Bijlagen folder in that. + $this->fileService->createFolder(folderPath: 'Publicaties'); + $publicationFolder = $this->fileService->getPublicationFolderName( + publicationId: $this->request->getHeader('Publication-Id'), + publicationTitle: $this->request->getHeader('Publication-Title') + ); + $this->fileService->createFolder(folderPath: "Publicaties/$publicationFolder"); + $this->fileService->createFolder(folderPath: "Publicaties/$publicationFolder/Bijlagen"); + + // Save the uploaded file. + $filePath = "Publicaties/$publicationFolder/Bijlagen/" . $uploadedFile['name']; // Add a file version to the file name? $created = $this->fileService->uploadFile( content: file_get_contents(filename: $uploadedFile['tmp_name']), filePath: $filePath @@ -231,7 +234,7 @@ private function handleFile(array $uploadedFile): JSONResponse|string /** - * Adds information about the uploaded file to the appropriate Attachment fields. + * Adds information about the uploaded file to the appropriate Attachment fields. Inclusive share link. * * @param array $data The form-data fields and their values (/request body) that we are going to update before posting the Attachment. * @param array $uploadedFile Information about the uploaded file from the request body. @@ -242,7 +245,7 @@ private function handleFile(array $uploadedFile): JSONResponse|string */ private function AddFileInfoToData(array $data, array $uploadedFile, string $filePath): array { - // Update Attachment data + // Update Attachment data. $currentUser = $this->userSession->getUser(); $userId = $currentUser ? $currentUser->getUID() : 'Guest'; $data['reference'] = "$userId/$filePath"; @@ -252,7 +255,7 @@ private function AddFileInfoToData(array $data, array $uploadedFile, string $fil $data['title'] = $explodedName[0]; $data['extension'] = end(array: $explodedName); - // Create ShareLink + // Create ShareLink. $shareLink = $this->fileService->createShareLink(path: $filePath); if (empty($data['accessUrl']) === true) { $data['accessUrl'] = $shareLink; @@ -274,9 +277,9 @@ private function AddFileInfoToData(array $data, array $uploadedFile, string $fil public function create(ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { $data = $this->request->getParams(); - // Uploaded _file and downloadURL are mutually exclusive + // Uploaded _file and downloadURL are mutually exclusive. if (empty($data['downloadUrl']) === true) { - // Check if a file was uploaded + // Check if a file was uploaded. $uploadedFile = $this->checkUploadedFile(); if ($uploadedFile instanceof JSONResponse) { return $uploadedFile; @@ -290,17 +293,17 @@ public function create(ObjectService $objectService, ElasticSearchService $elast } if (empty($uploadedFile) === false) { - // Handle saving the uploaded file in NextCloud + // Handle saving the uploaded file in NextCloud. $filePath = $this->handleFile(uploadedFile: $uploadedFile); if ($filePath instanceof JSONResponse) { return $filePath; } - // Update Attachment data + // Update Attachment data, inclusive share link. $data = $this->AddFileInfoToData(data: $data, uploadedFile: $uploadedFile, filePath: $filePath); } - // Remove fields we should never post + // Remove fields we should never post. unset($data['id']); foreach($data as $key => $value) { if(str_starts_with(haystack: $key, needle: '_')) { @@ -325,7 +328,6 @@ public function create(ObjectService $objectService, ElasticSearchService $elast config: $dbConfig ); - // get post from requests return new JSONResponse($returnData); } @@ -338,7 +340,7 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear { $data = $this->request->getParams(); - // Remove fields we should never post + // Remove fields we should never post. unset($data['id']); foreach($data as $key => $value) { if(str_starts_with(haystack: $key, needle: '_')) { @@ -365,7 +367,6 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear config: $dbConfig ); - // get post from requests return new JSONResponse($returnData); } @@ -385,8 +386,10 @@ public function destroy(string|int $id, ObjectService $objectService, ElasticSea } // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) -// $publicationFolder = '(' . $this->request->getHeader('Publication-Id') . ') ' -// . $this->request->getHeader('Publication-Title'); +// $publicationFolder = $this->fileService->getPublicationFolderName( +// publicationId: $this->request->getHeader('Publication-Id'), +// publicationTitle: $this->request->getHeader('Publication-Title') +// ); // $this->fileService->deleteFile(filePath: "Attachments/$publicationFolder" . $attachment['title'] . '.' . $attachment['extension']); $filePath = explode(separator: '/', string: $attachment['reference']); array_shift(array: $filePath); @@ -411,7 +414,6 @@ public function destroy(string|int $id, ObjectService $objectService, ElasticSea config: $dbConfig ); - // get post from requests return new JSONResponse($returnData); } } diff --git a/lib/Controller/CatalogiController.php b/lib/Controller/CatalogiController.php index 83f26a89..35034282 100644 --- a/lib/Controller/CatalogiController.php +++ b/lib/Controller/CatalogiController.php @@ -122,6 +122,11 @@ public function create(ObjectService $objectService, DirectoryService $directory } } + // @todo dit is alleen omdat er een onredenlijke verplichting aan de database is toegeveoegd + if(array_key_exists('metadata',$data) === false){ + $data['metadata'] = []; + } + if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' ) { diff --git a/lib/Controller/PublicationsController.php b/lib/Controller/PublicationsController.php index 3111b16e..205dec59 100644 --- a/lib/Controller/PublicationsController.php +++ b/lib/Controller/PublicationsController.php @@ -2,11 +2,15 @@ namespace OCA\OpenCatalogi\Controller; +use Exception; use GuzzleHttp\Exception\GuzzleException; +use Mpdf\MpdfException; +use Mpdf\Output\Destination; use OCA\OpenCatalogi\Db\AttachmentMapper; use OCA\opencatalogi\lib\Db\Publication; use OCA\OpenCatalogi\Db\PublicationMapper; use OCA\OpenCatalogi\Service\ElasticSearchService; +use OCA\OpenCatalogi\Service\FileService; use OCA\OpenCatalogi\Service\ObjectService; use OCA\OpenCatalogi\Service\SearchService; use OCP\AppFramework\Controller; @@ -16,6 +20,9 @@ use OCP\IAppConfig; use OCP\IRequest; use Symfony\Component\Uid\Uuid; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; class PublicationsController extends Controller { @@ -26,7 +33,8 @@ public function __construct IRequest $request, private readonly PublicationMapper $publicationMapper, private readonly AttachmentMapper $attachmentMapper, - private readonly IAppConfig $config + private readonly IAppConfig $config, + private readonly FileService $fileService ) { parent::__construct($appName, $request); @@ -139,13 +147,13 @@ public function index(ObjectService $objectService, SearchService $searchService * @NoAdminRequired * @NoCSRFRequired */ - public function attachments(string|int $id, ObjectService $objectService): JSONResponse + public function attachments(string|int $id, ObjectService $objectService, ?array $publication = null): JSONResponse { - $publication = $this->show($id, $objectService)->getData(); - if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false - || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' - ) { - $publication = $publication->jsonSerialize(); + if ($publication === null) { + $publication = $this->getPublicationData(id: $id, objectService: $objectService); + if ($publication instanceof JSONResponse) { + return $publication; + } } $attachments = $publication['attachments']; @@ -170,6 +178,33 @@ public function attachments(string|int $id, ObjectService $objectService): JSONR return new JSONResponse(['results' => $result['documents']]); } + + /** + * Gets a publication for the given id (if it exists) and returns its data as an array. + * + * @param string|int $id The id of a Publication we want to get a data array for. + * @param ObjectService $objectService The ObjectService, used to connect to a MongoDB database. + * + * @return array|JSONResponse An array containing all data of the publication or an error JSONResponse. + */ + private function getPublicationData(string|int $id, ObjectService $objectService): array|JSONResponse + { + $jsonResponse = $this->show($id, $objectService); + $publication = $jsonResponse->getData(); + if (is_array($publication) === true && isset($publication['error']) === true) { + return new JSONResponse(data: $publication, statusCode: $jsonResponse->getStatus()); + } + + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' + ) { + $publication = $publication->jsonSerialize(); + } + + return $publication; + } + + /** * @NoAdminRequired * @NoCSRFRequired @@ -198,6 +233,227 @@ public function show(string|int $id, ObjectService $objectService): JSONResponse } + /** + * Create/updates a file containing all metadata of a publication to NextCloud files, finds/creates a share link and returns it. + * + * @param string $filename The (tmp) filename of the file to store in NextCloud files. + * @param array $publication The publication data used to find/create the publication specific folder in NextCloud files. + * + * @return string|JSONResponse A share link url or an error JSONResponse. + * @throws Exception When a function reading or writing to NextCloud files goes wrong. + */ + private function saveFileToNextCloud(string $filename, array $publication): string|JSONResponse + { + // Create the Publicaties folder and the Publication specific folder. + $this->fileService->createFolder(folderPath: 'Publicaties'); + $publicationFolder = $this->fileService->getPublicationFolderName( + publicationId: $publication['id'], + publicationTitle: $publication['title'] + ); + $this->fileService->createFolder(folderPath: "Publicaties/$publicationFolder"); + + // Save the file to NextCloud. + $filePath = "Publicaties/$publicationFolder/$filename"; + $created = $this->fileService->updateFile( + content: file_get_contents(filename: $filename), + filePath: $filePath, + createNew: true + ); + + if ($created === false) { + return new JSONResponse(data: ['error' => "Failed to upload this file: $filePath to NextCloud"], statusCode: 500); + } + + // Create ShareLink + $share = $this->fileService->findShare(path: $filePath); + if ($share !== null) { + $shareLink = $this->fileService->getShareLink($share); + } else { + $shareLink = $this->fileService->createShareLink(path: $filePath); + } + + return $shareLink; + } + + + /** + * Creates a pdf file containing all metadata of the given publication. + * + * @param ObjectService $objectService The ObjectService, used to connect to a MongoDB database. + * @param string|int $id The id of a Publication we want to create / update a pdf file for. + * @param array|null $options A few options for this function, "download" & "saveToNextCloud" can't be both false! + * "download" = If we should return a download response (true = default). + * "saveToNextCloud" = If we should create and save the file in NextCloud (true = default). + * "publication" = If we already have a publication body prevent extra database requests by passing it along. + * + * @return JSONResponse A JSONResponse for downloading the pdf file. Or a JSONResponse containing a downloadUrl for a nextCloud file. Or an error response. + * @throws LoaderError|RuntimeError|SyntaxError|MpdfException|Exception + */ + private function createPublicationFile( + ObjectService $objectService, string|int $id, + ?array $options = [ + 'download' => true, + 'saveToNextCloud' => true, + 'publication' => null + ] + ): JSONResponse + { + if ($options['download'] === false && $options['saveToNextCloud'] === false) { + return new JSONResponse(data: ['error' => '$options "download" & "saveToNextCloud" for function + createPublicationFile should not be both set to false'], statusCode: 500); + } + + $publication = $options['publication'] ?? null; + if ($publication === null) { + $publication = $this->getPublicationData(id: $id, objectService: $objectService); + if ($publication instanceof JSONResponse) { + return $publication; + } + } + + // Create the PDF file using a twig template and publication data. + $mpdf = $this->fileService->createPdf(twigTemplate: 'publication.html.twig', context: ['publication' => $publication]); + + // The filename. + $filename = "{$publication['title']}.pdf"; + + if (isset($options['saveToNextCloud']) === false || $options['saveToNextCloud'] === true) { + // Output to a file. + $mpdf->Output(name: $filename, dest: Destination::FILE); + + // Save the file in NextCloud. + $shareLink = $this->saveFileToNextCloud(filename: $filename, publication: $publication); + if ($shareLink instanceof JSONResponse) { + return $shareLink; + } + } + + if (isset($options['download']) === false || $options['download'] === true) { + // Output directly to the browser. + $mpdf->Output(name: $filename, dest: Destination::DOWNLOAD); + } + + // Remove tmp folder after mpdf->Output & $this->saveFileToNextCloud have been called. + rmdir(directory: '/tmp/mpdf'); + + if (isset($options['saveToNextCloud']) === false || $options['saveToNextCloud'] === true) { + return new JSONResponse(data: [ + 'downloadUrl' => "$shareLink/download", + 'filename' => $filename + ], statusCode: 200 + ); + } + return new JSONResponse([], 200); + } + + + /** + * Prepares the creation of a ZIP archive for a publication, by adding all folders & files we want in this zip + * to a $tempFolder that will be used as input for creating the actual ZIP archive later. + * + * @param string $tempFolder The tmp location used as input for creating the ZIP archive. + * @param array $attachments An array containing all Attachments (Bijlagen) for the Publication. + * @param array $publicationFile An array containing the downloadUrl and filename of the pdf file created that contains all metadata of the Publication. + * + * @return void + */ + private function prepareZip(string $tempFolder, array $attachments, array $publicationFile): void + { + // Create temporary directory + if (file_exists(filename: $tempFolder) === false) { + mkdir(directory: $tempFolder, recursive: true); + if (count($attachments['results']) > 0) { + mkdir(directory: "$tempFolder/Bijlagen", recursive: true); + } + } + + // Add .pdf file containing publication metadata. + $file_content = file_get_contents(filename: $publicationFile['downloadUrl']); + if ($file_content !== false) { + file_put_contents(filename: "$tempFolder/{$publicationFile['filename']}", data: $file_content); + } + + // Add all attachments in Bijlagen folder. + foreach ($attachments['results'] as $attachment) { + $attachment = $attachment->jsonSerialize(); + $file_content = file_get_contents(filename: $attachment['downloadUrl']); + if ($file_content !== false) { + $filePath = explode(separator: '/', string: $attachment['reference']); + file_put_contents(filename: "$tempFolder/Bijlagen/".end(array: $filePath), data: $file_content); + } + } + } + + + /** + * Creates a ZIP archive containing a pdf file with all metadata of the publication for id = $id. + * Will also add all Attachments (Bijlagen) of this publication to this ZIP archive in a folder called 'Bijlagen'. + * + * @param ObjectService $objectService The ObjectService, used to connect to a MongoDB database. + * @param string|int $id The id of a Publication we want to download a ZIP archive for. + * + * @return JSONResponse A JSONResponse for downloading the ZIP archive. Or an error response. + * @throws LoaderError|MpdfException|RuntimeError|SyntaxError + */ + private function createPublicationZip(ObjectService $objectService, string|int $id): JSONResponse + { + // Get the publication. + $publication = $this->getPublicationData(id: $id, objectService: $objectService); + if ($publication instanceof JSONResponse) { + return $publication; + } + + // Update the publication .pdf file containing publication metadata. + $jsonResponse = $this->createPublicationFile(objectService: $objectService, id: $id, + options: ['download' => false, 'publication' => $publication]); + if ($jsonResponse->getStatus() !== 200) { + return $jsonResponse; + } + $publicationFile = $jsonResponse->getData(); + + // Get all publication attachments. + $attachments = $this->attachments(id: $id, objectService: $objectService, publication: $publication)->getData(); + if (isset($attachments['results']) === false) { + return new JSONResponse(data: ['error' => "failed to get attachments for this publication: $id"], statusCode: 500); + } + + // Temporary paths. + $tempFolder = '/tmp/nextcloud_download_' . $publication['title']; + $tempZip = '/tmp/publicatie_' . $publication['title'] . '.zip'; + + // Prepare ZIP by creating a temp folder with everything we want in the ZIP archive. + $this->prepareZip(tempFolder: $tempFolder, attachments: $attachments, publicationFile: $publicationFile); + + // Create the ZIP archive. + $error = $this->fileService->createZip(inputFolder: $tempFolder, tempZip: $tempZip); + if ($error !== null) { + return new JSONResponse(data: ['error' => "failed to create ZIP archive for this publication: $id"], statusCode: 500); + } + + // Return a download response containing the ZIP archive. And clean up temp files/folders. + $this->fileService->downloadZip(tempZip: $tempZip, inputFolder: $tempFolder); + + return new JSONResponse([], 200); + } + + + /** + * @NoAdminRequired + * @NoCSRFRequired + */ + public function download(string|int $id, ObjectService $objectService): JSONResponse + { + return match ($this->request->getHeader('Accept')) { + 'application/pdf' => $this->createPublicationFile(objectService: $objectService, id: $id), + 'application/zip' => $this->createPublicationZip(objectService: $objectService, id: $id), + default => new JSONResponse( + data: ['error' => 'Unsupported Accept header, please use [application/pdf] or [application/zip]'], + statusCode: 400 + ), + }; + } + + /** * @NoAdminRequired * @NoCSRFRequired @@ -249,6 +505,7 @@ public function create(ObjectService $objectService, ElasticSearchService $elast $returnData = $elasticSearchService->addObject(object: $returnData, config: $elasticConfig); } + // get post from requests return new JSONResponse($returnData); } diff --git a/lib/Db/Attachment.php b/lib/Db/Attachment.php index ed1c2847..c23e40fe 100644 --- a/lib/Db/Attachment.php +++ b/lib/Db/Attachment.php @@ -71,7 +71,6 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); } } @@ -96,8 +95,8 @@ public function jsonSerialize(): array 'hash' => $this->hash, 'anonymization' => $this->anonymization, 'language' => $this->language, - 'modified' => $this->modified->format('c'), - 'published' => $this->published->format('c'), + 'modified' => $this->modified?->format('c'), + 'published' => $this->published?->format('c'), 'license' => $this->license, ]; diff --git a/lib/Db/AttachmentMapper.php b/lib/Db/AttachmentMapper.php index 9c32b8b6..5575104b 100644 --- a/lib/Db/AttachmentMapper.php +++ b/lib/Db/AttachmentMapper.php @@ -55,9 +55,6 @@ public function createFromArray(array $object): Attachment { $attachment = new Attachment(); $attachment->hydrate(object: $object); - -// var_dump($attachment->getTitle()); - return $this->insert(entity: $attachment); } diff --git a/lib/Db/Catalog.php b/lib/Db/Catalog.php index 05958101..938e5122 100644 --- a/lib/Db/Catalog.php +++ b/lib/Db/Catalog.php @@ -42,12 +42,6 @@ public function getJsonFields(): array public function hydrate(array $object): self { - - - if(isset($object['metadata']) === false) { - $object['metadata'] = []; - } - $jsonFields = $this->getJsonFields(); foreach($object as $key => $value) { @@ -60,7 +54,7 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); +// ("Error writing $key"); } } diff --git a/lib/Db/CatalogMapper.php b/lib/Db/CatalogMapper.php index 0cc429d2..71284aee 100644 --- a/lib/Db/CatalogMapper.php +++ b/lib/Db/CatalogMapper.php @@ -61,9 +61,6 @@ public function createFromArray(array $object): Catalog { $catalog = new Catalog(); $catalog->hydrate(object: $object); - -// var_dump($catalog->getTitle()); - return $this->insert(entity: $catalog); } diff --git a/lib/Db/Listing.php b/lib/Db/Listing.php index de0825ad..b11d24dd 100644 --- a/lib/Db/Listing.php +++ b/lib/Db/Listing.php @@ -21,6 +21,7 @@ class Listing extends Entity implements JsonSerializable protected ?DateTime $lastSync = null; protected ?bool $default = false; protected ?bool $available = false; + protected ?string $organisation = null; public function __construct() { $this->addType(fieldName: 'title', type: 'string'); @@ -34,6 +35,7 @@ public function __construct() { $this->addType(fieldName: 'lastSync', type: 'datetime'); $this->addType(fieldName: 'default', type: 'boolean'); $this->addType(fieldName: 'available', type: 'boolean'); + $this->addType(fieldName: 'organisation', type: 'string'); } public function getJsonFields(): array @@ -59,7 +61,6 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); } } @@ -78,9 +79,10 @@ public function jsonSerialize(): array 'metadata' => $this->metadata, 'catalogId' => $this->catalogId, 'status' => $this->status, - 'lastSync' => $this->lastSync->format('c'), + 'lastSync' => $this->lastSync?->format('c'), 'default' => $this->default, 'available' => $this->available, + 'organisation'=> json_decode($this->organisation, true), ]; $jsonFields = $this->getJsonFields(); diff --git a/lib/Db/ListingMapper.php b/lib/Db/ListingMapper.php index bbe56e0e..c3337af4 100644 --- a/lib/Db/ListingMapper.php +++ b/lib/Db/ListingMapper.php @@ -3,6 +3,7 @@ namespace OCA\OpenCatalogi\Db; use OCA\OpenCatalogi\Db\Listing; +use OCA\OpenCatalogi\Db\Organisation; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -19,34 +20,134 @@ public function find(int $id): Listing { $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from('listings') - ->where( - $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) - ); + $qb->select( + 'l.*', + 'o.id AS organisation_id', + 'o.title AS organisation_title', + 'o.summary AS organisation_summary', + 'o.description AS organisation_description', + 'o.image AS organisation_image', + 'o.oin AS organisation_oin', + 'o.tooi AS organisation_tooi', + 'o.rsin AS organisation_rsin', + 'o.pki AS organisation_pki' + ) + ->from('listings', 'l') + ->leftJoin('l', 'organizations', 'o', 'l.organisation = o.id') + ->where( + $qb->expr()->eq('l.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + + return $this->findEntityCustom(query: $qb); + } - return $this->findEntity(query: $qb); + /** + * Returns a db result and throws exceptions when there are more or less + * results CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity the entity + * @psalm-return T the entity + * @throws Exception + * @throws MultipleObjectsReturnedException if more than one item exist + * @throws DoesNotExistException if the item does not exist + * @since 14.0.0 + */ + protected function findEntityCustom(IQueryBuilder $query): Entity { + return $this->mapRowToEntityCustom($this->findOneQuery($query)); } - public function findAll(?int $limit = null, ?int $offset = null, ?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): array - { - $qb = $this->db->getQueryBuilder(); + /** + * CUSTOM FOR JOINS + */ + protected function mapRowToEntityCustom(array $row): Entity { + unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column + + // Map the Organisation fields to a sub-array + $organisationData = [ + 'id' => $row['organisation_id'] ?? null, + 'title' => $row['organisation_title'] ?? null, + 'summary' => $row['organisation_summary'] ?? null, + 'description' => $row['organisation_description'] ?? null, + 'image' => $row['organisation_image'] ?? null, + 'oin' => $row['organisation_oin'] ?? null, + 'tooi' => $row['organisation_tooi'] ?? null, + 'rsin' => $row['organisation_rsin'] ?? null, + 'pki' => $row['organisation_pki'] ?? null, + ]; + + $organisationIsEmpty = true; + foreach ($organisationData as $key => $value) { + if ($value !== null) { + $organisationIsEmpty = false; + } + + if (array_key_exists("organisation_$key", $row) === true) { + unset($row["organisation_$key"]); + } + } + + $row['organisation'] = $organisationIsEmpty === true ? null : json_encode(Organisation::fromRow($organisationData)->jsonSerialize()); - $qb->select('*') - ->from('listings') - ->setMaxResults($limit) - ->setFirstResult($offset); - - foreach($filters as $filter => $value) { - if ($value === 'IS NOT NULL') { - $qb->andWhere($qb->expr()->isNotNull($filter)); - } elseif ($value === 'IS NULL') { - $qb->andWhere($qb->expr()->isNull($filter)); - } else { - $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value))); + return \call_user_func($this->entityClass .'::fromRow', $row); + } + + /** + * Runs a sql query and returns an array of entities CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity[] all fetched entities + * @psalm-return T[] all fetched entities + * @throws Exception + * @since 14.0.0 + */ + protected function findEntitiesCustom(IQueryBuilder $query): array { + $result = $query->executeQuery(); + try { + $entities = []; + while ($row = $result->fetch()) { + $entities[] = $this->mapRowToEntityCustom($row); } + return $entities; + } finally { + $result->closeCursor(); + } + } + + public function findAll(?int $limit = null, ?int $offset = null, ?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + 'l.*', + 'o.id AS organisation_id', + 'o.title AS organisation_title', + 'o.summary AS organisation_summary', + 'o.description AS organisation_description', + 'o.image AS organisation_image', + 'o.oin AS organisation_oin', + 'o.tooi AS organisation_tooi', + 'o.rsin AS organisation_rsin', + 'o.pki AS organisation_pki' + ) + ->from('listings', 'l') + ->leftJoin('l', 'organizations', 'o', 'l.organisation = o.id') + ->setMaxResults($limit) + ->setFirstResult($offset); + + + // Apply filters + foreach ($filters as $filter => $value) { + if ($value === 'IS NOT NULL') { + $qb->andWhere($qb->expr()->isNotNull($filter)); + } elseif ($value === 'IS NULL') { + $qb->andWhere($qb->expr()->isNull($filter)); + } else { + $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value))); + } } + // Apply search conditions if (!empty($searchConditions)) { $qb->andWhere('(' . implode(' OR ', $searchConditions) . ')'); foreach ($searchParams as $param => $value) { @@ -54,17 +155,18 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters } } - return $this->findEntities(query: $qb); - } + // Use the existing findEntities method to fetch and map the results + return $this->findEntitiesCustom($qb); + } public function createFromArray(array $object): Listing { $listing = new Listing(); $listing->hydrate(object: $object); -// var_dump($listing->getTitle()); + $listing = $this->insert(entity: $listing); - return $this->insert(entity: $listing); + return $this->find($listing->getId()); } public function updateFromArray(int $id, array $object): Listing @@ -72,6 +174,8 @@ public function updateFromArray(int $id, array $object): Listing $listing = $this->find($id); $listing->hydrate($object); - return $this->update($listing); + $listing = $this->update($listing); + + return $this->find($listing->getId()); } } diff --git a/lib/Db/MetaData.php b/lib/Db/MetaData.php index 759cf14c..ced83048 100644 --- a/lib/Db/MetaData.php +++ b/lib/Db/MetaData.php @@ -47,7 +47,6 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); } } diff --git a/lib/Db/MetaDataMapper.php b/lib/Db/MetaDataMapper.php index d73d1859..3bce0ac4 100644 --- a/lib/Db/MetaDataMapper.php +++ b/lib/Db/MetaDataMapper.php @@ -61,9 +61,6 @@ public function createFromArray(array $object): MetaData { $metadata = new MetaData(); $metadata->hydrate(object: $object); - -// var_dump($metadata->getTitle()); - return $this->insert(entity: $metadata); } diff --git a/lib/Db/Organisation.php b/lib/Db/Organisation.php index 4818a144..1978591f 100644 --- a/lib/Db/Organisation.php +++ b/lib/Db/Organisation.php @@ -53,8 +53,6 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); - } } return $this; diff --git a/lib/Db/OrganisationMapper.php b/lib/Db/OrganisationMapper.php index 2b412b2f..0701e61c 100644 --- a/lib/Db/OrganisationMapper.php +++ b/lib/Db/OrganisationMapper.php @@ -61,9 +61,6 @@ public function createFromArray(array $object): Organisation { $organisation = new Organisation(); $organisation->hydrate(object: $object); - -// var_dump($organisation->getTitle()); - return $this->insert(entity: $organisation); } diff --git a/lib/Db/Publication.php b/lib/Db/Publication.php index 3dc4edad..cde4baf2 100644 --- a/lib/Db/Publication.php +++ b/lib/Db/Publication.php @@ -78,10 +78,15 @@ public function hydrate(array $object): self $this->setModified(new DateTime()); - if(isset($object['published']) === false) { + if (isset($object['published']) === false) { $object['published'] = null; } + // Todo: MetaData is depricated, we should use Schema instead. But this needs front-end changes as well. + if (empty($object['schema']) === true) { + $object['schema'] = $object['metaData'] ?? $this->getMetaData(); + } + foreach($object as $key => $value) { if (in_array($key, $jsonFields) === true && $value === []) { $value = null; @@ -92,13 +97,10 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); +// ("Error writing $key"); } } - // Todo: MetaData is depricated, we should use Schema instead. But this needs front-end changes as well. - $this->setSchema($this->getMetaData()); - $this->setAttachmentCount('0'); if($this->attachments !== null) { $this->setAttachmentCount(count($this->getAttachments())); @@ -118,8 +120,8 @@ public function jsonSerialize(): array 'image' => $this->image, 'category' => $this->category, 'portal' => $this->portal, - 'catalogi' => $this->catalogi, - 'metaData' => $this->metaData, + 'catalogi' => json_decode($this->catalogi, true), + 'metaData' => json_decode($this->metaData, true), 'published' => $this->published?->format('c'), 'modified' => $this->modified?->format('c'), 'featured' => $this->featured !== null ? (bool) $this->featured : null, @@ -127,7 +129,7 @@ public function jsonSerialize(): array 'data' => $this->data, 'attachments' => $this->attachments, 'attachmentCount' => $this->attachmentCount, - 'schema' => $this->schema, + 'schema' => json_decode($this->schema, true), 'status' => $this->status, 'license' => $this->license, 'themes' => $this->themes, diff --git a/lib/Db/PublicationMapper.php b/lib/Db/PublicationMapper.php index 9aa60c71..57860873 100644 --- a/lib/Db/PublicationMapper.php +++ b/lib/Db/PublicationMapper.php @@ -6,6 +6,7 @@ use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; use OCP\IDBConnection; class PublicationMapper extends QBMapper @@ -19,13 +20,127 @@ public function find(int $id): Publication { $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from('publications') + $qb->select( + 'p.*', + 'c.id AS catalogi_id', + 'c.title AS catalogi_title', + 'c.summary AS catalogi_summary', + 'c.description AS catalogi_description', + 'c.image AS catalogi_image', + 'c.search AS catalogi_search', + 'c.listed AS catalogi_listed', + 'c.organisation AS catalogi_organisation', + 'c.metadata AS catalogi_metadata', + 'm.id AS metadata_id', + 'm.title AS metadata_title', + 'm.version AS metadata_version', + 'm.description AS metadata_description', + 'm.required AS metadata_required', + 'm.properties AS metadata_properties', + ) + ->from('publications', 'p') + ->leftJoin('p', 'catalogi', 'c', 'p.catalogi = c.id') + ->leftJoin('p', 'metadata', 'm', 'p.meta_data = m.id') ->where( - $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + $qb->expr()->eq('p.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); - return $this->findEntity(query: $qb); + return $this->findEntityCustom(query: $qb); + } + + /** + * Returns a db result and throws exceptions when there are more or less + * results CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity the entity + * @psalm-return T the entity + * @throws Exception + * @throws MultipleObjectsReturnedException if more than one item exist + * @throws DoesNotExistException if the item does not exist + * @since 14.0.0 + */ + protected function findEntityCustom(IQueryBuilder $query): Entity { + return $this->mapRowToEntityCustom($this->findOneQuery($query)); + } + + /** + * CUSTOM FOR JOINS + */ + protected function mapRowToEntityCustom(array $row): Entity { + unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column + + // Map the Catalogi fields to a sub-array + $catalogiData = [ + 'id' => $row['catalogi_id'] ?? null, + 'title' => $row['catalogi_title'] ?? null, + 'summary' => $row['catalogi_summary'] ?? null, + 'description' => $row['catalogi_description'] ?? null, + 'image' => $row['catalogi_image'] ?? null, + 'search' => $row['catalogi_search'] ?? null, + 'listed' => $row['catalogi_listed'] ?? null, + 'organisation' => $row['catalogi_organisation'] ?? null, + 'metadata' => $row['catalogi_metadata'] ?? null, + ]; + + $catalogiIsEmpty = true; + foreach ($catalogiData as $key => $value) { + if ($value !== null) { + $catalogiIsEmpty = false; + } + + if (array_key_exists("catalogi_$key", $row) === true) { + unset($row["catalogi_$key"]); + } + } + + // Map the MetaData fields to a sub-array + $metaDataData = [ + 'id' => $row['metadata_id'] ?? null, + 'title' => $row['metadata_title'] ?? null, + 'version' => $row['metadata_version'] ?? null, + 'description' => $row['metadata_description'] ?? null, + 'required' => $row['metadata_required'] ?? null, + 'properties' => $row['metadata_properties'] ?? null, + ]; + + $metaDataIsEmpty = true; + foreach ($metaDataData as $key => $value) { + if ($value !== null) { + $metaDataIsEmpty = false; + } + + if (array_key_exists("metadata_$key", $row) === true) { + unset($row["metadata_$key"]); + } + } + + $row['catalogi'] = $catalogiIsEmpty === true ? null : json_encode(Catalog::fromRow($catalogiData)->jsonSerialize()); + $row['metaData'] = $metaDataIsEmpty === true ? null : json_encode(MetaData::fromRow($metaDataData)->jsonSerialize()); + + return \call_user_func($this->entityClass .'::fromRow', $row); + } + + /** + * Runs a sql query and returns an array of entities CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity[] all fetched entities + * @psalm-return T[] all fetched entities + * @throws Exception + * @since 14.0.0 + */ + protected function findEntitiesCustom(IQueryBuilder $query): array { + $result = $query->executeQuery(); + try { + $entities = []; + while ($row = $result->fetch()) { + $entities[] = $this->mapRowToEntityCustom($row); + } + return $entities; + } finally { + $result->closeCursor(); + } } private function parseComplexFilter(IQueryBuilder $queryBuilder, array $filter, string $name): IQueryBuilder @@ -49,7 +164,7 @@ private function parseComplexFilter(IQueryBuilder $queryBuilder, array $filter, $queryBuilder->andWhere($queryBuilder->expr()->lt($name, $queryBuilder->createNamedParameter($value))); break; default: - $queryBuilder->andWhere($queryBuilder->expr()->eq($name, $queryBuilder->createNamedParameter($filter))); + $queryBuilder->andWhere($queryBuilder->expr()->eq(x: $name, y: $queryBuilder->createNamedParameter($filter))); } } @@ -60,7 +175,7 @@ private function addFilters(IQueryBuilder $queryBuilder, array $filters): IQuery { foreach($filters as $key => $filter) { if(is_array($filter) === false) { - $queryBuilder->andWhere($queryBuilder->expr()->eq($key, $queryBuilder->createNamedParameter($key))); + $queryBuilder->andWhere($queryBuilder->expr()->eq($key, $queryBuilder->createNamedParameter($filter))); $queryBuilder->setParameter($key, $filter); continue; } @@ -102,8 +217,27 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters { $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from('publications') + $qb->select( + 'p.*', + 'c.id AS catalogi_id', + 'c.title AS catalogi_title', + 'c.summary AS catalogi_summary', + 'c.description AS catalogi_description', + 'c.image AS catalogi_image', + 'c.search AS catalogi_search', + 'c.listed AS catalogi_listed', + 'c.organisation AS catalogi_organisation', + 'c.metadata AS catalogi_metadata', + 'm.id AS metadata_id', + 'm.title AS metadata_title', + 'm.version AS metadata_version', + 'm.description AS metadata_description', + 'm.required AS metadata_required', + 'm.properties AS metadata_properties', + ) + ->from('publications', 'p') + ->leftJoin('p', 'catalogi', 'c', 'p.catalogi = c.id') + ->leftJoin('p', 'metadata', 'm', 'p.meta_data = m.id') ->setMaxResults($limit) ->setFirstResult($offset); @@ -123,7 +257,8 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters } } - return $this->findEntities(query: $qb); + // Use the existing findEntities method to fetch and map the results + return $this->findEntitiesCustom($qb); } public function createFromArray(array $object): Publication @@ -131,9 +266,9 @@ public function createFromArray(array $object): Publication $publication = new Publication(); $publication->hydrate(object: $object); -// var_dump($publication->getTitle()); + $publication = $this->insert(entity: $publication); - return $this->insert(entity: $publication); + return $this->find($publication->getId()); } public function updateFromArray(int $id, array $object): Publication @@ -141,6 +276,8 @@ public function updateFromArray(int $id, array $object): Publication $publication = $this->find(id: $id); $publication->hydrate(object: $object); - return $this->update($publication); + $publication = $this->update($publication); + + return $this->find($publication->getId()); } } diff --git a/lib/Db/Theme.php b/lib/Db/Theme.php index 0854aad9..a8365809 100644 --- a/lib/Db/Theme.php +++ b/lib/Db/Theme.php @@ -45,7 +45,7 @@ public function hydrate(array $object): self try { $this->$method($value); } catch (\Exception $exception) { -// var_dump("Error writing $key"); +// ("Error writing $key"); } } diff --git a/lib/Db/ThemeMapper.php b/lib/Db/ThemeMapper.php index 96f2b37c..561b30d4 100644 --- a/lib/Db/ThemeMapper.php +++ b/lib/Db/ThemeMapper.php @@ -61,9 +61,6 @@ public function createFromArray(array $object): Theme { $theme = new Theme(); $theme->hydrate(object: $object); - -// var_dump($catalog->getTitle()); - return $this->insert(entity: $theme); } diff --git a/lib/Migration/Version6Date20240723125106.php b/lib/Migration/Version6Date20240723125106.php index d12ccc8d..b04a1818 100644 --- a/lib/Migration/Version6Date20240723125106.php +++ b/lib/Migration/Version6Date20240723125106.php @@ -80,18 +80,18 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => false, ] ); - $table->addColumn(name: 'organization', typeName: TYPES::JSON, options: [ - 'default' => 'a:0:{}', + $organization = $table->addColumn(name: 'organization', typeName: TYPES::JSON, options: [ 'notnull' => false, ]); - $table->addColumn(name: 'data', typeName: TYPES::JSON, options: [ - 'default' => 'a:0:{}', + $organization->setDefault('{}'); + $data = $table->addColumn(name: 'data', typeName: TYPES::JSON, options: [ 'notnull' => false, ]); - $table->addColumn(name: 'attachments', typeName: TYPES::JSON, options: [ - 'default' => 'a:0:{}', + $data->setDefault('{}'); + $attachments = $table->addColumn(name: 'attachments', typeName: TYPES::JSON, options: [ 'notnull' => false, ]); + $attachments->setDefault('{}'); $table->addColumn(name: 'attachment_count', typeName: TYPES::INTEGER); $table->addColumn(name: 'schema', typeName: TYPES::STRING); $table->addColumn(name: 'status', typeName: TYPES::STRING); diff --git a/lib/Migration/Version6Date20240808085441.php b/lib/Migration/Version6Date20240808085441.php index 28e43b3a..c1a2bea2 100644 --- a/lib/Migration/Version6Date20240808085441.php +++ b/lib/Migration/Version6Date20240808085441.php @@ -53,13 +53,13 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); } if($table->hasColumn(name: 'metadata') === false) { - $table->addColumn( + $metadata = $table->addColumn( name: 'metadata', typeName: Types::JSON, options: [ 'notNull' => false, - 'default' => 'a:0:{}' ]); + $metadata->setDefault('{}'); } } diff --git a/lib/Migration/Version6Date20240809120147.php b/lib/Migration/Version6Date20240809120147.php new file mode 100644 index 00000000..eb923ff0 --- /dev/null +++ b/lib/Migration/Version6Date20240809120147.php @@ -0,0 +1,72 @@ +hasTable(tableName: 'listings') === true) { + $table = $schema->getTable(tableName: 'listings'); + + if($table->hasColumn(name: 'organization') === true) { + $column = $table->dropColumn('organization'); + } + if($table->hasColumn(name: 'organisation') === false) { + $table->addColumn( + name: 'organisation', + typeName: Types::STRING, + options: [ + 'notNull' => false, + 'default' => null + ]); + $output->info('organisation should be added to listing'); + } + + } + + 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/DirectoryService.php b/lib/Service/DirectoryService.php index dc47b754..4de3e99b 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -222,7 +222,9 @@ public function listCatalog (array $catalog): array $listing = $this->getDirectoryEntry(catalogId: $catalogId); - $listing['title'] = $catalog['title']; + $listing['title'] = $catalog['title']; + $listing['organisation'] = $catalog['organisation']; + $listing['metaData'] = $catalog['metaData']; if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/lib/Service/ElasticSearchService.php b/lib/Service/ElasticSearchService.php index ef16edd4..3f0be4a7 100644 --- a/lib/Service/ElasticSearchService.php +++ b/lib/Service/ElasticSearchService.php @@ -91,7 +91,7 @@ public function updateObject(string $id, array $object, array $config): array } } - public function parseFilter(string $name, array $filter): array + public function parseFilter(string $name, array|string $filter): array { if(is_array($filter) === false) { @@ -149,7 +149,6 @@ public function parseFilters (array $filters): array } if(isset($filters['.catalogi']) === true) { -// var_dump($filters['.catalogi']); $body['query']['bool']['must'][] = [ 'match' => [ 'catalogi._id' => [ diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 6d6ec56c..9b33b0d6 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -4,10 +4,27 @@ use DateTime; use Exception; +use Mpdf\Mpdf; +use Mpdf\MpdfException; +use OCP\Files\File; +use OCP\Files\GenericFileException; +use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IUserSession; +use OCP\Lock\LockedException; use OCP\Share\IManager; +use OCP\Share\IShare; use Psr\Log\LoggerInterface; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Loader\FilesystemLoader; +use ZipArchive; class FileService { @@ -18,6 +35,19 @@ public function __construct( private readonly IManager $shareManager ) {} + /** + * Get the name for the folder used for storing files of the given publication. + * + * @param string $publicationId The id of the Publication. + * @param string $publicationTitle The title of the Publication. + * + * @return string The name the folder for this publication should have. + */ + public function getPublicationFolderName(string $publicationId, string $publicationTitle): string + { + return "($publicationId) $publicationTitle"; + } + /** * Gets and returns the current host / domain with correct protocol. * @@ -36,6 +66,63 @@ private function getCurrentDomain(): string return $protocol . $host; } + + /** + * Returns a share link for the given IShare object. + * + * @param IShare $share An IShare object we are getting the share link for. + * + * @return string The share link needed to get the file or folder for the given IShare object. + */ + public function getShareLink(IShare $share): string + { + return $this->getCurrentDomain() . '/index.php/s/' . $share->getToken(); + } + + + /** + * Try to find a IShare object with given $path & $shareType. + * + * @param string $path The path to a file we are trying to find a IShare object for. + * @param int|null $shareType The shareType of the share we are trying to find. + * + * @return IShare|null An IShare object or null. + */ + public function findShare(string $path, ?int $shareType = 3): ?IShare + { + $path = trim(string: $path, characters: '/'); + + // Get the current user. + $currentUser = $this->userSession->getUser(); + $userId = $currentUser ? $currentUser->getUID() : 'Guest'; + try { + $userFolder = $this->rootFolder->getUserFolder(userId: $userId); + } catch(NotPermittedException) { + $this->logger->error("Can't find share for $path because user (folder) for user $userId couldn't be found"); + + return null; + } + + try { + // Note: if we ever want to find shares for folders instead of files, this should work for folders as well? + $file = $userFolder->get(path: $path); + } catch(NotFoundException $e) { + $this->logger->error("Can't find share for $path because file doesn't exist"); + + return null; + } + + if ($file instanceof File) { + $shares = $this->shareManager->getSharesBy(userId: $userId, shareType: $shareType, path: $file); + if (count($shares) > 0) { + return $shares[0]; + } + } + + return null; + } + + /** * Creates and returns a share link for a file (or folder). * (https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share) @@ -63,8 +150,8 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss $userId = $currentUser ? $currentUser->getUID() : 'Guest'; try { $userFolder = $this->rootFolder->getUserFolder(userId: $userId); - } catch(\OCP\Files\NotPermittedException) { - $this->logger->error("Can't create share link for $path because user (folder) couldn't be found"); + } catch(NotPermittedException) { + $this->logger->error("Can't create share link for $path because user (folder) for user $userId couldn't be found"); return "User (folder) couldn't be found"; } @@ -72,7 +159,7 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss try { // Note: if we ever want to create share links for folders instead of files, just remove this try catch and only use setTarget, not setNodeId. $file = $userFolder->get(path: $path); - } catch(\OCP\Files\NotFoundException $e) { + } catch(NotFoundException $e) { $this->logger->error("Can't create share link for $path because file doesn't exist"); return 'File not found at '.$path; @@ -94,7 +181,7 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss try { $share = $this->shareManager->createShare(share: $share); - return $this->getCurrentDomain() . '/index.php/s/' . $share->getToken(); + return $this->getShareLink($share); } catch (Exception $exception) { $this->logger->error("Can't create share link for $path: " . $exception->getMessage()); @@ -103,7 +190,7 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss } /** - * Uploads a file to NextCloud. Will overwrite a file if it already exists and create a new one if it doesn't exist. + * Uploads a file to NextCloud. Will create a new file if it doesn't exist yet. * * @param mixed $content The content of the file. * @param string $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) @@ -123,7 +210,7 @@ public function uploadFile(mixed $content, string $filePath): bool try { try { $userFolder->get(path: $filePath); - } catch(\OCP\Files\NotFoundException $e) { + } catch(NotFoundException $e) { $userFolder->newFile(path: $filePath); $file = $userFolder->get(path: $filePath); @@ -136,7 +223,56 @@ public function uploadFile(mixed $content, string $filePath): bool $this->logger->warning("File $filePath already exists."); return false; - } catch(\OCP\Files\NotPermittedException|\OCP\Files\GenericFileException|\OCP\Lock\LockedException $e) { + } catch(NotPermittedException|GenericFileException|LockedException $e) { + $this->logger->error("Can't create file $filePath: " . $e->getMessage()); + + throw new Exception("Can't write to file $filePath"); + } + } + + /** + * Overwrites an existing file in NextCloud. + * + * @param mixed $content The content of the file. + * @param string $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) + * @param bool $createNew Default = false. If set to true this function will create a new file if it doesn't exist yet. + * + * @return bool True if successful. + * @throws Exception In case we can't write to file because it is not permitted. + */ + public function updateFile(mixed $content, string $filePath, bool $createNew = false): bool + { + $filePath = trim(string: $filePath, characters: '/'); + + // Get the current user. + $currentUser = $this->userSession->getUser(); + $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); + + // Check if file exists and overwrite it if it does. + try { + try { + $file = $userFolder->get(path: $filePath); + + $file->putContent(data: $content); + + return true; + } catch(NotFoundException $e) { + if ($createNew === true) { + $userFolder->newFile(path: $filePath); + $file = $userFolder->get(path: $filePath); + + $file->putContent(data: $content); + + $this->logger->info("File $filePath did not exist, created a new file for it."); + return true; + } + } + + // File already exists. + $this->logger->warning("File $filePath already exists."); + return false; + + } catch(NotPermittedException|GenericFileException|LockedException $e) { $this->logger->error("Can't create file $filePath: " . $e->getMessage()); throw new Exception("Can't write to file $filePath"); @@ -166,13 +302,13 @@ public function deleteFile(string $filePath): bool $file->delete(); return true; - } catch(\OCP\Files\NotFoundException $e) { + } catch(NotFoundException $e) { // File does not exist. $this->logger->warning("File $filePath does not exist."); return false; } - } catch(\OCP\Files\NotPermittedException|\OCP\Files\InvalidPathException $e) { + } catch(NotPermittedException|InvalidPathException $e) { $this->logger->error("Can't delete file $filePath: " . $e->getMessage()); throw new Exception("Can't delete file $filePath"); @@ -199,7 +335,7 @@ public function createFolder(string $folderPath): bool try { try { $userFolder->get(path: $folderPath); - } catch(\OCP\Files\NotFoundException $e) { + } catch(NotFoundException $e) { $userFolder->newFolder(path: $folderPath); return true; @@ -209,11 +345,115 @@ public function createFolder(string $folderPath): bool $this->logger->info("This folder already exits $folderPath"); return false; - } catch(\OCP\Files\NotPermittedException $e) { + } catch(NotPermittedException $e) { $this->logger->error("Can't create folder $folderPath: " . $e->getMessage()); throw new Exception("Can\'t create folder $folderPath"); } } + + /** + * Creates a pdf file in a /tmp folder using a twig template and given context. + * + * @param string $twigTemplate The path and filename of the twig template to use in the folder "lib/Templates". + * @param array $context The context to pass along while rendering the pdf with the given twig template. + * + * @return Mpdf A Mpdf object. + * Use "$mpdf->Output(name: $filename, dest: Destination::FILE)" to create the actual file or use one of the other Destination::X options. + * Please use the "rmdir(directory: '/tmp/mpdf');" function after this to clean up temporary files. + * @throws MpdfException|LoaderError|RuntimeError|SyntaxError + */ + public function createPdf(string $twigTemplate, array $context): Mpdf + { + // Initialize Twig + $loader = new FilesystemLoader(paths: 'lib/Templates', rootPath: '/var/www/html/apps-extra/opencatalogi'); + $twig = new Environment($loader); + + // Render the Twig template + $html = $twig->render(name: $twigTemplate, context: $context); + + // Check if the directory exists, if not, create it + if (file_exists(filename: '/tmp/mpdf') === false) { + mkdir(directory: '/tmp/mpdf', recursive: true); + } + + // Set permissions for the directory (ensure it's writable) + chmod(filename: '/tmp/mpdf', permissions: 0777); + + // Initialize mPDF + $mpdf = new Mpdf(config: ['tempDir' => '/tmp/mpdf']); + + // Write HTML to PDF + $mpdf->WriteHTML(html: $html); + + return $mpdf; + } + + + /** + * Creates a ZIP archive at the $tempZip location using the $tempFolder location as input for the ZIP archive. + * Please use "unlink(filename: $tempZip);" or the downloadZip() function after calling this function to clean up temporary files. + * + * @param string $inputFolder The (tmp) location used as input for creating the ZIP archive. + * @param string $tempZip The tmp location where the ZIP will be saved. Please start this with '/tmp/..' and end with '../zipName.zip'. + * + * @return string|null Returns null if created successfully and a string in case of an error. + */ + public function createZip(string $inputFolder, string $tempZip): ?string + { + // Create ZIP archive. + $zip = new ZipArchive(); + if ($zip->open(filename: $tempZip, flags: ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) { + $files = new RecursiveIteratorIterator( + iterator: new RecursiveDirectoryIterator($inputFolder), + mode: RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $name => $file) { + // Skip directories (they would be added automatically) + if ($file->isDir() === false) { + $filePath = $file->getRealPath(); + $relativePath = substr(string: $filePath, offset: strlen(string: $inputFolder) + 1); + + // Add file to zip + $zip->addFile(filepath: $filePath, entryname: $relativePath); + } + } + $zip->close(); + } else { + return "failed to create ZIP archive"; + } + + return null; + } + + + /** + * A function that outputs a downloadable ZIP to the response body of the current api request. + * Can best be used after creating a ZIP archive with the createZip() function. + * + * @param string $tempZip The tmp location where the ZIP is saved. + * Note that "unlink(filename: $tempZip);" will be called at the end of this function. + * @param string|null $inputFolder The tmp location used as input for creating the ZIP archive. + * Will unlink all files in this folder and remove this folder at the end of this function. + * + * @return void + */ + public function downloadZip(string $tempZip, ?string $inputFolder = null): void + { + // Send the ZIP file to the client for download. + header(header: 'Content-Type: application/zip'); + header(header: 'Content-disposition: attachment; filename=' . basename($tempZip)); + header(header: 'Content-Length: ' . filesize($tempZip)); + readfile(filename: $tempZip); + + // Cleanup temporary files. + if ($inputFolder !== null) { + array_map(callback: 'unlink', array: glob(pattern: "$inputFolder/*.*")); + rmdir(directory: $inputFolder); + } + unlink(filename: $tempZip); + } + } diff --git a/lib/Templates/publication.html.twig b/lib/Templates/publication.html.twig new file mode 100644 index 00000000..098414b8 --- /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/src/composables/UseFileSelection.js b/src/composables/UseFileSelection.js new file mode 100644 index 00000000..1fc33e0f --- /dev/null +++ b/src/composables/UseFileSelection.js @@ -0,0 +1,95 @@ +import { useDropZone, useFileDialog } from '@vueuse/core' +import { ref, computed } from 'vue' +import { publicationStore } from './../store/store.js' + +/** + * File selection composable + * @param {Array} options + * + * Special thanks to Github user adamreisnz for creating most of this file + * https://github.com/adamreisnz + * https://github.com/vueuse/vueuse/issues/4085 + * + */ +export function useFileSelection(options) { + + // Extract options + const { + dropzone, + allowMultiple, + allowedFileTypes, + onFileDrop, + } = options + + // Data types computed ref + const dataTypes = computed(() => { + if (allowedFileTypes?.value) { + if (!Array.isArray(allowedFileTypes.value)) { + return [allowedFileTypes.value] + } + return allowedFileTypes.value + } + return null + }) + + // Accept string computed ref + const accept = computed(() => { + if (Array.isArray(dataTypes.value)) { + return dataTypes.value.join(',') + } + return '*' + }) + + // Handling of files drop + const onDrop = files => { + if (!files || files.length === 0) { + return + } + if (files instanceof FileList) { + files = Array.from(files) + } + if (files.length > 1 && !allowMultiple.value) { + files = [files[0]] + } + filesList.value = files + onFileDrop && onFileDrop() + } + + const reset = () => { + filesList.value = null + } + + // const onLeave = () => { + // let timer + // document.addEventListener('mousemove', () => { + // clearTimeout(timer) + // timer = setTimeout(isOverDropZone.value = false, 300) + // }) + // } + + const setFiles = (files) => { + filesList.value = files + publicationStore.setAttachmentFile(null) + } + + // Setup dropzone and file dialog composables + const { isOverDropZone } = useDropZone(dropzone, { dataTypes, onDrop }) + const { onChange, open } = useFileDialog({ + accept: accept.value, + multiple: allowMultiple?.value, + }) + + const filesList = ref(null) + + // Use onChange handler + onChange(fileList => onDrop(fileList)) + + // Expose interface + return { + isOverDropZone, + openFileUpload: open, + files: filesList, + reset, + setFiles, + } +} diff --git a/src/dialogs/attachment/CopyAttachmentDialog.vue b/src/dialogs/attachment/CopyAttachmentDialog.vue index f1d4eb4f..785c73f3 100644 --- a/src/dialogs/attachment/CopyAttachmentDialog.vue +++ b/src/dialogs/attachment/CopyAttachmentDialog.vue @@ -86,14 +86,11 @@ export default { .then((response) => { this.loading = false this.succes = true - // Lets refresh the attachment list - response.json().then((data) => { - publicationStore.setAttachmentItem(data) - }) - if (publicationStore.publicationItem?.id) { - publicationStore.getPublicationAttachments(publicationStore.publicationItem.id) - // @todo update the publication item + + if (publicationStore.publicationItem) { + publicationStore.getPublicationAttachments(publicationStore.publicationItem?.id) } + // Wait for the user to read the feedback then close the model const self = this setTimeout(function() { diff --git a/src/dialogs/attachment/DeleteAttachmentDialog.vue b/src/dialogs/attachment/DeleteAttachmentDialog.vue index 9d6b36b0..f243cfa7 100644 --- a/src/dialogs/attachment/DeleteAttachmentDialog.vue +++ b/src/dialogs/attachment/DeleteAttachmentDialog.vue @@ -61,7 +61,7 @@ export default { }, data() { return { - + filterdAttachments: [], loading: false, succes: false, error: false, @@ -83,10 +83,38 @@ export default { this.loading = false this.succes = true // Lets refresh the attachment list - if (publicationStore.publicationItem?.id) { - publicationStore.getPublicationAttachments(publicationStore.publicationItem.id) - // @todo update the publication item + if (publicationStore.publicationItem) { + publicationStore.getPublicationAttachments(publicationStore.publicationItem?.id) + this.filterdAttachments = publicationStore.publicationItem.attachments.filter((attachment) => { return parseInt(attachment) !== parseInt(publicationStore.attachmentItem.id) }) + + fetch( + `/index.php/apps/opencatalogi/api/publications/${publicationStore.publicationItem.id}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...publicationStore.publicationItem, + attachments: [...this.filterdAttachments], + }), + }, + ) + .then((response) => { + this.loading = false + + // Lets refresh the publicationList + publicationStore.refreshPublicationList() + response.json().then((data) => { + publicationStore.setPublicationItem(data) + }) + }) + .catch((err) => { + this.error = err + this.loading = false + }) } + // Wait for the user to read the feedback then close the model const self = this setTimeout(function() { diff --git a/src/dialogs/attachment/DepublishAttachmentDialog.vue b/src/dialogs/attachment/DepublishAttachmentDialog.vue index bf0a2ff1..42e76980 100644 --- a/src/dialogs/attachment/DepublishAttachmentDialog.vue +++ b/src/dialogs/attachment/DepublishAttachmentDialog.vue @@ -30,7 +30,7 @@ import { publicationStore, navigationStore } from '../../store/store.js' v-if="!succes" :disabled="loading" type="primary" - @click="CopyAttachment()"> + @click="depublishAttachment()">