diff --git a/composer.json b/composer.json index 4a8bc11..b59f2a0 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "ext-curl": "*", "bref/bref": "^3", "bref/extra-php-extensions": "^3", - "craftcms/cms": "^4.6 || ^5", + "craftcms/cms": "^4.18.2 || ^5.10.6", "craftcms/flysystem": "^1.0.0 || ^2.0.0", "guzzlehttp/guzzle": "^7.4.5", "league/flysystem-aws-s3-v3": "^3.15", diff --git a/composer.lock b/composer.lock index 960e769..2468bc9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dcd08efcfa79b08fba1ef27eeef85643", + "content-hash": "5ef7670acc9490a8fe1b21b4deb350e7", "packages": [ { "name": "99designs/http-signatures", @@ -217,28 +217,29 @@ }, { "name": "bacon/bacon-qr-code", - "version": "2.0.8", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", - "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", "shasum": "" }, "require": { "dasprid/enum": "^1.0.3", "ext-iconv": "*", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "phly/keep-a-changelog": "^2.1", - "phpunit/phpunit": "^7 | ^8 | ^9", - "spatie/phpunit-snapshot-assertions": "^4.2.9", - "squizlabs/php_codesniffer": "^3.4" + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || ^11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "spatie/pixelmatch-php": "^1.2.0", + "squizlabs/php_codesniffer": "^3.9" }, "suggest": { "ext-imagick": "to generate QR code images" @@ -265,9 +266,9 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.1.1" }, - "time": "2022-12-07T17:46:57+00:00" + "time": "2026-04-05T21:06:35+00:00" }, { "name": "bref/bref", @@ -409,23 +410,22 @@ }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.17.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "8189e751995f9e15729c1aa2f89fa8f166ffe818" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/8189e751995f9e15729c1aa2f89fa8f166ffe818", + "reference": "8189e751995f9e15729c1aa2f89fa8f166ffe818", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -457,7 +457,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.17.2" }, "funding": [ { @@ -465,7 +465,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-05-25T20:34:43+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -822,24 +822,24 @@ }, { "name": "craftcms/cms", - "version": "5.9.17", + "version": "5.x-dev", "source": { "type": "git", "url": "https://github.com/craftcms/cms.git", - "reference": "e2000a68a29718608a56f6a21357d0ca875347f0" + "reference": "d7c4948d2608565140479884aaee3be75ed8908e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/cms/zipball/e2000a68a29718608a56f6a21357d0ca875347f0", - "reference": "e2000a68a29718608a56f6a21357d0ca875347f0", + "url": "https://api.github.com/repos/craftcms/cms/zipball/d7c4948d2608565140479884aaee3be75ed8908e", + "reference": "d7c4948d2608565140479884aaee3be75ed8908e", "shasum": "" }, "require": { - "bacon/bacon-qr-code": "^2.0", + "bacon/bacon-qr-code": "^3.0", "commerceguys/addressing": "^2.1.1", "composer/semver": "^3.3.2", "craftcms/plugin-installer": "~1.6.0", - "craftcms/server-check": "~5.0.1", + "craftcms/server-check": "~5.1.0", "creocoder/yii2-nested-sets": "~0.9.0", "elvanto/litemoji": "~4.3.0", "enshrined/svg-sanitize": "~0.22.0", @@ -863,7 +863,8 @@ "php": "^8.2", "phpdocumentor/reflection-docblock": "^5.3", "phpoffice/phpspreadsheet": "^5.3", - "pixelandtonic/imagine": "~1.3.3.1", + "pixelandtonic/graphql-php": "~14.11.10.1", + "pixelandtonic/imagine": "~1.5.2.1", "pragmarx/google2fa": "^8.0", "pragmarx/recovery": "^0.2.1", "samdark/yii2-psr-log-target": "^1.1.3", @@ -878,11 +879,10 @@ "symfony/var-dumper": "^5.0|^6.0|^7.0", "symfony/yaml": "^5.2.3|^6.0|^7.0", "theiconic/name-parser": "^1.2", - "twig/twig": "~3.21.1", + "twig/twig": "~3.27.0", "voku/portable-ascii": "^2.0", "web-auth/webauthn-lib": "~5.2.4", - "webonyx/graphql-php": "~14.11.10", - "yiisoft/yii2": "~2.0.54.0", + "yiisoft/yii2": "~2.0.55.0", "yiisoft/yii2-debug": "~2.1.27.0", "yiisoft/yii2-queue": "~2.3.2", "yiisoft/yii2-symfonymailer": "^4.0.0" @@ -915,6 +915,7 @@ "ext-iconv": "Adds support for more character encodings than PHP’s built-in mb_convert_encoding() function, which Craft will take advantage of when converting strings to UTF-8.", "ext-imagick": "Adds support for more image processing formats and options." }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -947,7 +948,7 @@ "rss": "https://github.com/craftcms/cms/releases.atom", "source": "https://github.com/craftcms/cms" }, - "time": "2026-03-17T17:27:38+00:00" + "time": "2026-06-11T21:33:19+00:00" }, { "name": "craftcms/flysystem", @@ -1059,16 +1060,16 @@ }, { "name": "craftcms/server-check", - "version": "5.0.4", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/craftcms/server-check.git", - "reference": "3b1f239c1cc781710978b0baa3e3bc99410d1973" + "reference": "7a4f1720c4fe1f0731254a82e63060a217f51cdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/server-check/zipball/3b1f239c1cc781710978b0baa3e3bc99410d1973", - "reference": "3b1f239c1cc781710978b0baa3e3bc99410d1973", + "url": "https://api.github.com/repos/craftcms/server-check/zipball/7a4f1720c4fe1f0731254a82e63060a217f51cdc", + "reference": "7a4f1720c4fe1f0731254a82e63060a217f51cdc", "shasum": "" }, "type": "library", @@ -1097,7 +1098,7 @@ "rss": "https://github.com/craftcms/server-check/releases.atom", "source": "https://github.com/craftcms/server-check" }, - "time": "2025-07-23T14:22:43+00:00" + "time": "2026-04-07T16:48:35+00:00" }, { "name": "craftcms/yii2-cache-cascade", @@ -2967,16 +2968,16 @@ }, { "name": "maennchen/zipstream-php", - "version": "3.2.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" + "reference": "77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", - "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e", + "reference": "77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e", "shasum": "" }, "require": { @@ -3033,7 +3034,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.2" }, "funding": [ { @@ -3041,7 +3042,7 @@ "type": "github" } ], - "time": "2025-12-10T09:58:31+00:00" + "time": "2026-04-11T18:38:28+00:00" }, { "name": "markbaker/complex", @@ -3265,16 +3266,16 @@ }, { "name": "moneyphp/money", - "version": "v4.8.0", + "version": "v4.9.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec" + "reference": "d49ee625c6ba79b9d7a228ce153b02fc1032152b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/b358727ea5a5cd2d7475e59c31dfc352440ae7ec", - "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec", + "url": "https://api.github.com/repos/moneyphp/money/zipball/d49ee625c6ba79b9d7a228ce153b02fc1032152b", + "reference": "d49ee625c6ba79b9d7a228ce153b02fc1032152b", "shasum": "" }, "require": { @@ -3349,9 +3350,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.8.0" + "source": "https://github.com/moneyphp/money/tree/v4.9.0" }, - "time": "2025-10-23T07:55:09+00:00" + "time": "2026-05-04T20:23:15+00:00" }, { "name": "monolog/monolog", @@ -4066,16 +4067,16 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "5.5.0", + "version": "5.8.0", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "eecd31b885a1c8192f12738130f85bbc6e8906ba" + "reference": "01964d92536edf1a3a874b9580a52824bebf6fbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/eecd31b885a1c8192f12738130f85bbc6e8906ba", - "reference": "eecd31b885a1c8192f12738130f85bbc6e8906ba", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/01964d92536edf1a3a874b9580a52824bebf6fbb", + "reference": "01964d92536edf1a3a874b9580a52824bebf6fbb", "shasum": "" }, "require": { @@ -4169,9 +4170,9 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.5.0" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.8.0" }, - "time": "2026-03-01T00:58:56+00:00" + "time": "2026-06-07T03:51:10+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -4220,22 +4221,86 @@ }, "time": "2026-01-25T14:56:51+00:00" }, + { + "name": "pixelandtonic/graphql-php", + "version": "v14.11.10.1", + "source": { + "type": "git", + "url": "https://github.com/pixelandtonic/graphql-php.git", + "reference": "fdb4a288878fc9ee449245e17209d676fc7c57fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pixelandtonic/graphql-php/zipball/fdb4a288878fc9ee449245e17209d676fc7c57fd", + "reference": "fdb4a288878fc9ee449245e17209d676fc7c57fd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.1 || ^8" + }, + "require-dev": { + "amphp/amp": "^2.3", + "doctrine/coding-standard": "^6.0", + "nyholm/psr7": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "0.12.82", + "phpstan/phpstan-phpunit": "0.12.18", + "phpstan/phpstan-strict-rules": "0.12.9", + "phpunit/phpunit": "^7.2 || ^8.5", + "psr/http-message": "^1.0", + "react/promise": "2.*", + "simpod/php-coveralls-mirror": "^3.0" + }, + "suggest": { + "psr/http-message": "To use standard GraphQL server", + "react/promise": "To leverage async resolving on React PHP platform" + }, + "type": "library", + "autoload": { + "psr-4": { + "GraphQL\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", + "keywords": [ + "api", + "graphql" + ], + "support": { + "source": "https://github.com/pixelandtonic/graphql-php/tree/v14.11.10.1" + }, + "funding": [ + { + "url": "https://opencollective.com/webonyx-graphql-php", + "type": "open_collective" + } + ], + "time": "2026-04-14T15:27:52+00:00" + }, { "name": "pixelandtonic/imagine", - "version": "1.3.3.1", + "version": "1.5.2.1", "source": { "type": "git", "url": "https://github.com/pixelandtonic/Imagine.git", - "reference": "4d9bb596ff60504e37ccf9103c0bb705dba7fec6" + "reference": "8e6c5cf929400142724b31482da51dc556277e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pixelandtonic/Imagine/zipball/4d9bb596ff60504e37ccf9103c0bb705dba7fec6", - "reference": "4d9bb596ff60504e37ccf9103c0bb705dba7fec6", + "url": "https://api.github.com/repos/pixelandtonic/Imagine/zipball/8e6c5cf929400142724b31482da51dc556277e15", + "reference": "8e6c5cf929400142724b31482da51dc556277e15", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3" @@ -4268,7 +4333,7 @@ "homepage": "http://avalanche123.com" } ], - "description": "Image processing for PHP 5.3", + "description": "Image processing for PHP", "homepage": "http://imagine.readthedocs.org/", "keywords": [ "drawing", @@ -4277,9 +4342,9 @@ "image processing" ], "support": { - "source": "https://github.com/pixelandtonic/Imagine/tree/1.3.3.1" + "source": "https://github.com/pixelandtonic/Imagine/tree/1.5.2.1" }, - "time": "2023-01-03T19:18:06+00:00" + "time": "2026-02-25T23:13:43+00:00" }, { "name": "pragmarx/google2fa", @@ -5218,20 +5283,20 @@ }, { "name": "spomky-labs/cbor-php", - "version": "3.2.2", + "version": "3.2.3", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0" + "reference": "dd6eb84e6d92f7b8bd0da56b4b4dd7235aed0c32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/2a5fb86aacfe1004611370ead6caa2bfc88435d0", - "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/dd6eb84e6d92f7b8bd0da56b4b4dd7235aed0c32", + "reference": "dd6eb84e6d92f7b8bd0da56b4b4dd7235aed0c32", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", "ext-mbstring": "*", "php": ">=8.0" }, @@ -5273,7 +5338,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.2" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.3" }, "funding": [ { @@ -5285,44 +5350,45 @@ "type": "patreon" } ], - "time": "2025-11-13T13:00:34+00:00" + "time": "2026-04-01T12:15:20+00:00" }, { "name": "spomky-labs/pki-framework", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "f0e9a548df4e3942886adc9b7830581a46334631" + "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/f0e9a548df4e3942886adc9b7830581a46334631", - "reference": "f0e9a548df4e3942886adc9b7830581a46334631", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/aa576cbd07128075bef97ac2f8af9854e67513d8", + "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", "ext-mbstring": "*", - "php": ">=8.1" + "php": ">=8.1", + "psr/clock": "^1.0" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29|^0.31", + "infection/infection": "^0.28|^0.29|^0.31|^0.32", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", "phpstan/phpstan-phpunit": "^1.1|^2.0", "phpstan/phpstan-strict-rules": "^1.3|^2.0", - "phpunit/phpunit": "^10.1|^11.0|^12.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0|^13.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", "symfony/string": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symplify/easy-coding-standard": "^12.0" + "symplify/easy-coding-standard": "^12.0|^13.0" }, "suggest": { "ext-bcmath": "For better performance (or GMP)", @@ -5382,7 +5448,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.1" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.2" }, "funding": [ { @@ -5394,20 +5460,20 @@ "type": "patreon" } ], - "time": "2025-12-20T12:57:40+00:00" + "time": "2026-03-23T22:56:56+00:00" }, { "name": "symfony/clock", - "version": "v7.4.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", "shasum": "" }, "require": { @@ -5452,7 +5518,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "source": "https://github.com/symfony/clock/tree/v7.4.8" }, "funding": [ { @@ -5472,20 +5538,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.6", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4", + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4", "shasum": "" }, "require": { @@ -5521,7 +5587,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.6" + "source": "https://github.com/symfony/css-selector/tree/v7.4.9" }, "funding": [ { @@ -5541,20 +5607,20 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -5567,7 +5633,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -5592,7 +5658,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -5603,25 +5669,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.4.6", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e" + "reference": "b59b59122690976550fd142c23fab62c84738db6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e", - "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b59b59122690976550fd142c23fab62c84738db6", + "reference": "b59b59122690976550fd142c23fab62c84738db6", "shasum": "" }, "require": { @@ -5660,7 +5730,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.12" }, "funding": [ { @@ -5680,20 +5750,20 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", "shasum": "" }, "require": { @@ -5745,7 +5815,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/v7.4.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" }, "funding": [ { @@ -5765,20 +5835,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -5792,7 +5862,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -5825,7 +5895,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -5836,25 +5906,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.34", + "version": "v6.4.39", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3" + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/01ffe0411b842f93c571e5c391f289c3fdd498c3", - "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c507b077756b4e3e09adbbe7975fac81cd3722ca", + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca", "shasum": "" }, "require": { @@ -5891,7 +5965,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.34" + "source": "https://github.com/symfony/filesystem/tree/v6.4.39" }, "funding": [ { @@ -5911,20 +5985,20 @@ "type": "tidelift" } ], - "time": "2026-02-24T17:51:06+00:00" + "time": "2026-05-07T13:11:42+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.7", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "1010624285470eb60e88ed10035102c75b4ea6af" + "reference": "e8a112b8415707265a7e614278136a9d92989a6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/1010624285470eb60e88ed10035102c75b4ea6af", - "reference": "1010624285470eb60e88ed10035102c75b4ea6af", + "url": "https://api.github.com/repos/symfony/http-client/zipball/e8a112b8415707265a7e614278136a9d92989a6a", + "reference": "e8a112b8415707265a7e614278136a9d92989a6a", "shasum": "" }, "require": { @@ -5992,7 +6066,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.7" + "source": "https://github.com/symfony/http-client/tree/v7.4.13" }, "funding": [ { @@ -6012,20 +6086,20 @@ "type": "tidelift" } ], - "time": "2026-03-05T11:16:58+00:00" + "time": "2026-05-24T09:57:54+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { @@ -6038,7 +6112,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6074,7 +6148,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" }, "funding": [ { @@ -6085,25 +6159,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-03-06T13:17:50+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.6", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff", + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff", "shasum": "" }, "require": { @@ -6154,7 +6232,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.6" + "source": "https://github.com/symfony/mailer/tree/v7.4.12" }, "funding": [ { @@ -6174,20 +6252,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/mime", - "version": "v7.4.7", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + "reference": "a845722765c4f6b2ce88beaf4f4479975b186770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "url": "https://api.github.com/repos/symfony/mime/zipball/a845722765c4f6b2ce88beaf4f4479975b186770", + "reference": "a845722765c4f6b2ce88beaf4f4479975b186770", "shasum": "" }, "require": { @@ -6243,7 +6321,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.7" + "source": "https://github.com/symfony/mime/tree/v7.4.13" }, "funding": [ { @@ -6263,20 +6341,20 @@ "type": "tidelift" } ], - "time": "2026-03-05T15:24:09+00:00" + "time": "2026-05-23T16:22:37+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -6326,7 +6404,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -6346,20 +6424,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { @@ -6408,7 +6486,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -6428,20 +6506,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { @@ -6495,7 +6573,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { @@ -6515,20 +6593,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -6580,7 +6658,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -6600,20 +6678,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -6665,7 +6743,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -6685,20 +6763,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { @@ -6749,7 +6827,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -6769,20 +6847,20 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", "shasum": "" }, "require": { @@ -6829,7 +6907,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" }, "funding": [ { @@ -6849,20 +6927,20 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-05-27T06:51:48+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { @@ -6909,7 +6987,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -6929,20 +7007,20 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", "shasum": "" }, "require": { @@ -6992,7 +7070,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -7012,7 +7090,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/process", @@ -7081,16 +7159,16 @@ }, { "name": "symfony/property-access", - "version": "v7.4.4", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1" + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", "shasum": "" }, "require": { @@ -7138,7 +7216,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.4" + "source": "https://github.com/symfony/property-access/tree/v7.4.8" }, "funding": [ { @@ -7158,20 +7236,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/property-info", - "version": "v7.4.7", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8" + "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/02501d75fd834345da3ecdd8e3200ced39e370f8", - "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8", + "url": "https://api.github.com/repos/symfony/property-info/zipball/ac5e82528b986c4f7cfccbf7764b5d2e824d6175", + "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175", "shasum": "" }, "require": { @@ -7228,7 +7306,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.7" + "source": "https://github.com/symfony/property-info/tree/v7.4.8" }, "funding": [ { @@ -7248,20 +7326,20 @@ "type": "tidelift" } ], - "time": "2026-03-04T15:53:26+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/serializer", - "version": "v7.4.7", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "bd395bbc6fabd136a48e1a6f91b09f88b5050b0b" + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/bd395bbc6fabd136a48e1a6f91b09f88b5050b0b", - "reference": "bd395bbc6fabd136a48e1a6f91b09f88b5050b0b", + "url": "https://api.github.com/repos/symfony/serializer/zipball/268c5aa6c4bd675eddd89348e7ecac292a843ddd", + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd", "shasum": "" }, "require": { @@ -7274,7 +7352,7 @@ "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/dependency-injection": "<6.4", - "symfony/property-access": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", "symfony/property-info": "<6.4", "symfony/type-info": "<7.2.5", "symfony/uid": "<6.4", @@ -7296,7 +7374,7 @@ "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/messenger": "^6.4|^7.0|^8.0", "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", "symfony/type-info": "^7.2.5|^8.0", @@ -7332,7 +7410,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/v7.4.7" + "source": "https://github.com/symfony/serializer/tree/v7.4.10" }, "funding": [ { @@ -7352,20 +7430,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:15:18+00:00" + "time": "2026-05-03T13:03:28+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -7383,7 +7461,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7419,7 +7497,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -7439,20 +7517,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", "shasum": "" }, "require": { @@ -7510,7 +7588,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v7.4.13" }, "funding": [ { @@ -7530,20 +7608,20 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-05-23T15:23:29+00:00" }, { "name": "symfony/translation", - "version": "v6.4.34", + "version": "v6.4.38", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "d07d117db41341511671b0a1a2be48f2772189ce" + "reference": "afaa31b0c12d9a659eed1ea97f268a614cc1299c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/d07d117db41341511671b0a1a2be48f2772189ce", - "reference": "d07d117db41341511671b0a1a2be48f2772189ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/afaa31b0c12d9a659eed1ea97f268a614cc1299c", + "reference": "afaa31b0c12d9a659eed1ea97f268a614cc1299c", "shasum": "" }, "require": { @@ -7609,7 +7687,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.34" + "source": "https://github.com/symfony/translation/tree/v6.4.38" }, "funding": [ { @@ -7629,20 +7707,20 @@ "type": "tidelift" } ], - "time": "2026-02-16T20:44:03+00:00" + "time": "2026-05-06T08:55:54+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", "shasum": "" }, "require": { @@ -7655,7 +7733,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7691,7 +7769,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" }, "funding": [ { @@ -7711,20 +7789,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/type-info", - "version": "v7.4.7", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269" + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/31f1e40cbf7851c7354281c90eb1b352c4cb8269", - "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269", + "url": "https://api.github.com/repos/symfony/type-info/zipball/cafeedbf157b890e94ac5b83eaed85595106d5d6", + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6", "shasum": "" }, "require": { @@ -7774,7 +7852,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.7" + "source": "https://github.com/symfony/type-info/tree/v7.4.9" }, "funding": [ { @@ -7794,20 +7872,20 @@ "type": "tidelift" } ], - "time": "2026-03-04T12:49:16+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/uid", - "version": "v7.4.4", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + "reference": "2676b524340abcfe4d6151ec698463cebafee439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "url": "https://api.github.com/repos/symfony/uid/zipball/2676b524340abcfe4d6151ec698463cebafee439", + "reference": "2676b524340abcfe4d6151ec698463cebafee439", "shasum": "" }, "require": { @@ -7852,7 +7930,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.4" + "source": "https://github.com/symfony/uid/tree/v7.4.9" }, "funding": [ { @@ -7872,20 +7950,20 @@ "type": "tidelift" } ], - "time": "2026-01-03T23:30:35+00:00" + "time": "2026-04-30T15:19:22+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", "shasum": "" }, "require": { @@ -7939,7 +8017,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" }, "funding": [ { @@ -7959,20 +8037,20 @@ "type": "tidelift" } ], - "time": "2026-02-15T10:53:20+00:00" + "time": "2026-03-30T13:44:50+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.6", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c", + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c", "shasum": "" }, "require": { @@ -8015,7 +8093,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.6" + "source": "https://github.com/symfony/yaml/tree/v7.4.13" }, "funding": [ { @@ -8035,7 +8113,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-05-25T06:06:12+00:00" }, { "name": "theiconic/name-parser", @@ -8087,16 +8165,16 @@ }, { "name": "twig/twig", - "version": "v3.21.1", + "version": "v3.27.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae2071bffb38f04847fc0864d730c94b9cb8ab74", + "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74", "shasum": "" }, "require": { @@ -8106,7 +8184,8 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "phpstan/phpstan": "^2.0", + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -8150,7 +8229,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.21.1" + "source": "https://github.com/twigphp/Twig/tree/v3.27.1" }, "funding": [ { @@ -8162,27 +8241,27 @@ "type": "tidelift" } ], - "time": "2025-05-03T07:21:55+00:00" + "time": "2026-05-30T17:09:26+00:00" }, { "name": "voku/portable-ascii", - "version": "2.0.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" }, "suggest": { "ext-intl": "Use Intl for transliterator_transliterate() support" @@ -8212,7 +8291,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { @@ -8236,24 +8315,24 @@ "type": "tidelift" } ], - "time": "2024-11-21T01:49:47+00:00" + "time": "2026-04-26T05:33:54+00:00" }, { "name": "web-auth/cose-lib", - "version": "4.5.0", + "version": "4.5.2", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "5adac6fe126994a3ee17ed9950efb4947ab132a9" + "reference": "5b38660f90070a8e45f3dbc9528ade3b608dd77d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/5adac6fe126994a3ee17ed9950efb4947ab132a9", - "reference": "5adac6fe126994a3ee17ed9950efb4947ab132a9", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/5b38660f90070a8e45f3dbc9528ade3b608dd77d", + "reference": "5b38660f90070a8e45f3dbc9528ade3b608dd77d", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", "ext-json": "*", "ext-openssl": "*", "php": ">=8.1", @@ -8295,7 +8374,7 @@ ], "support": { "issues": "https://github.com/web-auth/cose-lib/issues", - "source": "https://github.com/web-auth/cose-lib/tree/4.5.0" + "source": "https://github.com/web-auth/cose-lib/tree/4.5.2" }, "funding": [ { @@ -8307,20 +8386,20 @@ "type": "patreon" } ], - "time": "2026-01-03T14:43:18+00:00" + "time": "2026-05-03T09:49:50+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "5.2.4", + "version": "5.2.6", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "c346c9812d4d4a641f5ff26cd5fa4d0bf2035eeb" + "reference": "0785f55f242c1cc026ec24a9c8653eac59fe3493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/c346c9812d4d4a641f5ff26cd5fa4d0bf2035eeb", - "reference": "c346c9812d4d4a641f5ff26cd5fa4d0bf2035eeb", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/0785f55f242c1cc026ec24a9c8653eac59fe3493", + "reference": "0785f55f242c1cc026ec24a9c8653eac59fe3493", "shasum": "" }, "require": { @@ -8381,7 +8460,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.4" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.6" }, "funding": [ { @@ -8393,20 +8472,20 @@ "type": "patreon" } ], - "time": "2026-03-08T17:01:15+00:00" + "time": "2026-03-23T22:13:50+00:00" }, { "name": "webmozart/assert", - "version": "2.1.6", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", - "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", "shasum": "" }, "require": { @@ -8422,7 +8501,11 @@ }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, "branch-alias": { + "dev-master": "2.0-dev", "dev-feature/2-0": "2.0-dev" } }, @@ -8453,87 +8536,22 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.6" - }, - "time": "2026-02-27T10:28:38+00:00" - }, - { - "name": "webonyx/graphql-php", - "version": "v14.11.10", - "source": { - "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/d9c2fdebc6aa01d831bc2969da00e8588cffef19", - "reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.1 || ^8" - }, - "require-dev": { - "amphp/amp": "^2.3", - "doctrine/coding-standard": "^6.0", - "nyholm/psr7": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "0.12.82", - "phpstan/phpstan-phpunit": "0.12.18", - "phpstan/phpstan-strict-rules": "0.12.9", - "phpunit/phpunit": "^7.2 || ^8.5", - "psr/http-message": "^1.0", - "react/promise": "2.*", - "simpod/php-coveralls-mirror": "^3.0" - }, - "suggest": { - "psr/http-message": "To use standard GraphQL server", - "react/promise": "To leverage async resolving on React PHP platform" + "source": "https://github.com/webmozarts/assert/tree/2.4.0" }, - "type": "library", - "autoload": { - "psr-4": { - "GraphQL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", - "keywords": [ - "api", - "graphql" - ], - "support": { - "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v14.11.10" - }, - "funding": [ - { - "url": "https://opencollective.com/webonyx-graphql-php", - "type": "open_collective" - } - ], - "time": "2023-07-05T14:23:37+00:00" + "time": "2026-05-20T13:07:01+00:00" }, { "name": "yiisoft/yii2", - "version": "2.0.54", + "version": "2.0.55", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "99daebf2de0b031d129706a5db6ce0802c70bac9" + "reference": "b900eecdb225041a4c4e0f5e0e5336f606a23bdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/99daebf2de0b031d129706a5db6ce0802c70bac9", - "reference": "99daebf2de0b031d129706a5db6ce0802c70bac9", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/b900eecdb225041a4c4e0f5e0e5336f606a23bdb", + "reference": "b900eecdb225041a4c4e0f5e0e5336f606a23bdb", "shasum": "" }, "require": { @@ -8641,7 +8659,7 @@ "type": "tidelift" } ], - "time": "2026-01-09T22:24:11+00:00" + "time": "2026-05-09T14:50:57+00:00" }, { "name": "yiisoft/yii2-composer", @@ -11666,16 +11684,16 @@ }, { "name": "symfony/console", - "version": "v7.4.7", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", "shasum": "" }, "require": { @@ -11740,7 +11758,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.7" + "source": "https://github.com/symfony/console/tree/v7.4.13" }, "funding": [ { @@ -11760,20 +11778,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T14:06:20+00:00" + "time": "2026-05-24T08:56:14+00:00" }, { "name": "symfony/finder", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + "reference": "e0be088d22278583a82da281886e8c3592fbf149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", "shasum": "" }, "require": { @@ -11808,7 +11826,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.6" + "source": "https://github.com/symfony/finder/tree/v7.4.8" }, "funding": [ { @@ -11828,7 +11846,7 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:40:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symplify/easy-coding-standard", @@ -12032,5 +12050,5 @@ "ext-curl": "*" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/src/Module.php b/src/Module.php index 8e00e79..5d50fed 100644 --- a/src/Module.php +++ b/src/Module.php @@ -6,18 +6,21 @@ use craft\base\Event; use craft\base\Model; use craft\cloud\fs\AssetsFs; +use craft\cloud\imagetransforms\ImageEditor; use craft\cloud\imagetransforms\ImageTransformBehavior; use craft\cloud\imagetransforms\ImageTransformer; use craft\cloud\twig\TwigExtension; use craft\cloud\web\assets\uploader\UploaderAsset; use craft\cloud\web\ResponseEventHandler; use craft\console\Application as ConsoleApplication; +use craft\controllers\AssetsController as CraftAssetsController; use craft\elements\Asset; use craft\events\DefineBehaviorsEvent; use craft\events\DefineRulesEvent; use craft\events\GenerateTransformEvent; use craft\events\RegisterComponentTypesEvent; use craft\events\RegisterTemplateRootsEvent; +use craft\events\SaveAssetImageEvent; use craft\helpers\App; use craft\helpers\ConfigHelper; use craft\log\MonologTarget; @@ -93,6 +96,12 @@ public function bootstrap($app): void if ($this->getConfig()->useAssetCdn) { $app->getImages()->supportedImageFormats = ImageTransformer::SUPPORTED_IMAGE_FORMATS; + Event::on( + CraftAssetsController::class, + CraftAssetsController::EVENT_BEFORE_SAVE_IMAGE, + static fn(SaveAssetImageEvent $event) => (new ImageEditor())->handleSaveImage($event), + ); + Event::on( Asset::class, Asset::EVENT_BEFORE_GENERATE_TRANSFORM, diff --git a/src/imagetransforms/ImageEditor.php b/src/imagetransforms/ImageEditor.php new file mode 100644 index 0000000..5de0521 --- /dev/null +++ b/src/imagetransforms/ImageEditor.php @@ -0,0 +1,219 @@ +asset ?? null; + + if (!$asset instanceof Asset || !$asset->getVolume()->getFs() instanceof AssetsFs) { + return; + } + + $event->handled = true; + + try { + $editedAsset = $this->editImage( + $asset, + $event->replace, + $event->viewportRotation, + $event->imageRotation, + $event->cropData, + $event->focalPoint, + $event->imageDimensions, + $event->flipData, + $event->zoom, + ); + } catch (NotSupportedException $e) { + throw new BadRequestHttpException($e->getMessage(), 0, $e); + } catch (RequestException $e) { + $message = trim((string)$e->getResponse()?->getBody()) ?: $e->getMessage(); + + throw new BadRequestHttpException($message, 0, $e); + } + + $event->newAssetId = $event->replace ? null : $editedAsset->id; + } + + public function editImage( + Asset $asset, + bool $replace, + int $viewportRotation, + float $imageRotation, + array $cropData, + ?array $focalPoint, + array $imageDimensions, + ?array $flipData, + float $zoom, + ): Asset { + $focal = $this->focalPoint($asset, $focalPoint, $viewportRotation, $imageRotation, $cropData, $imageDimensions, $zoom); + $transform = ImageTransformer::fromImageEditor($asset, $viewportRotation, $imageRotation, $cropData, $imageDimensions, $flipData, $zoom, $focalPoint); + + if ($transform === null && !$this->focalPointChanged($asset, $focal)) { + return $replace ? $asset : $this->createAsset($asset, null, $focal); + } + + if ($replace) { + $this->replaceAsset($asset, $transform, $focal); + + return $asset; + } + + return $this->createAsset($asset, $transform, $focal); + } + + protected function focalPoint(Asset $asset, ?array $focalPoint, int $viewportRotation, float $imageRotation, array $cropData, array $imageDimensions, float $zoom): ?array + { + if (!$focalPoint) { + return null; + } + + if (!$asset->width || !$asset->height) { + throw new NotSupportedException('Image dimensions are required to edit images.'); + } + + $rotation = $this->rotation($viewportRotation, $imageRotation); + $crop = $this->crop($asset, $rotation, $cropData, $imageDimensions, $zoom); + $editedDimensions = $this->rotatedDimensions($asset->width, $asset->height, $rotation); + $adjustmentRatio = min( + $asset->width / $focalPoint['imageDimensions']['width'], + $asset->height / $focalPoint['imageDimensions']['height'], + ); + + return [ + 'x' => (($editedDimensions['width'] / 2) + ($focalPoint['offsetX'] * $zoom * $adjustmentRatio) - $crop['left']) / $crop['width'], + 'y' => (($editedDimensions['height'] / 2) + ($focalPoint['offsetY'] * $zoom * $adjustmentRatio) - $crop['top']) / $crop['height'], + ]; + } + + protected function crop(Asset $asset, int $rotation, array $cropData, array $imageDimensions, float $zoom): array + { + $adjustmentRatio = min( + $asset->width / $imageDimensions['width'], + $asset->height / $imageDimensions['height'], + ); + $editedDimensions = $this->rotatedDimensions($asset->width, $asset->height, $rotation); + + $width = (int)round($cropData['width'] * $zoom * $adjustmentRatio); + $height = (int)round($cropData['height'] * $zoom * $adjustmentRatio); + + return [ + 'left' => (int)round(($editedDimensions['width'] / 2) + ($cropData['offsetX'] * $zoom * $adjustmentRatio) - ($width / 2)), + 'top' => (int)round(($editedDimensions['height'] / 2) + ($cropData['offsetY'] * $zoom * $adjustmentRatio) - ($height / 2)), + 'width' => $width, + 'height' => $height, + ]; + } + + protected function rotation(int $viewportRotation, float $imageRotation): int + { + return ((int)round($imageRotation + $viewportRotation) % 360 + 360) % 360; + } + + protected function rotatedDimensions(int|float $width, int|float $height, int $rotation): array + { + return in_array($rotation, [90, 270], true) + ? ['width' => (int)round($height), 'height' => (int)round($width)] + : ['width' => (int)round($width), 'height' => (int)round($height)]; + } + + protected function focalPointChanged(Asset $asset, ?array $focal): bool + { + $oldFocal = $asset->getHasFocalPoint() ? $asset->getFocalPoint() : null; + + return $focal !== $oldFocal; + } + + protected function replaceAsset(Asset $asset, ?ImageTransform $transform, ?array $focal): void + { + $focalPointChanged = $this->focalPointChanged($asset, $focal); + $tempPath = null; + + $asset->setFocalPoint($focal); + + if ($focalPointChanged) { + Craft::$app->getImageTransforms()->deleteCreatedTransformsForAsset($asset); + } + + try { + if ($transform !== null) { + $tempPath = $this->downloadEditedImage($asset, $transform); + $asset->sanitizeOnUpload = false; + Craft::$app->getAssets()->replaceAssetFile($asset, $tempPath, $asset->getFilename()); + return; + } + + Craft::$app->getElements()->saveElement($asset); + } finally { + if ($tempPath !== null && file_exists($tempPath)) { + unlink($tempPath); + } + } + } + + protected function createAsset(Asset $asset, ?ImageTransform $transform, ?array $focal): Asset + { + $tempPath = null; + $newAsset = new Asset(); + $newAsset->avoidFilenameConflicts = true; + $newAsset->setScenario(Asset::SCENARIO_CREATE); + $newAsset->sanitizeOnUpload = false; + $newAsset->setFilename($asset->getFilename()); + $newAsset->newFolderId = $asset->folderId; + $newAsset->setVolumeId($asset->volumeId); + $newAsset->setFocalPoint($focal); + + try { + if ($transform !== null) { + $tempPath = $this->downloadEditedImage($asset, $transform); + } + + $newAsset->tempFilePath = $tempPath ?? $asset->getCopyOfFile(); + + Craft::$app->getElements()->saveElement($newAsset); + } finally { + if ($tempPath !== null && file_exists($tempPath)) { + unlink($tempPath); + } + } + + return $newAsset; + } + + protected function downloadEditedImage(Asset $asset, ImageTransform $transform): string + { + $path = sprintf( + '%s/%s.%s', + Craft::$app->getPath()->getTempPath(), + StringHelper::UUID(), + $asset->getExtension(), + ); + + try { + Craft::createGuzzleClient()->get((new ImageTransformer())->getTransformUrl($asset, $transform, true), [ + RequestOptions::SINK => $path, + ]); + } catch (\Throwable $e) { + if (file_exists($path)) { + unlink($path); + } + + throw $e; + } + + return $path; + } +} diff --git a/src/imagetransforms/ImageTransformBehavior.php b/src/imagetransforms/ImageTransformBehavior.php index 345b392..3922d38 100644 --- a/src/imagetransforms/ImageTransformBehavior.php +++ b/src/imagetransforms/ImageTransformBehavior.php @@ -8,7 +8,7 @@ use yii\base\Behavior; /** - * @see https://developers.cloudflare.com/images/transform-images/transform-via-workers/#fetch-options + * @see https://developers.cloudflare.com/images/optimization/features/#parameters * @see https://github.com/cloudflare/workerd/blob/main/types/defines/cf.d.ts * * @property ImageTransform $owner @@ -78,6 +78,11 @@ class ImageTransformBehavior extends Behavior */ public ?string $metadata = null; + /** + * @var int<1, 100>|'high'|'medium-high'|'medium-low'|'low'|null + */ + public int|string|null $quality = null; + /** * @var int|null */ @@ -121,6 +126,7 @@ public function toOptions(array|string|null $gravity = null): array $options['background'] = $this->computeBackground(); $options['gravity'] ??= $gravity ?? $this->computeGravity(); $options['height'] = $this->owner->height; + $options['quality'] ??= $this->owner->quality; $options['width'] = $this->owner->width; return Collection::make($options) diff --git a/src/imagetransforms/ImageTransformer.php b/src/imagetransforms/ImageTransformer.php index f898fe9..9e98ed8 100644 --- a/src/imagetransforms/ImageTransformer.php +++ b/src/imagetransforms/ImageTransformer.php @@ -26,6 +26,148 @@ class ImageTransformer extends Component implements ImageTransformerInterface private const SIGNING_PARAM = 's'; + public static function fromImageEditor( + Asset $asset, + int $viewportRotation, + float $imageRotation, + array $cropData, + array $imageDimensions, + ?array $flipData, + float $zoom, + ?array $focalPoint = null, + ): ?ImageTransform { + $rotation = ((int)round($imageRotation + $viewportRotation) % 360 + 360) % 360; + $flipX = !empty($flipData['x']); + $flipY = !empty($flipData['y']); + $flip = match (true) { + $flipX && $flipY => 'hv', + $flipX => 'h', + $flipY => 'v', + default => null, + }; + $sourceReferenceDimensions = self::rotatedDimensions($imageDimensions['width'], $imageDimensions['height'], 0); + $cropReferenceDimensions = self::rotatedDimensions($imageDimensions['width'], $imageDimensions['height'], $rotation); + $cropDimensions = [ + 'width' => (int)round($cropData['width']), + 'height' => (int)round($cropData['height']), + ]; + $imageCropped = $cropDimensions !== $sourceReferenceDimensions && $cropDimensions !== $cropReferenceDimensions; + $imageRotated = $rotation !== 0; + $imageFlipped = $flip !== null; + + if (!$imageCropped && !$imageRotated && !$imageFlipped) { + return null; + } + + if (!$asset->width || !$asset->height) { + throw new NotSupportedException('Image dimensions are required to edit images.'); + } + + $transform = new ImageTransform([ + 'width' => $asset->width, + 'height' => $asset->height, + ]); + + /** @var ImageTransformBehavior $behavior */ + $behavior = $transform->getBehavior('cloud'); + $behavior->fit = 'crop'; + + if ($zoom !== 1.0) { + $behavior->zoom = max(0, min(1, 1 - (1 / $zoom))); + } + + if ($imageCropped) { + $crop = self::crop($asset, $rotation, $cropData, $imageDimensions, $zoom); + + $transform->width = $crop['width']; + $transform->height = $crop['height']; + $behavior->trim = $crop; + } + + if ($imageRotated) { + $behavior->rotate = $rotation; + + if (in_array($rotation, [90, 270], true)) { + [$transform->width, $transform->height] = [$transform->height, $transform->width]; + } + } + + if ($imageFlipped) { + $behavior->flip = $flip; + } + + return $transform; + } + + private static function crop(Asset $asset, int $rotation, array $cropData, array $imageDimensions, float $zoom): array + { + $adjustmentRatio = min( + $asset->width / $imageDimensions['width'], + $asset->height / $imageDimensions['height'], + ); + $editedDimensions = self::rotatedDimensions($asset->width, $asset->height, $rotation); + + $width = (int)round($cropData['width'] * $zoom * $adjustmentRatio); + $height = (int)round($cropData['height'] * $zoom * $adjustmentRatio); + $crop = [ + 'left' => (int)round(($editedDimensions['width'] / 2) + ($cropData['offsetX'] * $zoom * $adjustmentRatio) - ($width / 2)), + 'top' => (int)round(($editedDimensions['height'] / 2) + ($cropData['offsetY'] * $zoom * $adjustmentRatio) - ($height / 2)), + 'width' => $width, + 'height' => $height, + ]; + + return self::constrainCrop( + self::sourceCrop($crop, $asset->width, $asset->height, $rotation), + $asset->width, + $asset->height, + ); + } + + private static function sourceCrop(array $crop, int $sourceWidth, int $sourceHeight, int $rotation): array + { + return match ($rotation) { + 90 => [ + 'left' => $crop['top'], + 'top' => $sourceHeight - $crop['left'] - $crop['width'], + 'width' => $crop['height'], + 'height' => $crop['width'], + ], + 180 => [ + 'left' => $sourceWidth - $crop['left'] - $crop['width'], + 'top' => $sourceHeight - $crop['top'] - $crop['height'], + 'width' => $crop['width'], + 'height' => $crop['height'], + ], + 270 => [ + 'left' => $sourceWidth - $crop['top'] - $crop['height'], + 'top' => $crop['left'], + 'width' => $crop['height'], + 'height' => $crop['width'], + ], + default => $crop, + }; + } + + private static function constrainCrop(array $crop, int $sourceWidth, int $sourceHeight): array + { + $left = max(0, $crop['left']); + $top = max(0, $crop['top']); + + return [ + 'left' => $left, + 'top' => $top, + 'width' => min($crop['width'], $sourceWidth - $left), + 'height' => min($crop['height'], $sourceHeight - $top), + ]; + } + + private static function rotatedDimensions(int|float $width, int|float $height, int $rotation): array + { + return in_array($rotation, [90, 270], true) + ? ['width' => (int)round($height), 'height' => (int)round($width)] + : ['width' => (int)round($width), 'height' => (int)round($height)]; + } + public function getTransformUrl(Asset $asset, ImageTransform $imageTransform, bool $immediately): string { if (version_compare(Craft::$app->version, '5.0', '>=')) { diff --git a/tests/unit/ImageTransformTest.php b/tests/unit/ImageTransformTest.php index 6a86308..1d1945d 100644 --- a/tests/unit/ImageTransformTest.php +++ b/tests/unit/ImageTransformTest.php @@ -6,13 +6,16 @@ use Craft; use craft\cloud\Module as CloudModule; use craft\cloud\fs\AssetsFs; +use craft\cloud\imagetransforms\ImageEditor; use craft\cloud\imagetransforms\ImageTransformBehavior; use craft\cloud\imagetransforms\ImageTransformer; use craft\elements\Asset; use craft\events\GenerateTransformEvent; +use craft\events\SaveAssetImageEvent; use craft\models\ImageTransform; use craft\models\Volume; use ReflectionProperty; +use yii\web\BadRequestHttpException; class ImageTransformTest extends Unit { @@ -103,6 +106,16 @@ public function testInlineCloudPropertyDoesNotBreakBaseTransform(): void ], $this->behavior($transform)->toOptions()); } + public function testQualityMapsToCloudflareOptions(): void + { + $transform = new ImageTransform([ + 'width' => 200, + 'quality' => 72, + ]); + + $this->assertSame(72, $this->behavior($transform)->toOptions()['quality']); + } + public function testGetTransformUrlDoesNotLeakGravityBetweenAssets(): void { $transform = new ImageTransform([ @@ -169,6 +182,249 @@ public function testSupportedInputFormatsMatchCloudflareImages(): void ], ImageTransformer::SUPPORTED_IMAGE_FORMATS); } + public function testImageEditorCropMapsToTrimTransform(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 4000, 3000, ['x' => 0.5, 'y' => 0.5]); + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 0, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 200, + 'offsetY' => -100, + 'width' => 600, + 'height' => 400, + ], + imageDimensions: [ + 'width' => 1000, + 'height' => 750, + ], + flipData: null, + zoom: 1.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(2400, $transform->width); + $this->assertSame(1600, $transform->height); + $this->assertSame([ + 'left' => 1600, + 'top' => 300, + 'width' => 2400, + 'height' => 1600, + ], $this->behavior($transform)->trim); + $this->assertSame('crop', $this->behavior($transform)->fit); + } + + public function testImageEditorRotationMapsToRotateTransform(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 4000, 3000, ['x' => 0.5, 'y' => 0.5]); + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 90, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 1000, + 'height' => 750, + ], + imageDimensions: [ + 'width' => 1000, + 'height' => 750, + ], + flipData: null, + zoom: 1.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(3000, $transform->width); + $this->assertSame(4000, $transform->height); + $this->assertSame(90, $this->behavior($transform)->rotate); + } + + public function testImageEditorRotationWithFullDisplayCropDoesNotTrim(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 568, 908, ['x' => 0.5, 'y' => 0.5]); + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 270, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 908, + 'height' => 568, + ], + imageDimensions: [ + 'width' => 568, + 'height' => 908, + ], + flipData: null, + zoom: 1.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(908, $transform->width); + $this->assertSame(568, $transform->height); + $this->assertSame(270, $this->behavior($transform)->rotate); + $this->assertNull($this->behavior($transform)->trim); + } + + public function testImageEditorRotatedCropMapsToSourceSpaceTrim(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 568, 908, ['x' => 0.5, 'y' => 0.5]); + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 270, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 454, + 'height' => 284, + ], + imageDimensions: [ + 'width' => 568, + 'height' => 908, + ], + flipData: null, + zoom: 1.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(454, $transform->width); + $this->assertSame(284, $transform->height); + $this->assertSame([ + 'left' => 142, + 'top' => 227, + 'width' => 284, + 'height' => 454, + ], $this->behavior($transform)->trim); + $this->assertSame(270, $this->behavior($transform)->rotate); + } + + public function testImageEditorCombinesCropRotationAndFlip(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 4000, 3000, ['x' => 0.5, 'y' => 0.5]); + + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 90, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 200, + 'offsetY' => -100, + 'width' => 600, + 'height' => 400, + ], + imageDimensions: [ + 'width' => 1000, + 'height' => 750, + ], + flipData: ['x' => true], + zoom: 1.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(2400, $transform->width); + $this->assertSame(1600, $transform->height); + $this->assertSame([ + 'left' => 800, + 'top' => 0, + 'width' => 1600, + 'height' => 2400, + ], $this->behavior($transform)->trim); + $this->assertSame(90, $this->behavior($transform)->rotate); + $this->assertSame('h', $this->behavior($transform)->flip); + } + + public function testImageEditorMapsZoomToCloudflareZoom(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 4000, 3000, ['x' => 0.25, 'y' => 0.75]); + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 0, + imageRotation: 0.0, + cropData: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 1000, + 'height' => 750, + ], + imageDimensions: [ + 'width' => 1000, + 'height' => 750, + ], + flipData: ['x' => true], + zoom: 2.0, + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(0.5, $this->behavior($transform)->zoom); + } + + public function testImageEditorAllowsRotationWithFocalPoint(): void + { + $asset = $this->makeUrlAssetStub(1, 'image.jpg', 4000, 3000, ['x' => 0.5, 'y' => 0.5]); + + $transform = ImageTransformer::fromImageEditor( + asset: $asset, + viewportRotation: 0, + imageRotation: 135.0, + cropData: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 1000, + 'height' => 750, + ], + imageDimensions: [ + 'width' => 1000, + 'height' => 750, + ], + flipData: null, + zoom: 1.0, + focalPoint: [ + 'offsetX' => 0, + 'offsetY' => 0, + 'imageDimensions' => [ + 'width' => 1000, + 'height' => 750, + ], + ], + ); + + $this->assertInstanceOf(ImageTransform::class, $transform); + $this->assertSame(135, $this->behavior($transform)->rotate); + } + + public function testImageEditorReportsMissingAssetDimensionsAsBadRequest(): void + { + $event = new SaveAssetImageEvent([ + 'asset' => new TransformDecisionAsset(), + 'replace' => true, + 'viewportRotation' => 0, + 'imageRotation' => 0.0, + 'cropData' => [ + 'offsetX' => 0, + 'offsetY' => 0, + 'width' => 500, + 'height' => 500, + ], + 'focalPoint' => null, + 'imageDimensions' => [ + 'width' => 1000, + 'height' => 750, + ], + 'flipData' => null, + 'zoom' => 1.0, + ]); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('Image dimensions are required to edit images.'); + + (new ImageEditor())->handleSaveImage($event); + } + private function setActionSegments(?array $actionSegments): void { $property = new ReflectionProperty(Craft::$app->getRequest(), '_actionSegments');