From 275bada15d9b48ab98dbbb77550554d6a9b311f8 Mon Sep 17 00:00:00 2001 From: Cyril Chapellier Date: Sun, 29 Oct 2023 20:47:31 +0100 Subject: [PATCH] Feature: allow public calendars (#105) --- .github/workflows/ci.yml | 35 +- composer.json | 6 +- composer.lock | 884 ++++++++++-------- .../Admin/AddressBookController.php | 101 ++ src/Controller/Admin/CalendarController.php | 281 ++++++ src/Controller/Admin/DashboardController.php | 38 + src/Controller/Admin/UserController.php | 304 ++++++ src/Controller/AdminController.php | 662 ------------- src/Controller/DAVController.php | 10 +- src/Entity/CalendarInstance.php | 39 +- src/Form/CalendarInstanceType.php | 9 + src/Plugins/PublicAwareDAVACLPlugin.php | 70 ++ src/Repository/CalendarInstanceRepository.php | 8 +- templates/calendars/index.html.twig | 35 +- translations/messages+intl-icu.en.xlf | 16 + 15 files changed, 1383 insertions(+), 1115 deletions(-) create mode 100644 src/Controller/Admin/AddressBookController.php create mode 100644 src/Controller/Admin/CalendarController.php create mode 100644 src/Controller/Admin/DashboardController.php create mode 100644 src/Controller/Admin/UserController.php delete mode 100644 src/Controller/AdminController.php create mode 100644 src/Plugins/PublicAwareDAVACLPlugin.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3988e8f..5c6798f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,23 +34,15 @@ jobs: docker-php-ext-install gd - name: Install Composer run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet - - name: Cache Composer dependencies - uses: actions/cache@v3 - with: - path: ~/.composer/cache - key: composer-php${{ matrix.php }}-highest- - restore-keys: | - composer-php${{ matrix.php }}-highest- - composer- - name: Validate Composer run: composer validate - - name: Install highest dependencies with Composer - run: composer update --no-progress --ansi + - name: Update to highest dependencies with Composer + run: composer update --no-interaction --no-progress --ansi - name: Analyze run: PHP_CS_FIXER_IGNORE_ENV=True vendor/bin/php-cs-fixer fix --ansi phpunit: - name: PHPUnit (PHP ${{ matrix.php }} Deps ${{ matrix.dependencies }}) + name: PHPUnit (PHP ${{ matrix.php }}) runs-on: ubuntu-latest container: image: php:${{ matrix.php }}-alpine @@ -78,9 +70,6 @@ jobs: - '8.0' - '8.1' - '8.2' - dependencies: - - lowest - - highest fail-fast: false steps: - name: Checkout @@ -93,21 +82,9 @@ jobs: docker-php-ext-install pdo pdo_mysql intl gd - name: Install Composer run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet - - name: Cache Composer dependencies - uses: actions/cache@v3 - with: - path: ~/.composer/cache - key: composer-php${{ matrix.php }}-${{ matrix.dependencies }}- - restore-keys: | - composer-php${{ matrix.php }}-${{ matrix.dependencies }}- - composer- - - name: Install lowest dependencies with Composer - if: matrix.dependencies == 'lowest' - run: composer update --no-progress --prefer-stable --prefer-lowest --ansi - - name: Install highest dependencies with Composer - if: matrix.dependencies == 'highest' - run: composer update --no-progress --ansi + - name: Install dependencies with Composer + run: composer install --no-progress --no-interaction --ansi - name: Migrate database - run: bin/console doctrine:schema:update --force --no-interaction + run: bin/console doctrine:schema:update --force --no-interaction --complete - name: Run tests with PHPUnit run: vendor/bin/phpunit --process-isolation --colors=always diff --git a/composer.json b/composer.json index d4919c2..ee60b65 100644 --- a/composer.json +++ b/composer.json @@ -6,8 +6,8 @@ "require": { "php": "^8.0", "ext-ctype": "*", - "ext-iconv": "*", "ext-gd": "*", + "ext-iconv": "*", "composer-runtime-api": "^2", "dantsu/php-osm-static-api": "^0.5.0", "doctrine/annotations": "^1.12", @@ -32,6 +32,7 @@ "symfony/property-access": "^5.4.21", "symfony/property-info": "^5.4.21", "symfony/proxy-manager-bridge": "^5.4.21", + "symfony/runtime": "^5.4.21", "symfony/security-bundle": "^5.4.21", "symfony/security-guard": "^5.4.21", "symfony/serializer": "^5.4.21", @@ -62,7 +63,8 @@ }, "allow-plugins": { "composer/package-versions-deprecated": true, - "symfony/flex": true + "symfony/flex": true, + "symfony/runtime": true } }, "autoload": { diff --git a/composer.lock b/composer.lock index cc7a5c8..8a8c00c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4488196cf8fd3a00876821a7c0aef89b", + "content-hash": "6bd9076f16158bb7cf3026aed78a87f7", "packages": [ { "name": "dantsu/php-image-editor", - "version": "1.4.1", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/DantSu/php-image-editor.git", - "reference": "3438934f9d3af6fc662dae94d8bceaf76fa5f14a" + "reference": "59a3e922000767051d380a2c530c61344f1e79ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DantSu/php-image-editor/zipball/3438934f9d3af6fc662dae94d8bceaf76fa5f14a", - "reference": "3438934f9d3af6fc662dae94d8bceaf76fa5f14a", + "url": "https://api.github.com/repos/DantSu/php-image-editor/zipball/59a3e922000767051d380a2c530c61344f1e79ec", + "reference": "59a3e922000767051d380a2c530c61344f1e79ec", "shasum": "" }, "require": { @@ -55,7 +55,7 @@ ], "support": { "issues": "https://github.com/DantSu/php-image-editor/issues", - "source": "https://github.com/DantSu/php-image-editor/tree/1.4.1" + "source": "https://github.com/DantSu/php-image-editor/tree/1.4.5" }, "funding": [ { @@ -63,7 +63,7 @@ "type": "github" } ], - "time": "2023-05-25T13:55:50+00:00" + "time": "2023-08-23T07:36:11+00:00" }, { "name": "dantsu/php-osm-static-api", @@ -456,16 +456,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.4", + "version": "3.6.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", "shasum": "" }, "require": { @@ -480,11 +480,12 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.14", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.29", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.7", + "phpunit/phpunit": "9.6.9", "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", @@ -548,7 +549,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.4" + "source": "https://github.com/doctrine/dbal/tree/3.6.6" }, "funding": [ { @@ -564,7 +565,7 @@ "type": "tidelift" } ], - "time": "2023-06-15T07:40:12+00:00" + "time": "2023-08-17T05:38:17+00:00" }, { "name": "doctrine/deprecations", @@ -615,16 +616,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.10.0", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "b2ec6c2668f6dc514e8bf51257d19c7c19398afe" + "reference": "f28b1f78de3a2938ff05cfe751233097624cc756" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b2ec6c2668f6dc514e8bf51257d19c7c19398afe", - "reference": "b2ec6c2668f6dc514e8bf51257d19c7c19398afe", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/f28b1f78de3a2938ff05cfe751233097624cc756", + "reference": "f28b1f78de3a2938ff05cfe751233097624cc756", "shasum": "" }, "require": { @@ -711,7 +712,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.10.0" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.10.2" }, "funding": [ { @@ -727,7 +728,7 @@ "type": "tidelift" } ], - "time": "2023-06-05T14:43:41+00:00" + "time": "2023-08-06T09:31:40+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -1249,16 +1250,16 @@ }, { "name": "doctrine/orm", - "version": "2.15.3", + "version": "2.16.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5" + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/4c3bd208018c26498e5f682aaad45fa00ea307d5", - "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5", + "url": "https://api.github.com/repos/doctrine/orm/zipball/17500f56eaa930f5cd14d765bc2cd851c7d37cc0", + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0", "shasum": "" }, "require": { @@ -1287,14 +1288,14 @@ "doctrine/annotations": "^1.13 || ^2", "doctrine/coding-standard": "^9.0.2 || ^12.0", "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.18", + "phpstan/phpstan": "~1.4.10 || 1.10.28", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.12.0" + "vimeo/psalm": "4.30.0 || 5.14.1" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", @@ -1344,9 +1345,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.15.3" + "source": "https://github.com/doctrine/orm/tree/2.16.2" }, - "time": "2023-06-22T12:36:06+00:00" + "time": "2023-08-27T18:21:56+00:00" }, { "name": "doctrine/persistence", @@ -1927,16 +1928,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d" + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d", - "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", "shasum": "" }, "require": { @@ -1979,22 +1980,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" }, - "time": "2023-05-30T18:13:47+00:00" + "time": "2023-08-12T11:01:26+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.22.0", + "version": "1.23.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c" + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", - "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", "shasum": "" }, "require": { @@ -2026,9 +2027,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" }, - "time": "2023-06-01T12:35:21+00:00" + "time": "2023-08-03T16:32:59+00:00" }, { "name": "psr/cache", @@ -2434,16 +2435,16 @@ }, { "name": "sabre/http", - "version": "5.1.6", + "version": "5.1.10", "source": { "type": "git", "url": "https://github.com/sabre-io/http.git", - "reference": "9976ac34ced206bd6579b7b37b401de9fac98dae" + "reference": "f9f3d1fba8916fa2f4ec25636c4fedc26cb94e02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/http/zipball/9976ac34ced206bd6579b7b37b401de9fac98dae", - "reference": "9976ac34ced206bd6579b7b37b401de9fac98dae", + "url": "https://api.github.com/repos/sabre-io/http/zipball/f9f3d1fba8916fa2f4ec25636c4fedc26cb94e02", + "reference": "f9f3d1fba8916fa2f4ec25636c4fedc26cb94e02", "shasum": "" }, "require": { @@ -2493,7 +2494,7 @@ "issues": "https://github.com/sabre-io/http/issues", "source": "https://github.com/fruux/sabre-http" }, - "time": "2022-07-15T14:51:14+00:00" + "time": "2023-08-18T01:55:28+00:00" }, { "name": "sabre/uri", @@ -2661,16 +2662,16 @@ }, { "name": "sabre/xml", - "version": "2.2.5", + "version": "2.2.6", "source": { "type": "git", "url": "https://github.com/sabre-io/xml.git", - "reference": "a6af111850e7536d200d9637c34885cd3c77a86c" + "reference": "9cde7cdab1e50893cc83b037b40cd47bfde42a2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/xml/zipball/a6af111850e7536d200d9637c34885cd3c77a86c", - "reference": "a6af111850e7536d200d9637c34885cd3c77a86c", + "url": "https://api.github.com/repos/sabre-io/xml/zipball/9cde7cdab1e50893cc83b037b40cd47bfde42a2b", + "reference": "9cde7cdab1e50893cc83b037b40cd47bfde42a2b", "shasum": "" }, "require": { @@ -2726,7 +2727,7 @@ "issues": "https://github.com/sabre-io/xml/issues", "source": "https://github.com/fruux/sabre-xml" }, - "time": "2021-11-04T06:37:27+00:00" + "time": "2023-06-28T12:56:05+00:00" }, { "name": "symfony/apache-pack", @@ -2830,16 +2831,16 @@ }, { "name": "symfony/cache", - "version": "v5.4.23", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "983c79ff28612cdfd66d8e44e1a06e5afc87e107" + "reference": "62b7ae3bccc5b474a30fadc7ef6bbc362007d3f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/983c79ff28612cdfd66d8e44e1a06e5afc87e107", - "reference": "983c79ff28612cdfd66d8e44e1a06e5afc87e107", + "url": "https://api.github.com/repos/symfony/cache/zipball/62b7ae3bccc5b474a30fadc7ef6bbc362007d3f9", + "reference": "62b7ae3bccc5b474a30fadc7ef6bbc362007d3f9", "shasum": "" }, "require": { @@ -2907,7 +2908,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.23" + "source": "https://github.com/symfony/cache/tree/v5.4.28" }, "funding": [ { @@ -2923,7 +2924,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:38:51+00:00" + "time": "2023-08-05T08:32:42+00:00" }, { "name": "symfony/cache-contracts", @@ -3006,16 +3007,16 @@ }, { "name": "symfony/config", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4" + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2a6b1111d038adfa15d52c0871e540f3b352d1e4", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4", + "url": "https://api.github.com/repos/symfony/config/zipball/8109892f27beed9252bd1f1c1880aeb4ad842650", + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650", "shasum": "" }, "require": { @@ -3065,7 +3066,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.21" + "source": "https://github.com/symfony/config/tree/v5.4.26" }, "funding": [ { @@ -3081,20 +3082,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-19T20:21:11+00:00" }, { "name": "symfony/console", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8" + "reference": "f4f71842f24c2023b91237c72a365306f3c58827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", - "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", + "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", + "reference": "f4f71842f24c2023b91237c72a365306f3c58827", "shasum": "" }, "require": { @@ -3164,7 +3165,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.24" + "source": "https://github.com/symfony/console/tree/v5.4.28" }, "funding": [ { @@ -3180,20 +3181,20 @@ "type": "tidelift" } ], - "time": "2023-05-26T05:13:16+00:00" + "time": "2023-08-07T06:12:30+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "4645e032d0963fb614969398ca28e47605b1a7da" + "reference": "addc22fed594f9ce04e73ef6a9d3e2416f77192d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4645e032d0963fb614969398ca28e47605b1a7da", - "reference": "4645e032d0963fb614969398ca28e47605b1a7da", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/addc22fed594f9ce04e73ef6a9d3e2416f77192d", + "reference": "addc22fed594f9ce04e73ef6a9d3e2416f77192d", "shasum": "" }, "require": { @@ -3253,7 +3254,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.24" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.28" }, "funding": [ { @@ -3269,7 +3270,7 @@ "type": "tidelift" } ], - "time": "2023-05-05T14:42:55+00:00" + "time": "2023-08-14T10:47:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3340,16 +3341,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf" + "reference": "70780f364af653951da5f82caea2e83407d890d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf", - "reference": "1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/70780f364af653951da5f82caea2e83407d890d0", + "reference": "70780f364af653951da5f82caea2e83407d890d0", "shasum": "" }, "require": { @@ -3375,7 +3376,7 @@ "symfony/proxy-manager-bridge": "<4.4.19", "symfony/security-bundle": "<5", "symfony/security-core": "<5.3", - "symfony/validator": "<5.2" + "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1" }, "require-dev": { "doctrine/annotations": "^1.10.4|^2", @@ -3399,7 +3400,7 @@ "symfony/stopwatch": "^4.4|^5.0|^6.0", "symfony/translation": "^4.4|^5.0|^6.0", "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^5.2|^6.0", + "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { @@ -3436,7 +3437,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v5.4.24" + "source": "https://github.com/symfony/doctrine-bridge/tree/v5.4.28" }, "funding": [ { @@ -3452,7 +3453,7 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-08-08T10:04:45+00:00" }, { "name": "symfony/dotenv", @@ -3527,16 +3528,16 @@ }, { "name": "symfony/error-handler", - "version": "v5.4.24", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946" + "reference": "b26719213a39c9ba57520cbc5e52bfcc5e8d92f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946", - "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/b26719213a39c9ba57520cbc5e52bfcc5e8d92f9", + "reference": "b26719213a39c9ba57520cbc5e52bfcc5e8d92f9", "shasum": "" }, "require": { @@ -3578,7 +3579,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.24" + "source": "https://github.com/symfony/error-handler/tree/v5.4.26" }, "funding": [ { @@ -3594,20 +3595,20 @@ "type": "tidelift" } ], - "time": "2023-05-02T16:13:31+00:00" + "time": "2023-07-16T16:48:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" + "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac", + "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac", "shasum": "" }, "require": { @@ -3663,7 +3664,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26" }, "funding": [ { @@ -3679,7 +3680,7 @@ "type": "tidelift" } ], - "time": "2023-03-17T11:31:58+00:00" + "time": "2023-07-06T06:34:20+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3825,16 +3826,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.23", + "version": "v5.4.25", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5" + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", "shasum": "" }, "require": { @@ -3869,7 +3870,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.23" + "source": "https://github.com/symfony/filesystem/tree/v5.4.25" }, "funding": [ { @@ -3885,20 +3886,20 @@ "type": "tidelift" } ], - "time": "2023-03-02T11:38:35+00:00" + "time": "2023-05-31T13:04:02+00:00" }, { "name": "symfony/finder", - "version": "v5.4.21", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19" + "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/078e9a5e1871fcfe6a5ce421b539344c21afef19", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19", + "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d", + "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d", "shasum": "" }, "require": { @@ -3932,7 +3933,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.21" + "source": "https://github.com/symfony/finder/tree/v5.4.27" }, "funding": [ { @@ -3948,20 +3949,20 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2023-07-31T08:02:31+00:00" }, { "name": "symfony/flex", - "version": "v1.20.0", + "version": "v1.20.2", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "49059a10127ac8270957e116a2251ae535d202ac" + "reference": "a2554c7e1b669f5049c1b67bc56f13aa1c4bf7da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/49059a10127ac8270957e116a2251ae535d202ac", - "reference": "49059a10127ac8270957e116a2251ae535d202ac", + "url": "https://api.github.com/repos/symfony/flex/zipball/a2554c7e1b669f5049c1b67bc56f13aa1c4bf7da", + "reference": "a2554c7e1b669f5049c1b67bc56f13aa1c4bf7da", "shasum": "" }, "require": { @@ -3997,7 +3998,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v1.20.0" + "source": "https://github.com/symfony/flex/tree/v1.20.2" }, "funding": [ { @@ -4013,20 +4014,20 @@ "type": "tidelift" } ], - "time": "2023-05-26T16:25:26+00:00" + "time": "2023-08-04T09:02:01+00:00" }, { "name": "symfony/form", - "version": "v5.4.24", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "813b79a34ab9843b5a01a6f809f1e4a009aaea2e" + "reference": "ca99a6874695aa266044a28ff945176cf480d9bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/813b79a34ab9843b5a01a6f809f1e4a009aaea2e", - "reference": "813b79a34ab9843b5a01a6f809f1e4a009aaea2e", + "url": "https://api.github.com/repos/symfony/form/zipball/ca99a6874695aa266044a28ff945176cf480d9bf", + "reference": "ca99a6874695aa266044a28ff945176cf480d9bf", "shasum": "" }, "require": { @@ -4099,7 +4100,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v5.4.24" + "source": "https://github.com/symfony/form/tree/v5.4.26" }, "funding": [ { @@ -4115,20 +4116,20 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-07-26T07:54:04+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "c06a56a47817d29318aaace1c655cbde16c998e8" + "reference": "b84ebb25405c7334976b5791bfbbe0e50f4e472c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c06a56a47817d29318aaace1c655cbde16c998e8", - "reference": "c06a56a47817d29318aaace1c655cbde16c998e8", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b84ebb25405c7334976b5791bfbbe0e50f4e472c", + "reference": "b84ebb25405c7334976b5791bfbbe0e50f4e472c", "shasum": "" }, "require": { @@ -4174,7 +4175,7 @@ "symfony/translation": "<5.3", "symfony/twig-bridge": "<4.4", "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", + "symfony/validator": "<5.3.11", "symfony/web-profiler-bundle": "<4.4", "symfony/workflow": "<5.2" }, @@ -4207,7 +4208,7 @@ "symfony/string": "^5.0|^6.0", "symfony/translation": "^5.3|^6.0", "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0", + "symfony/validator": "^5.3.11|^6.0", "symfony/web-link": "^4.4|^5.0|^6.0", "symfony/workflow": "^5.2|^6.0", "symfony/yaml": "^4.4|^5.0|^6.0", @@ -4249,7 +4250,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.4.24" + "source": "https://github.com/symfony/framework-bundle/tree/v5.4.28" }, "funding": [ { @@ -4265,20 +4266,20 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-08-08T11:21:07+00:00" }, { "name": "symfony/http-client", - "version": "v5.4.24", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "9e89ac4c9dfe29f4ed2b10a36e62720286632ad6" + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/9e89ac4c9dfe29f4ed2b10a36e62720286632ad6", - "reference": "9e89ac4c9dfe29f4ed2b10a36e62720286632ad6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/19d48ef7f38e5057ed1789a503cd3eccef039bce", + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce", "shasum": "" }, "require": { @@ -4340,7 +4341,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v5.4.24" + "source": "https://github.com/symfony/http-client/tree/v5.4.26" }, "funding": [ { @@ -4356,7 +4357,7 @@ "type": "tidelift" } ], - "time": "2023-05-07T13:11:28+00:00" + "time": "2023-07-03T12:14:50+00:00" }, { "name": "symfony/http-client-contracts", @@ -4438,16 +4439,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5" + "reference": "365992c83a836dfe635f1e903ccca43ee03d3dd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3c59f97f6249ce552a44f01b93bfcbd786a954f5", - "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/365992c83a836dfe635f1e903ccca43ee03d3dd2", + "reference": "365992c83a836dfe635f1e903ccca43ee03d3dd2", "shasum": "" }, "require": { @@ -4494,7 +4495,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.24" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.28" }, "funding": [ { @@ -4510,20 +4511,20 @@ "type": "tidelift" } ], - "time": "2023-05-19T07:21:23+00:00" + "time": "2023-08-21T07:23:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1" + "reference": "127a2322ca1828157901092518b8ea8e4e1109d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f38b722e1557eb3f487d351b48f5a1279b50e9d1", - "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/127a2322ca1828157901092518b8ea8e4e1109d4", + "reference": "127a2322ca1828157901092518b8ea8e4e1109d4", "shasum": "" }, "require": { @@ -4606,7 +4607,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.24" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.28" }, "funding": [ { @@ -4622,20 +4623,20 @@ "type": "tidelift" } ], - "time": "2023-05-27T08:06:30+00:00" + "time": "2023-08-26T13:47:51+00:00" }, { "name": "symfony/intl", - "version": "v5.4.23", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "962789bbc76c82c266623321ffc24416f574b636" + "reference": "c26c40b64ecdc056810e294ea67ac5b34182cd69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/962789bbc76c82c266623321ffc24416f574b636", - "reference": "962789bbc76c82c266623321ffc24416f574b636", + "url": "https://api.github.com/repos/symfony/intl/zipball/c26c40b64ecdc056810e294ea67ac5b34182cd69", + "reference": "c26c40b64ecdc056810e294ea67ac5b34182cd69", "shasum": "" }, "require": { @@ -4644,7 +4645,8 @@ "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/filesystem": "^4.4|^5.0|^6.0" + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -4694,7 +4696,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v5.4.23" + "source": "https://github.com/symfony/intl/tree/v5.4.26" }, "funding": [ { @@ -4710,7 +4712,7 @@ "type": "tidelift" } ], - "time": "2023-04-13T10:36:25+00:00" + "time": "2023-07-13T09:02:54+00:00" }, { "name": "symfony/mailer", @@ -4790,16 +4792,16 @@ }, { "name": "symfony/mime", - "version": "v5.4.23", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ae0a1032a450a3abf305ee44fc55ed423fbf16e3" + "reference": "2ea06dfeee20000a319d8407cea1d47533d5a9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ae0a1032a450a3abf305ee44fc55ed423fbf16e3", - "reference": "ae0a1032a450a3abf305ee44fc55ed423fbf16e3", + "url": "https://api.github.com/repos/symfony/mime/zipball/2ea06dfeee20000a319d8407cea1d47533d5a9d2", + "reference": "2ea06dfeee20000a319d8407cea1d47533d5a9d2", "shasum": "" }, "require": { @@ -4814,7 +4816,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<4.4", - "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" + "symfony/serializer": "<5.4.26|>=6,<6.2.13|>=6.3,<6.3.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", @@ -4822,7 +4824,7 @@ "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/property-access": "^4.4|^5.1|^6.0", "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" + "symfony/serializer": "^5.4.26|~6.2.13|^6.3.2" }, "type": "library", "autoload": { @@ -4854,7 +4856,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.23" + "source": "https://github.com/symfony/mime/tree/v5.4.26" }, "funding": [ { @@ -4870,7 +4872,7 @@ "type": "tidelift" } ], - "time": "2023-04-19T09:49:13+00:00" + "time": "2023-07-27T06:29:31+00:00" }, { "name": "symfony/monolog-bridge", @@ -5108,20 +5110,21 @@ }, { "name": "symfony/password-hasher", - "version": "v5.4.21", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "7ce4529b2b2ea7de3b6f344a1a41f58201999180" + "reference": "6dd2daf41b44384752f6b59e8ad3e56ffb81e35c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/7ce4529b2b2ea7de3b6f344a1a41f58201999180", - "reference": "7ce4529b2b2ea7de3b6f344a1a41f58201999180", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/6dd2daf41b44384752f6b59e8ad3e56ffb81e35c", + "reference": "6dd2daf41b44384752f6b59e8ad3e56ffb81e35c", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.15" }, "conflict": { @@ -5161,7 +5164,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v5.4.21" + "source": "https://github.com/symfony/password-hasher/tree/v5.4.27" }, "funding": [ { @@ -5177,20 +5180,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-28T14:44:35+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -5202,7 +5205,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5242,7 +5245,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -5258,20 +5261,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c" + "reference": "e46b4da57951a16053cd751f63f4a24292788157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/a3d9148e2c363588e05abbdd4ee4f971f0a5330c", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e46b4da57951a16053cd751f63f4a24292788157", + "reference": "e46b4da57951a16053cd751f63f4a24292788157", "shasum": "" }, "require": { @@ -5283,7 +5286,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5329,7 +5332,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.28.0" }, "funding": [ { @@ -5345,20 +5348,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-03-21T17:27:24+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -5372,7 +5375,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5416,7 +5419,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -5432,20 +5435,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -5457,7 +5460,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5500,7 +5503,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -5516,20 +5519,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -5544,7 +5547,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5583,7 +5586,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -5599,20 +5602,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -5621,7 +5624,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5659,7 +5662,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -5675,20 +5678,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", "shasum": "" }, "require": { @@ -5697,7 +5700,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5738,7 +5741,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" }, "funding": [ { @@ -5754,20 +5757,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -5776,7 +5779,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5821,7 +5824,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -5837,20 +5840,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", "shasum": "" }, "require": { @@ -5859,7 +5862,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5900,7 +5903,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" }, "funding": [ { @@ -5916,20 +5919,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64" + "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e3c46cc5689c8782944274bb30702106ecbe3b64", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64", + "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", "shasum": "" }, "require": { @@ -5962,7 +5965,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.24" + "source": "https://github.com/symfony/process/tree/v5.4.28" }, "funding": [ { @@ -5978,20 +5981,20 @@ "type": "tidelift" } ], - "time": "2023-05-17T11:26:05+00:00" + "time": "2023-08-07T10:36:04+00:00" }, { "name": "symfony/property-access", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "ffee082889586b5718347b291e04071f4d07b38f" + "reference": "0249e46f69e92049a488f39fcf531cb42c50caaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/ffee082889586b5718347b291e04071f4d07b38f", - "reference": "ffee082889586b5718347b291e04071f4d07b38f", + "url": "https://api.github.com/repos/symfony/property-access/zipball/0249e46f69e92049a488f39fcf531cb42c50caaa", + "reference": "0249e46f69e92049a488f39fcf531cb42c50caaa", "shasum": "" }, "require": { @@ -6043,7 +6046,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v5.4.22" + "source": "https://github.com/symfony/property-access/tree/v5.4.26" }, "funding": [ { @@ -6059,7 +6062,7 @@ "type": "tidelift" } ], - "time": "2023-03-14T14:59:20+00:00" + "time": "2023-07-13T15:20:41+00:00" }, { "name": "symfony/property-info", @@ -6221,16 +6224,16 @@ }, { "name": "symfony/routing", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d" + "reference": "853fc7df96befc468692de0a48831b38f04d2cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/c2ac11eb34947999b7c38fb4c835a57306907e6d", - "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d", + "url": "https://api.github.com/repos/symfony/routing/zipball/853fc7df96befc468692de0a48831b38f04d2cb2", + "reference": "853fc7df96befc468692de0a48831b38f04d2cb2", "shasum": "" }, "require": { @@ -6291,7 +6294,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.22" + "source": "https://github.com/symfony/routing/tree/v5.4.26" }, "funding": [ { @@ -6307,20 +6310,100 @@ "type": "tidelift" } ], - "time": "2023-03-14T14:59:20+00:00" + "time": "2023-07-24T13:28:37+00:00" + }, + { + "name": "symfony/runtime", + "version": "v5.4.26", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "4659b552bc9f2380986e3f4b7e2bd4e512470e0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/4659b552bc9f2380986e3f4b7e2bd4e512470e0d", + "reference": "4659b552bc9f2380986e3f4b7e2bd4e512470e0d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dotenv": "<5.1" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "symfony/console": "^4.4.30|^5.4.9|^6.0.9", + "symfony/dotenv": "^5.1|^6.0", + "symfony/http-foundation": "^4.4.30|^5.3.7|^6.0", + "symfony/http-kernel": "^4.4.30|^5.3.7|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v5.4.26" + }, + "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": "2023-07-16T16:48:57+00:00" }, { "name": "symfony/security-bundle", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "36eddff8266126de032ab528417ad13eb43f6cb5" + "reference": "38d674b6150ebd638f7c517b19790ac631f8dc35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/36eddff8266126de032ab528417ad13eb43f6cb5", - "reference": "36eddff8266126de032ab528417ad13eb43f6cb5", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/38d674b6150ebd638f7c517b19790ac631f8dc35", + "reference": "38d674b6150ebd638f7c517b19790ac631f8dc35", "shasum": "" }, "require": { @@ -6393,7 +6476,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v5.4.22" + "source": "https://github.com/symfony/security-bundle/tree/v5.4.26" }, "funding": [ { @@ -6409,7 +6492,7 @@ "type": "tidelift" } ], - "time": "2023-03-10T10:02:45+00:00" + "time": "2023-07-05T15:49:26+00:00" }, { "name": "symfony/security-core", @@ -6506,20 +6589,21 @@ }, { "name": "symfony/security-csrf", - "version": "v5.4.21", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "776a538e5f20fb560a182f790979c71455694203" + "reference": "995fcfcc5a3be09df157b4960668f61cceb86611" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/776a538e5f20fb560a182f790979c71455694203", - "reference": "776a538e5f20fb560a182f790979c71455694203", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/995fcfcc5a3be09df157b4960668f61cceb86611", + "reference": "995fcfcc5a3be09df157b4960668f61cceb86611", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.16", "symfony/security-core": "^4.4|^5.0|^6.0" }, @@ -6558,7 +6642,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v5.4.21" + "source": "https://github.com/symfony/security-csrf/tree/v5.4.27" }, "funding": [ { @@ -6574,24 +6658,25 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2023-07-28T14:44:35+00:00" }, { "name": "symfony/security-guard", - "version": "v5.4.22", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/security-guard.git", - "reference": "62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa" + "reference": "72c53142533462fc6fda4a429c2a21c2b944a8cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa", - "reference": "62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa", + "url": "https://api.github.com/repos/symfony/security-guard/zipball/72c53142533462fc6fda4a429c2a21c2b944a8cc", + "reference": "72c53142533462fc6fda4a429c2a21c2b944a8cc", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.15", "symfony/security-core": "^5.0", "symfony/security-http": "^5.3" @@ -6625,7 +6710,7 @@ "description": "Symfony Security Component - Guard", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-guard/tree/v5.4.22" + "source": "https://github.com/symfony/security-guard/tree/v5.4.27" }, "funding": [ { @@ -6641,20 +6726,20 @@ "type": "tidelift" } ], - "time": "2023-03-06T21:29:33+00:00" + "time": "2023-07-28T14:44:35+00:00" }, { "name": "symfony/security-http", - "version": "v5.4.23", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "6791856229cc605834d169091981e4eae77dad45" + "reference": "7815edb5716e765063469b6b9232d4eaf8c03516" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/6791856229cc605834d169091981e4eae77dad45", - "reference": "6791856229cc605834d169091981e4eae77dad45", + "url": "https://api.github.com/repos/symfony/security-http/zipball/7815edb5716e765063469b6b9232d4eaf8c03516", + "reference": "7815edb5716e765063469b6b9232d4eaf8c03516", "shasum": "" }, "require": { @@ -6710,7 +6795,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v5.4.23" + "source": "https://github.com/symfony/security-http/tree/v5.4.28" }, "funding": [ { @@ -6726,20 +6811,20 @@ "type": "tidelift" } ], - "time": "2023-04-21T11:34:27+00:00" + "time": "2023-08-23T10:00:20+00:00" }, { "name": "symfony/serializer", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "12535bb7b1d3b53802bf18d61a98bb1145fabcdb" + "reference": "701e2b8d48a3a627ffe128b38fbe6c4cf3ddcb3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/12535bb7b1d3b53802bf18d61a98bb1145fabcdb", - "reference": "12535bb7b1d3b53802bf18d61a98bb1145fabcdb", + "url": "https://api.github.com/repos/symfony/serializer/zipball/701e2b8d48a3a627ffe128b38fbe6c4cf3ddcb3c", + "reference": "701e2b8d48a3a627ffe128b38fbe6c4cf3ddcb3c", "shasum": "" }, "require": { @@ -6754,7 +6839,7 @@ "phpdocumentor/type-resolver": "<1.4.0", "symfony/dependency-injection": "<4.4", "symfony/property-access": "<5.4", - "symfony/property-info": "<5.3.13", + "symfony/property-info": "<5.4.24|>=6,<6.2.11", "symfony/uid": "<5.3", "symfony/yaml": "<4.4" }, @@ -6771,7 +6856,7 @@ "symfony/http-kernel": "^4.4|^5.0|^6.0", "symfony/mime": "^4.4|^5.0|^6.0", "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.3.13|^6.0", + "symfony/property-info": "^5.4.24|^6.2.11", "symfony/uid": "^5.3|^6.0", "symfony/validator": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0", @@ -6813,7 +6898,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v5.4.24" + "source": "https://github.com/symfony/serializer/tree/v5.4.28" }, "funding": [ { @@ -6829,7 +6914,7 @@ "type": "tidelift" } ], - "time": "2023-05-12T08:37:35+00:00" + "time": "2023-08-24T14:14:18+00:00" }, { "name": "symfony/service-contracts", @@ -6978,16 +7063,16 @@ }, { "name": "symfony/string", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + "reference": "1181fe9270e373537475e826873b5867b863883c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", + "reference": "1181fe9270e373537475e826873b5867b863883c", "shasum": "" }, "require": { @@ -7044,7 +7129,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.22" + "source": "https://github.com/symfony/string/tree/v5.4.26" }, "funding": [ { @@ -7060,7 +7145,7 @@ "type": "tidelift" } ], - "time": "2023-03-14T06:11:53+00:00" + "time": "2023-06-28T12:46:07+00:00" }, { "name": "symfony/translation", @@ -7239,16 +7324,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "e5b174464f68be6876046db3ad6e217d9a7dbbac" + "reference": "832461a5f556df7933fd82e75b097d76182c640b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e5b174464f68be6876046db3ad6e217d9a7dbbac", - "reference": "e5b174464f68be6876046db3ad6e217d9a7dbbac", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/832461a5f556df7933fd82e75b097d76182c640b", + "reference": "832461a5f556df7933fd82e75b097d76182c640b", "shasum": "" }, "require": { @@ -7340,7 +7425,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.22" + "source": "https://github.com/symfony/twig-bridge/tree/v5.4.26" }, "funding": [ { @@ -7356,25 +7441,26 @@ "type": "tidelift" } ], - "time": "2023-03-31T08:28:44+00:00" + "time": "2023-07-20T16:28:53+00:00" }, { "name": "symfony/twig-bundle", - "version": "v5.4.21", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "875d0edfc8df7505c1993419882c4071fc28c477" + "reference": "a16996ad54d75e220e91a0c7517437ad592eccca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/875d0edfc8df7505c1993419882c4071fc28c477", - "reference": "875d0edfc8df7505c1993419882c4071fc28c477", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/a16996ad54d75e220e91a0c7517437ad592eccca", + "reference": "a16996ad54d75e220e91a0c7517437ad592eccca", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/config": "^4.4|^5.0|^6.0", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/http-foundation": "^4.4|^5.0|^6.0", "symfony/http-kernel": "^5.0|^6.0", "symfony/polyfill-ctype": "~1.8", @@ -7429,7 +7515,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v5.4.21" + "source": "https://github.com/symfony/twig-bundle/tree/v5.4.27" }, "funding": [ { @@ -7445,20 +7531,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-28T14:44:35+00:00" }, { "name": "symfony/validator", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "47794a3cb530e01593ecad9856ba80f5c011e36b" + "reference": "0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/47794a3cb530e01593ecad9856ba80f5c011e36b", - "reference": "47794a3cb530e01593ecad9856ba80f5c011e36b", + "url": "https://api.github.com/repos/symfony/validator/zipball/0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9", + "reference": "0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9", "shasum": "" }, "require": { @@ -7541,7 +7627,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v5.4.24" + "source": "https://github.com/symfony/validator/tree/v5.4.28" }, "funding": [ { @@ -7557,20 +7643,20 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-08-14T13:04:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.24", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3" + "reference": "684b36ff415e1381d4a943c3ca2502cd2debad73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/8e12706bf9c68a2da633f23bfdc15b4dce5970b3", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/684b36ff415e1381d4a943c3ca2502cd2debad73", + "reference": "684b36ff415e1381d4a943c3ca2502cd2debad73", "shasum": "" }, "require": { @@ -7584,6 +7670,7 @@ "require-dev": { "ext-iconv": "*", "symfony/console": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", "symfony/process": "^4.4|^5.0|^6.0", "symfony/uid": "^5.1|^6.0", "twig/twig": "^2.13|^3.0.4" @@ -7629,7 +7716,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.24" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.28" }, "funding": [ { @@ -7645,7 +7732,7 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-08-24T13:38:36+00:00" }, { "name": "symfony/var-exporter", @@ -7883,16 +7970,16 @@ }, { "name": "twig/twig", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "shasum": "" }, "require": { @@ -7902,7 +7989,7 @@ }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4.9|^6.3" }, "type": "library", "autoload": { @@ -7938,7 +8025,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.6.1" + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" }, "funding": [ { @@ -7950,7 +8037,7 @@ "type": "tidelift" } ], - "time": "2023-06-08T12:52:13+00:00" + "time": "2023-08-28T11:09:02+00:00" }, { "name": "webmozart/assert", @@ -8085,16 +8172,16 @@ }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { @@ -8144,9 +8231,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { @@ -8162,7 +8249,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/xdebug-handler", @@ -8232,27 +8319,25 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.14.2", + "version": "v3.25.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "14f0541651841b63640e7aafad041ad55dc7aa88" + "reference": "9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/14f0541651841b63640e7aafad041ad55dc7aa88", - "reference": "14f0541651841b63640e7aafad041ad55dc7aa88", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d", + "reference": "9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d", "shasum": "" }, "require": { "composer/semver": "^3.3", "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.14.2 || ^2", - "doctrine/lexer": "^2", "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0", + "sebastian/diff": "^4.0 || ^5.0", "symfony/console": "^5.4 || ^6.0", "symfony/event-dispatcher": "^5.4 || ^6.0", "symfony/filesystem": "^5.4 || ^6.0", @@ -8265,6 +8350,7 @@ "symfony/stopwatch": "^5.4 || ^6.0" }, "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", "justinrainbow/json-schema": "^5.2", "keradus/cli-executor": "^2.0", "mikey179/vfsstream": "^1.6.11", @@ -8308,9 +8394,15 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.25.0" }, "funding": [ { @@ -8318,7 +8410,7 @@ "type": "github" } ], - "time": "2023-01-29T23:47:01+00:00" + "time": "2023-08-31T21:27:18+00:00" }, { "name": "myclabs/deep-copy", @@ -8381,16 +8473,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.5", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -8431,9 +8523,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-05-19T20:20:00+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "phar-io/manifest", @@ -8548,16 +8640,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -8613,7 +8705,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -8621,7 +8714,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8866,16 +8959,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.9", + "version": "9.6.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a9aceaf20a682aeacf28d582654a1670d8826778" + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778", - "reference": "a9aceaf20a682aeacf28d582654a1670d8826778", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", "shasum": "" }, "require": { @@ -8949,7 +9042,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" }, "funding": [ { @@ -8965,7 +9058,7 @@ "type": "tidelift" } ], - "time": "2023-06-11T06:13:56+00:00" + "time": "2023-08-19T07:10:56+00:00" }, { "name": "sebastian/cli-parser", @@ -9473,16 +9566,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -9525,7 +9618,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -9533,7 +9626,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -10005,16 +10098,16 @@ }, { "name": "symfony/css-selector", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "95f3c7468db1da8cc360b24fa2a26e7cefcb355d" + "reference": "0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/95f3c7468db1da8cc360b24fa2a26e7cefcb355d", - "reference": "95f3c7468db1da8cc360b24fa2a26e7cefcb355d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a", + "reference": "0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a", "shasum": "" }, "require": { @@ -10051,7 +10144,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.21" + "source": "https://github.com/symfony/css-selector/tree/v5.4.26" }, "funding": [ { @@ -10067,20 +10160,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-07T06:10:25+00:00" }, { "name": "symfony/debug-bundle", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "8b4360bf8ce9a917ef8796c5e6065a185d8722bd" + "reference": "17c372891d4554d5d2f5cf602aef02c859ad52d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/8b4360bf8ce9a917ef8796c5e6065a185d8722bd", - "reference": "8b4360bf8ce9a917ef8796c5e6065a185d8722bd", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/17c372891d4554d5d2f5cf602aef02c859ad52d8", + "reference": "17c372891d4554d5d2f5cf602aef02c859ad52d8", "shasum": "" }, "require": { @@ -10130,7 +10223,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v5.4.21" + "source": "https://github.com/symfony/debug-bundle/tree/v5.4.26" }, "funding": [ { @@ -10146,20 +10239,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-11T21:42:03+00:00" }, { "name": "symfony/dom-crawler", - "version": "v5.4.23", + "version": "v5.4.25", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08" + "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08", - "reference": "4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d2aefa5a7acc5511422792931d14d1be96fe9fea", + "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea", "shasum": "" }, "require": { @@ -10205,7 +10298,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.23" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.25" }, "funding": [ { @@ -10221,20 +10314,20 @@ "type": "tidelift" } ], - "time": "2023-04-08T21:20:19+00:00" + "time": "2023-06-05T08:05:41+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.49.0", + "version": "v1.50.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "ce1d424f76bbb377f1956cc7641e8e2eafe81cde" + "reference": "a1733f849b999460c308e66f6392fb09b621fa86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/ce1d424f76bbb377f1956cc7641e8e2eafe81cde", - "reference": "ce1d424f76bbb377f1956cc7641e8e2eafe81cde", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a1733f849b999460c308e66f6392fb09b621fa86", + "reference": "a1733f849b999460c308e66f6392fb09b621fa86", "shasum": "" }, "require": { @@ -10292,13 +10385,14 @@ "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", "keywords": [ "code generator", + "dev", "generator", "scaffold", "scaffolding" ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.49.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.50.0" }, "funding": [ { @@ -10314,20 +10408,20 @@ "type": "tidelift" } ], - "time": "2023-06-07T13:10:14+00:00" + "time": "2023-07-10T18:21:57+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v5.4.23", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba" + "reference": "d04639b395e25efa4260fc5b12a9fa1eafb38a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/1572c5b7cad812bdf0414d89a32a33a2dafb38ba", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/d04639b395e25efa4260fc5b12a9fa1eafb38a64", + "reference": "d04639b395e25efa4260fc5b12a9fa1eafb38a64", "shasum": "" }, "require": { @@ -10381,7 +10475,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v5.4.23" + "source": "https://github.com/symfony/phpunit-bridge/tree/v5.4.26" }, "funding": [ { @@ -10397,20 +10491,20 @@ "type": "tidelift" } ], - "time": "2023-04-18T09:42:03+00:00" + "time": "2023-07-12T15:44:31+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v5.4.24", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "42dbb751c0363d75a3697775e662d6f21f3d8b83" + "reference": "a08572ac2e4aea7ed85065524bb4fe3ace6c81c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/42dbb751c0363d75a3697775e662d6f21f3d8b83", - "reference": "42dbb751c0363d75a3697775e662d6f21f3d8b83", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/a08572ac2e4aea7ed85065524bb4fe3ace6c81c3", + "reference": "a08572ac2e4aea7ed85065524bb4fe3ace6c81c3", "shasum": "" }, "require": { @@ -10461,7 +10555,7 @@ "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.24" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.26" }, "funding": [ { @@ -10477,7 +10571,7 @@ "type": "tidelift" } ], - "time": "2023-05-02T16:38:36+00:00" + "time": "2023-07-19T19:34:05+00:00" }, { "name": "theseer/tokenizer", @@ -10538,13 +10632,13 @@ "platform": { "php": "^8.0", "ext-ctype": "*", - "ext-iconv": "*", "ext-gd": "*", + "ext-iconv": "*", "composer-runtime-api": "^2" }, "platform-dev": [], "platform-overrides": { "php": "8.0.28" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Controller/Admin/AddressBookController.php b/src/Controller/Admin/AddressBookController.php new file mode 100644 index 0000000..87e6ee7 --- /dev/null +++ b/src/Controller/Admin/AddressBookController.php @@ -0,0 +1,101 @@ +getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + $addressbooks = $doctrine->getRepository(AddressBook::class)->findByPrincipalUri(Principal::PREFIX.$username); + + return $this->render('addressbooks/index.html.twig', [ + 'addressbooks' => $addressbooks, + 'principal' => $principal, + 'username' => $username, + ]); + } + + /** + * @Route("/adressbooks/{username}/new", name="addressbook_create") + * @Route("/adressbooks/{username}/edit/{id}", name="addressbook_edit", requirements={"id":"\d+"}) + */ + public function addressbookCreate(ManagerRegistry $doctrine, Request $request, string $username, ?int $id, TranslatorInterface $trans) + { + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + + if (!$principal) { + throw $this->createNotFoundException('User not found'); + } + + if ($id) { + $addressbook = $doctrine->getRepository(AddressBook::class)->findOneById($id); + if (!$addressbook) { + throw $this->createNotFoundException('Address book not found'); + } + } else { + $addressbook = new AddressBook(); + } + + $form = $this->createForm(AddressBookType::class, $addressbook, ['new' => !$id]); + + $form->get('principalUri')->setData(Principal::PREFIX.$username); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager = $doctrine->getManager(); + + $entityManager->persist($addressbook); + $entityManager->flush(); + + $this->addFlash('success', $trans->trans('addressbooks.saved')); + + return $this->redirectToRoute('address_books', ['username' => $username]); + } + + return $this->render('addressbooks/edit.html.twig', [ + 'form' => $form->createView(), + 'principal' => $principal, + 'username' => $username, + 'addressbook' => $addressbook, + ]); + } + + /** + * @Route("/addressbooks/{username}/delete/{id}", name="addressbook_delete", requirements={"id":"\d+"}) + */ + public function addressbookDelete(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) + { + $addressbook = $doctrine->getRepository(AddressBook::class)->findOneById($id); + if (!$addressbook) { + throw $this->createNotFoundException('Address Book not found'); + } + + $entityManager = $doctrine->getManager(); + + foreach ($addressbook->getCards() ?? [] as $card) { + $entityManager->remove($card); + } + foreach ($addressbook->getChanges() ?? [] as $change) { + $entityManager->remove($change); + } + $entityManager->remove($addressbook); + + $entityManager->flush(); + $this->addFlash('success', $trans->trans('addressbooks.deleted')); + + return $this->redirectToRoute('address_books', ['username' => $username]); + } +} diff --git a/src/Controller/Admin/CalendarController.php b/src/Controller/Admin/CalendarController.php new file mode 100644 index 0000000..f88cf54 --- /dev/null +++ b/src/Controller/Admin/CalendarController.php @@ -0,0 +1,281 @@ +getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + $allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri(Principal::PREFIX.$username); + + // Separate shared calendars + $calendars = []; + $shared = []; + foreach ($allCalendars as $calendar) { + if (!$calendar->isShared()) { + $calendars[] = [ + 'entity' => $calendar, + 'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL), + ]; + } else { + $shared[] = [ + 'entity' => $calendar, + 'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL), + ]; + } + } + + // We need all the other users so we can propose to share calendars with them + $allPrincipalsExcept = $doctrine->getRepository(Principal::class)->findAllExceptPrincipal(Principal::PREFIX.$username); + + return $this->render('calendars/index.html.twig', [ + 'calendars' => $calendars, + 'shared' => $shared, + 'principal' => $principal, + 'username' => $username, + 'allPrincipals' => $allPrincipalsExcept, + ]); + } + + /** + * @Route("/calendars/{username}/new", name="calendar_create") + * @Route("/calendars/{username}/edit/{id}", name="calendar_edit", requirements={"id":"\d+"}) + */ + public function calendarEdit(ManagerRegistry $doctrine, Request $request, string $username, ?int $id, TranslatorInterface $trans) + { + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + + if (!$principal) { + throw $this->createNotFoundException('User not found'); + } + + if ($id) { + $calendarInstance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); + if (!$calendarInstance) { + throw $this->createNotFoundException('Calendar not found'); + } + } else { + $calendarInstance = new CalendarInstance(); + $calendar = new Calendar(); + $calendarInstance->setCalendar($calendar); + } + + $form = $this->createForm(CalendarInstanceType::class, $calendarInstance, [ + 'new' => !$id, + 'shared' => $calendarInstance->isShared(), + ]); + + $components = explode(',', $calendarInstance->getCalendar()->getComponents()); + + $form->get('events')->setData(in_array(Calendar::COMPONENT_EVENTS, $components)); + $form->get('todos')->setData(in_array(Calendar::COMPONENT_TODOS, $components)); + $form->get('notes')->setData(in_array(Calendar::COMPONENT_NOTES, $components)); + $form->get('public')->setData($calendarInstance->isPublic()); + $form->get('principalUri')->setData(Principal::PREFIX.$username); + + $form->handleRequest($request); + + $entityManager = $doctrine->getManager(); + + if ($form->isSubmitted() && $form->isValid()) { + // Only owners can change those + if (!$calendarInstance->isShared()) { + $components = []; + if ($form->get('events')->getData()) { + $components[] = Calendar::COMPONENT_EVENTS; + } + if ($form->get('todos')->getData()) { + $components[] = Calendar::COMPONENT_TODOS; + } + if ($form->get('notes')->getData()) { + $components[] = Calendar::COMPONENT_NOTES; + } + if (true === $form->get('public')->getData()) { + $calendarInstance->setAccess(CalendarInstance::ACCESS_PUBLIC); + } else { + $calendarInstance->setAccess(CalendarInstance::ACCESS_SHAREDOWNER); + } + + $calendarInstance->getCalendar()->setComponents(implode(',', $components)); + } + + // We want to remove all shares if a calendar goes public + if (true === $form->get('public')->getData() && $id) { + $calendarId = $calendarInstance->getCalendar()->getId(); + $instances = $doctrine->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($calendarId, false); + foreach ($instances as $instance) { + $entityManager->remove($instance); + } + } + + $entityManager->persist($calendarInstance); + $entityManager->flush(); + + $this->addFlash('success', $trans->trans('calendar.saved')); + + return $this->redirectToRoute('calendars', ['username' => $username]); + } + + return $this->render('calendars/edit.html.twig', [ + 'form' => $form->createView(), + 'principal' => $principal, + 'username' => $username, + 'calendar' => $calendarInstance, + ]); + } + + /** + * @Route("/calendars/{username}/shares/{calendarid}", name="calendar_shares", requirements={"calendarid":"\d+"}) + */ + public function calendarShares(ManagerRegistry $doctrine, string $username, string $calendarid, TranslatorInterface $trans) + { + $instances = $doctrine->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($calendarid, true); + + $response = []; + foreach ($instances as $instance) { + $response[] = [ + 'principalUri' => $instance[0]['principalUri'], + 'displayName' => $instance['displayName'], + 'email' => $instance['email'], + 'accessText' => $trans->trans('calendar.share_access.'.$instance[0]['access']), + 'isWriteAccess' => CalendarInstance::ACCESS_READWRITE === $instance[0]['access'], + 'revokeUrl' => $this->generateUrl('calendar_revoke', ['username' => $username, 'id' => $instance[0]['id']]), + ]; + } + + return new JsonResponse($response); + } + + /** + * @Route("/calendars/{username}/share/{instanceid}", name="calendar_share_add", requirements={"instanceid":"\d+"}) + */ + public function calendarShareAdd(ManagerRegistry $doctrine, Request $request, string $username, string $instanceid, TranslatorInterface $trans) + { + $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($instanceid); + if (!$instance) { + throw $this->createNotFoundException('Calendar not found'); + } + + if (!is_numeric($request->get('principalId'))) { + throw new BadRequestHttpException(); + } + + $newShareeToAdd = $doctrine->getRepository(Principal::class)->findOneById($request->get('principalId')); + if (!$newShareeToAdd) { + throw $this->createNotFoundException('Member not found'); + } + + // Let's check that there wasn't another instance + // already existing first, so we can update it: + $existingSharedInstance = $doctrine->getRepository(CalendarInstance::class)->findSharedInstanceOfInstanceFor($instance->getCalendar()->getId(), $newShareeToAdd->getUri()); + + $writeAccess = ('true' === $request->get('write') ? CalendarInstance::ACCESS_READWRITE : CalendarInstance::ACCESS_READ); + + $entityManager = $doctrine->getManager(); + + if ($existingSharedInstance) { + $existingSharedInstance->setAccess($writeAccess); + } else { + $sharedInstance = new CalendarInstance(); + $sharedInstance->setTransparent(1) + ->setCalendar($instance->getCalendar()) + ->setShareHref('mailto:'.$newShareeToAdd->getEmail()) + ->setDescription($instance->getDescription()) + ->setDisplayName($instance->getDisplayName()) + ->setUri(\Sabre\DAV\UUIDUtil::getUUID()) + ->setPrincipalUri($newShareeToAdd->getUri()) + ->setAccess($writeAccess); + $entityManager->persist($sharedInstance); + } + + $entityManager->flush(); + $this->addFlash('success', $trans->trans('calendar.shared')); + + return $this->redirectToRoute('calendars', ['username' => $username]); + } + + /** + * @Route("/calendars/{username}/delete/{id}", name="calendar_delete", requirements={"id":"\d+"}) + */ + public function calendarDelete(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) + { + $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); + if (!$instance) { + throw $this->createNotFoundException('Calendar not found'); + } + + $entityManager = $doctrine->getManager(); + + $calendarsSubscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri($instance->getPrincipalUri()); + foreach ($calendarsSubscriptions ?? [] as $subscription) { + $entityManager->remove($subscription); + } + + $schedulingObjects = $doctrine->getRepository(SchedulingObject::class)->findByPrincipalUri($instance->getPrincipalUri()); + foreach ($schedulingObjects ?? [] as $object) { + $entityManager->remove($object); + } + + foreach ($instance->getCalendar()->getObjects() ?? [] as $object) { + $entityManager->remove($object); + } + foreach ($instance->getCalendar()->getChanges() ?? [] as $change) { + $entityManager->remove($change); + } + + // Remove the original calendar instance + $entityManager->remove($instance); + + // Remove shared instances + $sharedInstances = $doctrine->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($instance->getCalendar()->getId(), false); + foreach ($sharedInstances as $sharedInstance) { + $entityManager->remove($sharedInstance); + } + + // Finally remove the calendar itself + $entityManager->remove($instance->getCalendar()); + + $entityManager->flush(); + $this->addFlash('success', $trans->trans('calendar.deleted')); + + return $this->redirectToRoute('calendars', ['username' => $username]); + } + + /** + * @Route("/calendars/{username}/revoke/{id}", name="calendar_revoke", requirements={"id":"\d+"}) + */ + public function calendarRevoke(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) + { + $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); + if (!$instance) { + throw $this->createNotFoundException('Calendar not found'); + } + + $entityManager = $doctrine->getManager(); + $entityManager->remove($instance); + + $entityManager->flush(); + $this->addFlash('success', $trans->trans('calendar.revoked')); + + return $this->redirectToRoute('calendars', ['username' => $username]); + } +} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php new file mode 100644 index 0000000..8ea9056 --- /dev/null +++ b/src/Controller/Admin/DashboardController.php @@ -0,0 +1,38 @@ +getRepository(User::class)->findAll(); + $calendars = $doctrine->getRepository(CalendarInstance::class)->findAll(); + $addressbooks = $doctrine->getRepository(AddressBook::class)->findAll(); + $events = $doctrine->getRepository(CalendarObject::class)->findAll(); + $contacts = $doctrine->getRepository(Card::class)->findAll(); + + return $this->render('dashboard.html.twig', [ + 'users' => $users, + 'calendars' => $calendars, + 'addressbooks' => $addressbooks, + 'events' => $events, + 'contacts' => $contacts, + 'timezone' => date_default_timezone_get(), + 'version' => \App\Version::VERSION, + 'sabredav_version' => \Sabre\DAV\Version::VERSION, + ]); + } +} diff --git a/src/Controller/Admin/UserController.php b/src/Controller/Admin/UserController.php new file mode 100644 index 0000000..a248e37 --- /dev/null +++ b/src/Controller/Admin/UserController.php @@ -0,0 +1,304 @@ +getRepository(Principal::class)->findByIsMain(true); + + return $this->render('users/index.html.twig', [ + 'principals' => $principals, + ]); + } + + /** + * @Route("/users/new", name="user_create") + * @Route("/users/edit/{username}", name="user_edit") + */ + public function userCreate(ManagerRegistry $doctrine, Utils $utils, Request $request, ?string $username, TranslatorInterface $trans) + { + if ($username) { + $user = $doctrine->getRepository(User::class)->findOneByUsername($username); + if (!$user) { + throw $this->createNotFoundException('User not found'); + } + $oldHash = $user->getPassword(); + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + } else { + $user = new User(); + $principal = new Principal(); + } + + $form = $this->createForm(UserType::class, $user, ['new' => !$username]); + + $form->get('displayName')->setData($principal->getDisplayName()); + $form->get('email')->setData($principal->getEmail()); + $form->get('isAdmin')->setData($principal->getIsAdmin()); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $displayName = $form->get('displayName')->getData(); + $email = $form->get('email')->getData(); + $isAdmin = $form->get('isAdmin')->getData(); + + // Create password for user + if ($username && is_null($user->getPassword())) { + // The user is not new and does not want to change its password + $user->setPassword($oldHash); + } else { + $hash = password_hash($user->getPassword(), PASSWORD_DEFAULT); + $user->setPassword($hash); + } + + $entityManager = $doctrine->getManager(); + + // If it's a new user, create default calendar and address book, and principal + if (null === $user->getId()) { + $principal->setUri(Principal::PREFIX.$user->getUsername()); + + $calendarInstance = new CalendarInstance(); + $calendar = new Calendar(); + $calendarInstance->setPrincipalUri(Principal::PREFIX.$user->getUsername()) + ->setUri('default') // No risk of collision since unicity is guaranteed by the new user principal + ->setDisplayName($trans->trans('default.calendar.title')) + ->setDescription($trans->trans('default.calendar.description', ['user' => $displayName])) + ->setCalendar($calendar); + + // Enable delegation by default + $principalProxyRead = new Principal(); + $principalProxyRead->setUri($principal->getUri().Principal::READ_PROXY_SUFFIX) + ->setIsMain(false); + $entityManager->persist($principalProxyRead); + + $principalProxyWrite = new Principal(); + $principalProxyWrite->setUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX) + ->setIsMain(false); + $entityManager->persist($principalProxyWrite); + + $addressbook = new AddressBook(); + $addressbook->setPrincipalUri(Principal::PREFIX.$user->getUsername()) + ->setUri('default') // No risk of collision since unicity is guaranteed by the new user principal + ->setDisplayName($trans->trans('default.addressbook.title')) + ->setDescription($trans->trans('default.addressbook.description', ['user' => $displayName])); + $entityManager->persist($calendarInstance); + $entityManager->persist($addressbook); + $entityManager->persist($principal); + } + + $principal->setDisplayName($displayName) + ->setEmail($email) + ->setIsAdmin($isAdmin); + + $entityManager->persist($user); + $entityManager->flush(); + + $this->addFlash('success', $trans->trans('user.saved')); + + return $this->redirectToRoute('users'); + } + + return $this->render('users/edit.html.twig', [ + 'form' => $form->createView(), + 'username' => $username, + ]); + } + + /** + * @Route("/users/delete/{username}", name="user_delete") + */ + public function userDelete(ManagerRegistry $doctrine, string $username, TranslatorInterface $trans) + { + $user = $doctrine->getRepository(User::class)->findOneByUsername($username); + if (!$user) { + throw $this->createNotFoundException('User not found'); + } + + $entityManager = $doctrine->getManager(); + $entityManager->remove($user); + + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + $entityManager->remove($principal); + + // Remove calendars and addressbooks + $calendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri(Principal::PREFIX.$username); + foreach ($calendars ?? [] as $instance) { + foreach ($instance->getCalendar()->getObjects() ?? [] as $object) { + $entityManager->remove($object); + } + foreach ($instance->getCalendar()->getChanges() ?? [] as $change) { + $entityManager->remove($change); + } + $entityManager->remove($instance->getCalendar()); + $entityManager->remove($instance); + } + $calendarsSubscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri(Principal::PREFIX.$username); + foreach ($calendarsSubscriptions ?? [] as $subscription) { + $entityManager->remove($subscription); + } + $schedulingObjects = $doctrine->getRepository(SchedulingObject::class)->findByPrincipalUri(Principal::PREFIX.$username); + foreach ($schedulingObjects ?? [] as $object) { + $entityManager->remove($object); + } + + $addressbooks = $doctrine->getRepository(AddressBook::class)->findByPrincipalUri(Principal::PREFIX.$username); + foreach ($addressbooks ?? [] as $addressbook) { + foreach ($addressbook->getCards() ?? [] as $card) { + $entityManager->remove($card); + } + foreach ($addressbook->getChanges() ?? [] as $change) { + $entityManager->remove($change); + } + $entityManager->remove($addressbook); + } + + $entityManager->flush(); + $this->addFlash('success', $trans->trans('user.deleted')); + + return $this->redirectToRoute('users'); + } + + /** + * @Route("/users/delegates/{username}", name="delegates") + */ + public function userDelegates(ManagerRegistry $doctrine, string $username) + { + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + + $allPrincipalsExcept = $doctrine->getRepository(Principal::class)->findAllExceptPrincipal(Principal::PREFIX.$username); + + // Get delegates. They are not linked to the principal in itself, but to its proxies + $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::READ_PROXY_SUFFIX); + $principalProxyWrite = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX); + + return $this->render('users/delegates.html.twig', [ + 'principal' => $principal, + 'delegation' => $principalProxyRead && $principalProxyWrite, + 'principalProxyRead' => $principalProxyRead, + 'principalProxyWrite' => $principalProxyWrite, + 'allPrincipals' => $allPrincipalsExcept, + ]); + } + + /** + * @Route("/users/delegation/{username}/{toggle}", name="user_delegation_toggle", requirements={"toggle":"(on|off)"}) + */ + public function userToggleDelegation(ManagerRegistry $doctrine, string $username, string $toggle) + { + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); + + if (!$principal) { + throw $this->createNotFoundException('Principal not found'); + } + + $entityManager = $doctrine->getManager(); + + if ('on' === $toggle) { + $principalProxyRead = new Principal(); + $principalProxyRead->setUri($principal->getUri().Principal::READ_PROXY_SUFFIX) + ->setIsMain(false); + $entityManager->persist($principalProxyRead); + + $principalProxyWrite = new Principal(); + $principalProxyWrite->setUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX) + ->setIsMain(false); + $entityManager->persist($principalProxyWrite); + } else { + $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::READ_PROXY_SUFFIX); + $principalProxyRead && $entityManager->remove($principalProxyRead); + + $principalProxyWrite = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX); + $principalProxyWrite && $entityManager->remove($principalProxyWrite); + + // Remove also delegates + $principal->removeAllDelegees(); + } + + $entityManager->flush(); + + return $this->redirectToRoute('delegates', ['username' => $username]); + } + + /** + * @Route("/users/delegates/{username}/add", name="user_delegate_add") + */ + public function userDelegateAdd(ManagerRegistry $doctrine, Request $request, string $username) + { + if (!is_numeric($request->get('principalId'))) { + throw new BadRequestHttpException(); + } + + $newMemberToAdd = $doctrine->getRepository(Principal::class)->findOneById($request->get('principalId')); + + if (!$newMemberToAdd) { + throw $this->createNotFoundException('Member not found'); + } + + // Depending on write access or not, attach to the correct principal + if ('true' === $request->get('write')) { + // Let's check that there wasn't a read proxy first + $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::READ_PROXY_SUFFIX); + if (!$principalProxyRead) { + throw $this->createNotFoundException('Principal linked to this calendar not found'); + } + $principalProxyRead->removeDelegee($newMemberToAdd); + // And then add the Write access + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::WRITE_PROXY_SUFFIX); + } else { + $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::READ_PROXY_SUFFIX); + } + + if (!$principal) { + throw $this->createNotFoundException('Principal linked to this calendar not found'); + } + + $principal->addDelegee($newMemberToAdd); + $entityManager = $doctrine->getManager(); + $entityManager->flush(); + + return $this->redirectToRoute('delegates', ['username' => $username]); + } + + /** + * @Route("/users/delegates/{username}/remove/{principalProxyId}/{delegateId}", name="user_delegate_remove", requirements={"principalProxyId":"\d+", "delegateId":"\d+"}) + */ + public function userDelegateRemove(ManagerRegistry $doctrine, Request $request, string $username, int $principalProxyId, int $delegateId) + { + $principalProxy = $doctrine->getRepository(Principal::class)->findOneById($principalProxyId); + if (!$principalProxy) { + throw $this->createNotFoundException('Principal linked to this calendar not found'); + } + + $memberToRemove = $doctrine->getRepository(Principal::class)->findOneById($delegateId); + if (!$memberToRemove) { + throw $this->createNotFoundException('Member not found'); + } + + $principalProxy->removeDelegee($memberToRemove); + $entityManager = $doctrine->getManager(); + $entityManager->flush(); + + return $this->redirectToRoute('delegates', ['username' => $username]); + } +} diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php deleted file mode 100644 index 1dcb351..0000000 --- a/src/Controller/AdminController.php +++ /dev/null @@ -1,662 +0,0 @@ -getRepository(User::class)->findAll(); - $calendars = $doctrine->getRepository(CalendarInstance::class)->findAll(); - $addressbooks = $doctrine->getRepository(AddressBook::class)->findAll(); - $events = $doctrine->getRepository(CalendarObject::class)->findAll(); - $contacts = $doctrine->getRepository(Card::class)->findAll(); - - return $this->render('dashboard.html.twig', [ - 'users' => $users, - 'calendars' => $calendars, - 'addressbooks' => $addressbooks, - 'events' => $events, - 'contacts' => $contacts, - 'timezone' => date_default_timezone_get(), - 'version' => \App\Version::VERSION, - 'sabredav_version' => \Sabre\DAV\Version::VERSION, - ]); - } - - /** - * @Route("/users", name="users") - */ - public function users(ManagerRegistry $doctrine) - { - $principals = $doctrine->getRepository(Principal::class)->findByIsMain(true); - - return $this->render('users/index.html.twig', [ - 'principals' => $principals, - ]); - } - - /** - * @Route("/users/new", name="user_create") - * @Route("/users/edit/{username}", name="user_edit") - */ - public function userCreate(ManagerRegistry $doctrine, Utils $utils, Request $request, ?string $username, TranslatorInterface $trans) - { - if ($username) { - $user = $doctrine->getRepository(User::class)->findOneByUsername($username); - if (!$user) { - throw $this->createNotFoundException('User not found'); - } - $oldHash = $user->getPassword(); - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - } else { - $user = new User(); - $principal = new Principal(); - } - - $form = $this->createForm(UserType::class, $user, ['new' => !$username]); - - $form->get('displayName')->setData($principal->getDisplayName()); - $form->get('email')->setData($principal->getEmail()); - $form->get('isAdmin')->setData($principal->getIsAdmin()); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $displayName = $form->get('displayName')->getData(); - $email = $form->get('email')->getData(); - $isAdmin = $form->get('isAdmin')->getData(); - - // Create password for user - if ($username && is_null($user->getPassword())) { - // The user is not new and does not want to change its password - $user->setPassword($oldHash); - } else { - $hash = password_hash($user->getPassword(), PASSWORD_DEFAULT); - $user->setPassword($hash); - } - - $entityManager = $doctrine->getManager(); - - // If it's a new user, create default calendar and address book, and principal - if (null === $user->getId()) { - $principal->setUri(Principal::PREFIX.$user->getUsername()); - - $calendarInstance = new CalendarInstance(); - $calendar = new Calendar(); - $calendarInstance->setPrincipalUri(Principal::PREFIX.$user->getUsername()) - ->setUri('default') // No risk of collision since unicity is guaranteed by the new user principal - ->setDisplayName($trans->trans('default.calendar.title')) - ->setDescription($trans->trans('default.calendar.description', ['user' => $displayName])) - ->setCalendar($calendar); - - // Enable delegation by default - $principalProxyRead = new Principal(); - $principalProxyRead->setUri($principal->getUri().Principal::READ_PROXY_SUFFIX) - ->setIsMain(false); - $entityManager->persist($principalProxyRead); - - $principalProxyWrite = new Principal(); - $principalProxyWrite->setUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX) - ->setIsMain(false); - $entityManager->persist($principalProxyWrite); - - $addressbook = new AddressBook(); - $addressbook->setPrincipalUri(Principal::PREFIX.$user->getUsername()) - ->setUri('default') // No risk of collision since unicity is guaranteed by the new user principal - ->setDisplayName($trans->trans('default.addressbook.title')) - ->setDescription($trans->trans('default.addressbook.description', ['user' => $displayName])); - $entityManager->persist($calendarInstance); - $entityManager->persist($addressbook); - $entityManager->persist($principal); - } - - $principal->setDisplayName($displayName) - ->setEmail($email) - ->setIsAdmin($isAdmin); - - $entityManager->persist($user); - $entityManager->flush(); - - $this->addFlash('success', $trans->trans('user.saved')); - - return $this->redirectToRoute('users'); - } - - return $this->render('users/edit.html.twig', [ - 'form' => $form->createView(), - 'username' => $username, - ]); - } - - /** - * @Route("/users/delete/{username}", name="user_delete") - */ - public function userDelete(ManagerRegistry $doctrine, string $username, TranslatorInterface $trans) - { - $user = $doctrine->getRepository(User::class)->findOneByUsername($username); - if (!$user) { - throw $this->createNotFoundException('User not found'); - } - - $entityManager = $doctrine->getManager(); - $entityManager->remove($user); - - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - $entityManager->remove($principal); - - // Remove calendars and addressbooks - $calendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri(Principal::PREFIX.$username); - foreach ($calendars ?? [] as $instance) { - foreach ($instance->getCalendar()->getObjects() ?? [] as $object) { - $entityManager->remove($object); - } - foreach ($instance->getCalendar()->getChanges() ?? [] as $change) { - $entityManager->remove($change); - } - $entityManager->remove($instance->getCalendar()); - $entityManager->remove($instance); - } - $calendarsSubscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri(Principal::PREFIX.$username); - foreach ($calendarsSubscriptions ?? [] as $subscription) { - $entityManager->remove($subscription); - } - $schedulingObjects = $doctrine->getRepository(SchedulingObject::class)->findByPrincipalUri(Principal::PREFIX.$username); - foreach ($schedulingObjects ?? [] as $object) { - $entityManager->remove($object); - } - - $addressbooks = $doctrine->getRepository(AddressBook::class)->findByPrincipalUri(Principal::PREFIX.$username); - foreach ($addressbooks ?? [] as $addressbook) { - foreach ($addressbook->getCards() ?? [] as $card) { - $entityManager->remove($card); - } - foreach ($addressbook->getChanges() ?? [] as $change) { - $entityManager->remove($change); - } - $entityManager->remove($addressbook); - } - - $entityManager->flush(); - $this->addFlash('success', $trans->trans('user.deleted')); - - return $this->redirectToRoute('users'); - } - - /** - * @Route("/users/delegates/{username}", name="delegates") - */ - public function userDelegates(ManagerRegistry $doctrine, string $username) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - - $allPrincipalsExcept = $doctrine->getRepository(Principal::class)->findAllExceptPrincipal(Principal::PREFIX.$username); - - // Get delegates. They are not linked to the principal in itself, but to its proxies - $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::READ_PROXY_SUFFIX); - $principalProxyWrite = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX); - - return $this->render('users/delegates.html.twig', [ - 'principal' => $principal, - 'delegation' => $principalProxyRead && $principalProxyWrite, - 'principalProxyRead' => $principalProxyRead, - 'principalProxyWrite' => $principalProxyWrite, - 'allPrincipals' => $allPrincipalsExcept, - ]); - } - - /** - * @Route("/users/delegation/{username}/{toggle}", name="user_delegation_toggle", requirements={"toggle":"(on|off)"}) - */ - public function userToggleDelegation(ManagerRegistry $doctrine, string $username, string $toggle) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - - if (!$principal) { - throw $this->createNotFoundException('Principal not found'); - } - - $entityManager = $doctrine->getManager(); - - if ('on' === $toggle) { - $principalProxyRead = new Principal(); - $principalProxyRead->setUri($principal->getUri().Principal::READ_PROXY_SUFFIX) - ->setIsMain(false); - $entityManager->persist($principalProxyRead); - - $principalProxyWrite = new Principal(); - $principalProxyWrite->setUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX) - ->setIsMain(false); - $entityManager->persist($principalProxyWrite); - } else { - $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::READ_PROXY_SUFFIX); - $principalProxyRead && $entityManager->remove($principalProxyRead); - - $principalProxyWrite = $doctrine->getRepository(Principal::class)->findOneByUri($principal->getUri().Principal::WRITE_PROXY_SUFFIX); - $principalProxyWrite && $entityManager->remove($principalProxyWrite); - - // Remove also delegates - $principal->removeAllDelegees(); - } - - $entityManager->flush(); - - return $this->redirectToRoute('delegates', ['username' => $username]); - } - - /** - * @Route("/users/delegates/{username}/add", name="user_delegate_add") - */ - public function userDelegateAdd(ManagerRegistry $doctrine, Request $request, string $username) - { - if (!is_numeric($request->get('principalId'))) { - throw new BadRequestHttpException(); - } - - $newMemberToAdd = $doctrine->getRepository(Principal::class)->findOneById($request->get('principalId')); - - if (!$newMemberToAdd) { - throw $this->createNotFoundException('Member not found'); - } - - // Depending on write access or not, attach to the correct principal - if ('true' === $request->get('write')) { - // Let's check that there wasn't a read proxy first - $principalProxyRead = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::READ_PROXY_SUFFIX); - if (!$principalProxyRead) { - throw $this->createNotFoundException('Principal linked to this calendar not found'); - } - $principalProxyRead->removeDelegee($newMemberToAdd); - // And then add the Write access - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::WRITE_PROXY_SUFFIX); - } else { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username.Principal::READ_PROXY_SUFFIX); - } - - if (!$principal) { - throw $this->createNotFoundException('Principal linked to this calendar not found'); - } - - $principal->addDelegee($newMemberToAdd); - $entityManager = $doctrine->getManager(); - $entityManager->flush(); - - return $this->redirectToRoute('delegates', ['username' => $username]); - } - - /** - * @Route("/users/delegates/{username}/remove/{principalProxyId}/{delegateId}", name="user_delegate_remove", requirements={"principalProxyId":"\d+", "delegateId":"\d+"}) - */ - public function userDelegateRemove(ManagerRegistry $doctrine, Request $request, string $username, int $principalProxyId, int $delegateId) - { - $principalProxy = $doctrine->getRepository(Principal::class)->findOneById($principalProxyId); - if (!$principalProxy) { - throw $this->createNotFoundException('Principal linked to this calendar not found'); - } - - $memberToRemove = $doctrine->getRepository(Principal::class)->findOneById($delegateId); - if (!$memberToRemove) { - throw $this->createNotFoundException('Member not found'); - } - - $principalProxy->removeDelegee($memberToRemove); - $entityManager = $doctrine->getManager(); - $entityManager->flush(); - - return $this->redirectToRoute('delegates', ['username' => $username]); - } - - /** - * @Route("/calendars/{username}", name="calendars") - */ - public function calendars(ManagerRegistry $doctrine, UrlGeneratorInterface $router, string $username) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - $allCalendars = $doctrine->getRepository(CalendarInstance::class)->findByPrincipalUri(Principal::PREFIX.$username); - - // Separate shared calendars - $calendars = []; - $shared = []; - foreach ($allCalendars as $calendar) { - if (CalendarInstance::ACCESS_OWNER === $calendar->getAccess()) { - $calendars[] = [ - 'entity' => $calendar, - 'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL), - ]; - } else { - $shared[] = [ - 'entity' => $calendar, - 'uri' => $router->generate('dav', ['path' => 'calendars/'.$username.'/'.$calendar->getUri()], UrlGeneratorInterface::ABSOLUTE_URL), - ]; - } - } - - // We need all the other users so we can propose to share calendars with them - $allPrincipalsExcept = $doctrine->getRepository(Principal::class)->findAllExceptPrincipal(Principal::PREFIX.$username); - - return $this->render('calendars/index.html.twig', [ - 'calendars' => $calendars, - 'shared' => $shared, - 'principal' => $principal, - 'username' => $username, - 'allPrincipals' => $allPrincipalsExcept, - ]); - } - - /** - * @Route("/calendars/{username}/new", name="calendar_create") - * @Route("/calendars/{username}/edit/{id}", name="calendar_edit", requirements={"id":"\d+"}) - */ - public function calendarEdit(ManagerRegistry $doctrine, Request $request, string $username, ?int $id, TranslatorInterface $trans) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - - if (!$principal) { - throw $this->createNotFoundException('User not found'); - } - - if ($id) { - $calendarInstance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); - if (!$calendarInstance) { - throw $this->createNotFoundException('Calendar not found'); - } - } else { - $calendarInstance = new CalendarInstance(); - $calendar = new Calendar(); - $calendarInstance->setCalendar($calendar); - } - - $form = $this->createForm(CalendarInstanceType::class, $calendarInstance, [ - 'new' => !$id, - 'shared' => CalendarInstance::ACCESS_OWNER !== $calendarInstance->getAccess(), - ]); - - $components = explode(',', $calendarInstance->getCalendar()->getComponents()); - - $form->get('events')->setData(in_array(Calendar::COMPONENT_EVENTS, $components)); - $form->get('todos')->setData(in_array(Calendar::COMPONENT_TODOS, $components)); - $form->get('notes')->setData(in_array(Calendar::COMPONENT_NOTES, $components)); - $form->get('principalUri')->setData(Principal::PREFIX.$username); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $components = []; - if ($form->get('events')->getData()) { - $components[] = Calendar::COMPONENT_EVENTS; - } - if ($form->get('todos')->getData()) { - $components[] = Calendar::COMPONENT_TODOS; - } - if ($form->get('notes')->getData()) { - $components[] = Calendar::COMPONENT_NOTES; - } - - $calendarInstance->getCalendar()->setComponents(implode(',', $components)); - - $entityManager = $doctrine->getManager(); - - $entityManager->persist($calendarInstance); - $entityManager->flush(); - - $this->addFlash('success', $trans->trans('calendar.saved')); - - return $this->redirectToRoute('calendars', ['username' => $username]); - } - - return $this->render('calendars/edit.html.twig', [ - 'form' => $form->createView(), - 'principal' => $principal, - 'username' => $username, - 'calendar' => $calendarInstance, - ]); - } - - /** - * @Route("/calendars/{username}/shares/{calendarid}", name="calendar_shares", requirements={"calendarid":"\d+"}) - */ - public function calendarShares(ManagerRegistry $doctrine, string $username, string $calendarid, TranslatorInterface $trans) - { - $instances = $doctrine->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($calendarid, true); - - $response = []; - foreach ($instances as $instance) { - $response[] = [ - 'principalUri' => $instance[0]['principalUri'], - 'displayName' => $instance['displayName'], - 'email' => $instance['email'], - 'accessText' => $trans->trans('calendar.share_access.'.$instance[0]['access']), - 'isWriteAccess' => CalendarInstance::ACCESS_READWRITE === $instance[0]['access'], - 'revokeUrl' => $this->generateUrl('calendar_revoke', ['username' => $username, 'id' => $instance[0]['id']]), - ]; - } - - return new JsonResponse($response); - } - - /** - * @Route("/calendars/{username}/share/{instanceid}", name="calendar_share_add", requirements={"instanceid":"\d+"}) - */ - public function calendarShareAdd(ManagerRegistry $doctrine, Request $request, string $username, string $instanceid, TranslatorInterface $trans) - { - $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($instanceid); - if (!$instance) { - throw $this->createNotFoundException('Calendar not found'); - } - - if (!is_numeric($request->get('principalId'))) { - throw new BadRequestHttpException(); - } - - $newShareeToAdd = $doctrine->getRepository(Principal::class)->findOneById($request->get('principalId')); - if (!$newShareeToAdd) { - throw $this->createNotFoundException('Member not found'); - } - - // Let's check that there wasn't another instance - // already existing first, so we can update it: - $existingSharedInstance = $doctrine->getRepository(CalendarInstance::class)->findSharedInstanceOfInstanceFor($instance->getCalendar()->getId(), $newShareeToAdd->getUri()); - - $writeAccess = ('true' === $request->get('write') ? CalendarInstance::ACCESS_READWRITE : CalendarInstance::ACCESS_READ); - - $entityManager = $doctrine->getManager(); - - if ($existingSharedInstance) { - $existingSharedInstance->setAccess($writeAccess); - } else { - $sharedInstance = new CalendarInstance(); - $sharedInstance->setTransparent(1) - ->setCalendar($instance->getCalendar()) - ->setShareHref('mailto:'.$newShareeToAdd->getEmail()) - ->setDescription($instance->getDescription()) - ->setDisplayName($instance->getDisplayName()) - ->setUri(\Sabre\DAV\UUIDUtil::getUUID()) - ->setPrincipalUri($newShareeToAdd->getUri()) - ->setAccess($writeAccess); - $entityManager->persist($sharedInstance); - } - - $entityManager->flush(); - $this->addFlash('success', $trans->trans('calendar.shared')); - - return $this->redirectToRoute('calendars', ['username' => $username]); - } - - /** - * @Route("/calendars/{username}/delete/{id}", name="calendar_delete", requirements={"id":"\d+"}) - */ - public function calendarDelete(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) - { - $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); - if (!$instance) { - throw $this->createNotFoundException('Calendar not found'); - } - - $entityManager = $doctrine->getManager(); - - $calendarsSubscriptions = $doctrine->getRepository(CalendarSubscription::class)->findByPrincipalUri($instance->getPrincipalUri()); - foreach ($calendarsSubscriptions ?? [] as $subscription) { - $entityManager->remove($subscription); - } - - $schedulingObjects = $doctrine->getRepository(SchedulingObject::class)->findByPrincipalUri($instance->getPrincipalUri()); - foreach ($schedulingObjects ?? [] as $object) { - $entityManager->remove($object); - } - - foreach ($instance->getCalendar()->getObjects() ?? [] as $object) { - $entityManager->remove($object); - } - foreach ($instance->getCalendar()->getChanges() ?? [] as $change) { - $entityManager->remove($change); - } - - // Remove the original calendar instance - $entityManager->remove($instance); - - // Remove shared instances - $sharedInstances = $doctrine->getRepository(CalendarInstance::class)->findSharedInstancesOfInstance($instance->getCalendar()->getId(), false); - foreach ($sharedInstances as $sharedInstance) { - $entityManager->remove($sharedInstance); - } - - // Finally remove the calendar itself - $entityManager->remove($instance->getCalendar()); - - $entityManager->flush(); - $this->addFlash('success', $trans->trans('calendar.deleted')); - - return $this->redirectToRoute('calendars', ['username' => $username]); - } - - /** - * @Route("/calendars/{username}/revoke/{id}", name="calendar_revoke", requirements={"id":"\d+"}) - */ - public function calendarRevoke(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) - { - $instance = $doctrine->getRepository(CalendarInstance::class)->findOneById($id); - if (!$instance) { - throw $this->createNotFoundException('Calendar not found'); - } - - $entityManager = $doctrine->getManager(); - $entityManager->remove($instance); - - $entityManager->flush(); - $this->addFlash('success', $trans->trans('calendar.revoked')); - - return $this->redirectToRoute('calendars', ['username' => $username]); - } - - /** - * @Route("/adressbooks/{username}", name="address_books") - */ - public function addressBooks(ManagerRegistry $doctrine, string $username) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - $addressbooks = $doctrine->getRepository(AddressBook::class)->findByPrincipalUri(Principal::PREFIX.$username); - - return $this->render('addressbooks/index.html.twig', [ - 'addressbooks' => $addressbooks, - 'principal' => $principal, - 'username' => $username, - ]); - } - - /** - * @Route("/adressbooks/{username}/new", name="addressbook_create") - * @Route("/adressbooks/{username}/edit/{id}", name="addressbook_edit", requirements={"id":"\d+"}) - */ - public function addressbookCreate(ManagerRegistry $doctrine, Request $request, string $username, ?int $id, TranslatorInterface $trans) - { - $principal = $doctrine->getRepository(Principal::class)->findOneByUri(Principal::PREFIX.$username); - - if (!$principal) { - throw $this->createNotFoundException('User not found'); - } - - if ($id) { - $addressbook = $doctrine->getRepository(AddressBook::class)->findOneById($id); - if (!$addressbook) { - throw $this->createNotFoundException('Address book not found'); - } - } else { - $addressbook = new AddressBook(); - } - - $form = $this->createForm(AddressBookType::class, $addressbook, ['new' => !$id]); - - $form->get('principalUri')->setData(Principal::PREFIX.$username); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $entityManager = $doctrine->getManager(); - - $entityManager->persist($addressbook); - $entityManager->flush(); - - $this->addFlash('success', $trans->trans('addressbooks.saved')); - - return $this->redirectToRoute('address_books', ['username' => $username]); - } - - return $this->render('addressbooks/edit.html.twig', [ - 'form' => $form->createView(), - 'principal' => $principal, - 'username' => $username, - 'addressbook' => $addressbook, - ]); - } - - /** - * @Route("/addressbooks/{username}/delete/{id}", name="addressbook_delete", requirements={"id":"\d+"}) - */ - public function addressbookDelete(ManagerRegistry $doctrine, string $username, string $id, TranslatorInterface $trans) - { - $addressbook = $doctrine->getRepository(AddressBook::class)->findOneById($id); - if (!$addressbook) { - throw $this->createNotFoundException('Address Book not found'); - } - - $entityManager = $doctrine->getManager(); - - foreach ($addressbook->getCards() ?? [] as $card) { - $entityManager->remove($card); - } - foreach ($addressbook->getChanges() ?? [] as $change) { - $entityManager->remove($change); - } - $entityManager->remove($addressbook); - - $entityManager->flush(); - $this->addFlash('success', $trans->trans('addressbooks.deleted')); - - return $this->redirectToRoute('address_books', ['username' => $username]); - } -} diff --git a/src/Controller/DAVController.php b/src/Controller/DAVController.php index 2b69972..7e12263 100644 --- a/src/Controller/DAVController.php +++ b/src/Controller/DAVController.php @@ -5,6 +5,7 @@ use App\Entity\Principal; use App\Entity\User; use App\Plugins\DavisIMipPlugin; +use App\Plugins\PublicAwareDAVACLPlugin; use App\Services\BasicAuth; use App\Services\IMAPAuth; use App\Services\LDAPAuth; @@ -226,11 +227,12 @@ private function initServer(string $authMethod, string $authRealm = User::DEFAUL // Plugins $this->server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $authRealm)); - $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin()); + $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin(false)); // We disable the file creation / upload / sharing in the browser $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); - $aclPlugin = new \Sabre\DAVACL\Plugin(); + $aclPlugin = new PublicAwareDAVACLPlugin($this->em); $aclPlugin->hideNodesFromListings = true; + // Fetch admins, if any $admins = $this->em->getRepository(Principal::class)->findBy(['isAdmin' => true]); foreach ($admins as $principal) { @@ -293,6 +295,10 @@ private function initExceptionListener() */ public function dav(Request $request, string $path) { + if ($request->getMethod() === "OPTIONS") { + return new Response(); + } + // \Sabre\DAV\Server does not let us use a custom SAPI, and its behaviour // is to directly output headers and content to php://output. Hence, we // let the headers pass (we have not choice) and capture the output in a diff --git a/src/Entity/CalendarInstance.php b/src/Entity/CalendarInstance.php index b107e69..faa80fa 100644 --- a/src/Entity/CalendarInstance.php +++ b/src/Entity/CalendarInstance.php @@ -19,14 +19,29 @@ */ class CalendarInstance { - public const INVITE_STATUS_NORESPONSE = 1; - public const INVITE_STATUS_ACCEPTED = 2; - public const INVITE_STATUS_DECLINED = 3; - public const INVITE_STATUS_INVALID = 4; + public const INVITE_NORESPONSE = 1; + public const INVITE_ACCEPTED = 2; + public const INVITE_DECLINED = 3; + public const INVITE_INVALID = 4; - public const ACCESS_OWNER = 1; + public const ACCESS_NOTSHARED = 0; + public const ACCESS_SHAREDOWNER = 1; public const ACCESS_READ = 2; public const ACCESS_READWRITE = 3; + public const ACCESS_NOACCESS = 4; + + // Used to identify a public calendar, available to anyone without logging in. + // It can't be shared, and it's owned by the principal. + public const ACCESS_PUBLIC = 10; + + public static function getOwnerAccesses(): array + { + return [ + self::ACCESS_NOTSHARED, + self::ACCESS_SHAREDOWNER, + self::ACCESS_PUBLIC, + ]; + } /** * @ORM\Id() @@ -110,10 +125,10 @@ class CalendarInstance public function __construct() { - $this->shareInviteStatus = self::INVITE_STATUS_ACCEPTED; + $this->shareInviteStatus = self::INVITE_ACCEPTED; $this->transparent = 0; $this->calendarOrder = 0; - $this->access = self::ACCESS_OWNER; + $this->access = self::ACCESS_SHAREDOWNER; } public function getId(): ?int @@ -157,6 +172,16 @@ public function setAccess(int $access): self return $this; } + public function isShared(): bool + { + return !in_array($this->access, self::getOwnerAccesses()); + } + + public function isPublic(): bool + { + return self::ACCESS_PUBLIC === $this->access; + } + public function getDisplayName(): ?string { return $this->displayName; diff --git a/src/Form/CalendarInstanceType.php b/src/Form/CalendarInstanceType.php index 8ef161c..f3e34fc 100644 --- a/src/Form/CalendarInstanceType.php +++ b/src/Form/CalendarInstanceType.php @@ -5,6 +5,7 @@ use App\Entity\CalendarInstance; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; @@ -26,6 +27,14 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'help' => 'form.uri.help.caldav', 'required' => true, ]) + ->add('public', ChoiceType::class, [ + 'label' => 'form.public', + 'mapped' => false, + 'disabled' => $options['shared'], + 'help' => 'form.public.help.caldav', + 'required' => true, + 'choices' => ['yes' => true, 'no' => false], + ]) ->add('displayName', TextType::class, [ 'label' => 'form.displayName', 'help' => 'form.name.help.caldav', diff --git a/src/Plugins/PublicAwareDAVACLPlugin.php b/src/Plugins/PublicAwareDAVACLPlugin.php new file mode 100644 index 0000000..fb35074 --- /dev/null +++ b/src/Plugins/PublicAwareDAVACLPlugin.php @@ -0,0 +1,70 @@ +em = $entityManager; + } + + /** + * We override this method so that public objects can be seen correctly in the browser, + * with the assets (css, images). + */ + public function beforeMethod(RequestInterface $request, ResponseInterface $response) + { + $params = $request->getQueryParameters(); + if (isset($params['sabreAction']) && 'asset' === $params['sabreAction']) { + return; + } + + return parent::beforeMethod($request, $response); + } + + public function getAcl($node): array + { + $acl = parent::getAcl($node); + + if ($node instanceof \Sabre\CalDAV\Calendar) { + if (CalendarInstance::ACCESS_PUBLIC === $node->getShareAccess()) { + // We must add the ACL on the calendar itself + $acl[] = [ + 'principal' => '{DAV:}unauthenticated', + 'privilege' => '{DAV:}read', + 'protected' => false, + ]; + } + } elseif ($node instanceof \Sabre\CalDAV\CalendarObject) { + // The property is private in \Sabre\CalDAV\CalendarObject and we don't want to create + // a new class just to access it, so we use a closure. + $calendarInfo = (fn () => $this->calendarInfo)->call($node); + // [0] is the calendarId, [1] is the calendarInstanceId + $calendarInstanceId = $calendarInfo['id'][1]; + + $calendar = $this->em->getRepository(CalendarInstance::class)->findOneById($calendarInstanceId); + + if ($calendar && $calendar->isPublic()) { + // We must add the ACL on the object itself + $acl[] = [ + 'principal' => '{DAV:}unauthenticated', + 'privilege' => '{DAV:}read', + 'protected' => false, + ]; + } + } + + return $acl; + } +} diff --git a/src/Repository/CalendarInstanceRepository.php b/src/Repository/CalendarInstanceRepository.php index f84a754..1d54b0f 100644 --- a/src/Repository/CalendarInstanceRepository.php +++ b/src/Repository/CalendarInstanceRepository.php @@ -29,8 +29,8 @@ public function findSharedInstancesOfInstance(int $calendarId, bool $withCalenda ->leftJoin(Principal::class, 'p', \Doctrine\ORM\Query\Expr\Join::WITH, 'c.principalUri = p.uri') ->where('c.calendar = :id') ->setParameter('id', $calendarId) - ->andWhere('c.access != :ownerAccess') - ->setParameter('ownerAccess', CalendarInstance::ACCESS_OWNER); + ->andWhere('c.access NOT IN (:ownerAccess)') + ->setParameter('ownerAccess', CalendarInstance::getOwnerAccesses()); if ($withCalendar) { // Returns CalendarInstances as arrays, with displayName and email of the owner @@ -52,8 +52,8 @@ public function findSharedInstanceOfInstanceFor(int $calendarId, string $princip return $this->createQueryBuilder('c') ->where('c.calendar = :id') ->setParameter('id', $calendarId) - ->andWhere('c.access != :ownerAccess') - ->setParameter('ownerAccess', CalendarInstance::ACCESS_OWNER) + ->andWhere('c.access NOT IN (:ownerAccess)') + ->setParameter('ownerAccess', CalendarInstance::getOwnerAccesses()) ->andWhere('c.principalUri = :principalUri') ->setParameter('principalUri', $principalUri) ->getQuery() diff --git a/templates/calendars/index.html.twig b/templates/calendars/index.html.twig index f970a1a..ef0f5d6 100644 --- a/templates/calendars/index.html.twig +++ b/templates/calendars/index.html.twig @@ -14,17 +14,22 @@