From 2f2ebd6293773d9dbc293f529b5ecf3cbe96d2d8 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Mon, 2 Jun 2025 16:07:48 +0200 Subject: [PATCH] Release 2.36.0 (#5514) --- composer.lock | 383 +++++++++--------- .../ValidationFailedExceptionErrorHandler.php | 36 +- src/API/TimesheetController.php | 10 +- src/Activity/ActivityService.php | 2 +- src/Constants.php | 4 +- .../TimesheetAbstractController.php | 6 +- src/Controller/TimesheetTeamController.php | 2 +- src/Customer/CustomerService.php | 2 +- src/Form/TimesheetEditForm.php | 5 +- src/Invoice/InvoiceModel.php | 4 +- src/Project/ProjectService.php | 2 +- src/Saml/SamlAuthenticator.php | 9 +- src/Timesheet/TimesheetService.php | 32 +- src/User/UserService.php | 2 +- .../Constraints/TimesheetNegativeDuration.php | 26 ++ .../TimesheetNegativeDurationValidator.php | 43 ++ .../TimesheetZeroDurationValidator.php | 2 +- src/Validator/ValidationException.php | 2 +- src/Validator/ValidationFailedException.php | 4 +- tests/Entity/TimesheetValidationTest.php | 2 +- tests/Timesheet/TimesheetServiceTest.php | 18 +- .../TimesheetNegativeDurationTest.php | 25 ++ ...TimesheetNegativeDurationValidatorTest.php | 90 ++++ tests/Validator/ValidationExceptionTest.php | 2 +- .../ValidationFailedExceptionTest.php | 2 +- translations/validators.de.xlf | 4 + translations/validators.en.xlf | 4 + 27 files changed, 466 insertions(+), 257 deletions(-) create mode 100644 src/Validator/Constraints/TimesheetNegativeDuration.php create mode 100644 src/Validator/Constraints/TimesheetNegativeDurationValidator.php create mode 100644 tests/Validator/Constraints/TimesheetNegativeDurationTest.php create mode 100644 tests/Validator/Constraints/TimesheetNegativeDurationValidatorTest.php diff --git a/composer.lock b/composer.lock index dedc5efe..3da546ae 100644 --- a/composer.lock +++ b/composer.lock @@ -2272,16 +2272,16 @@ }, { "name": "jms/serializer", - "version": "3.32.4", + "version": "3.32.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "f5c6227b2664d1e75fda65f1e6c5686a0c034b31" + "reference": "7c88b1b02ff868eecc870eeddbb3b1250e4bd89c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/f5c6227b2664d1e75fda65f1e6c5686a0c034b31", - "reference": "f5c6227b2664d1e75fda65f1e6c5686a0c034b31", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/7c88b1b02ff868eecc870eeddbb3b1250e4bd89c", + "reference": "7c88b1b02ff868eecc870eeddbb3b1250e4bd89c", "shasum": "" }, "require": { @@ -2358,7 +2358,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/3.32.4" + "source": "https://github.com/schmittjoh/serializer/tree/3.32.5" }, "funding": [ { @@ -2370,7 +2370,7 @@ "type": "github" } ], - "time": "2025-04-06T18:42:47+00:00" + "time": "2025-05-26T15:55:41+00:00" }, { "name": "jms/serializer-bundle", @@ -3202,16 +3202,16 @@ }, { "name": "nelmio/api-doc-bundle", - "version": "v5.2.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioApiDocBundle.git", - "reference": "f48d9cb3930a34df209547ffd86db01254d5fce8" + "reference": "60429836bab244d3c92f37b1c329af89aafd786f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/f48d9cb3930a34df209547ffd86db01254d5fce8", - "reference": "f48d9cb3930a34df209547ffd86db01254d5fce8", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/60429836bab244d3c92f37b1c329af89aafd786f", + "reference": "60429836bab244d3c92f37b1c329af89aafd786f", "shasum": "" }, "require": { @@ -3261,6 +3261,7 @@ "symfony/serializer": "^6.4 || ^7.1", "symfony/stopwatch": "^6.4 || ^7.1", "symfony/templating": "^6.4 || ^7.1", + "symfony/translation": "^6.4 || ^7.1", "symfony/twig-bundle": "^6.4 || ^7.1", "symfony/uid": "^6.4 || ^7.1", "symfony/validator": "^6.4 || ^7.1", @@ -3312,7 +3313,7 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioApiDocBundle/issues", - "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v5.2.0" + "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v5.3.0" }, "funding": [ { @@ -3320,7 +3321,7 @@ "type": "github" } ], - "time": "2025-05-23T21:30:13+00:00" + "time": "2025-05-24T13:30:55+00:00" }, { "name": "nelmio/cors-bundle", @@ -3386,16 +3387,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -3438,22 +3439,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "onelogin/php-saml", - "version": "4.2.0", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "d3b5172f137db2f412239432d77253ceaaa1e939" + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/d3b5172f137db2f412239432d77253ceaaa1e939", - "reference": "d3b5172f137db2f412239432d77253ceaaa1e939", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", "shasum": "" }, "require": { @@ -3504,7 +3505,7 @@ "type": "github" } ], - "time": "2024-05-30T15:10:40+00:00" + "time": "2025-05-25T14:28:00+00:00" }, { "name": "openspout/openspout", @@ -5299,16 +5300,16 @@ }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -5322,7 +5323,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5355,7 +5356,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -5371,7 +5372,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/clock", @@ -5449,16 +5450,16 @@ }, { "name": "symfony/config", - "version": "v6.4.14", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" + "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", + "url": "https://api.github.com/repos/symfony/config/zipball/af5917a3b1571f54689e56677a3f06440d2fe4c7", + "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7", "shasum": "" }, "require": { @@ -5504,7 +5505,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.14" + "source": "https://github.com/symfony/config/tree/v6.4.22" }, "funding": [ { @@ -5520,20 +5521,20 @@ "type": "tidelift" } ], - "time": "2024-11-04T11:33:53+00:00" + "time": "2025-05-14T06:00:01+00:00" }, { "name": "symfony/console", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719" + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a3011c7b7adb58d89f6c0d822abb641d7a5f9719", - "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719", + "url": "https://api.github.com/repos/symfony/console/zipball/7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", "shasum": "" }, "require": { @@ -5598,7 +5599,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.21" + "source": "https://github.com/symfony/console/tree/v6.4.22" }, "funding": [ { @@ -5614,7 +5615,7 @@ "type": "tidelift" } ], - "time": "2025-04-07T15:42:41+00:00" + "time": "2025-05-07T07:05:04+00:00" }, { "name": "symfony/css-selector", @@ -5683,16 +5684,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.20", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "c49796a9184a532843e78e50df9e55708b92543a" + "reference": "8cb11f833d1f5bfbb2df97dfc23c92b4d42c18d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c49796a9184a532843e78e50df9e55708b92543a", - "reference": "c49796a9184a532843e78e50df9e55708b92543a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8cb11f833d1f5bfbb2df97dfc23c92b4d42c18d9", + "reference": "8cb11f833d1f5bfbb2df97dfc23c92b4d42c18d9", "shasum": "" }, "require": { @@ -5744,7 +5745,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.20" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.22" }, "funding": [ { @@ -5760,20 +5761,20 @@ "type": "tidelift" } ], - "time": "2025-03-13T09:55:08+00:00" + "time": "2025-05-17T07:35:26+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -5786,7 +5787,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5811,7 +5812,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.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -5827,20 +5828,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "fcce66ede41ca56100b91fd4a00131ba6cf89aba" + "reference": "b880cebd0689a466cc4afef30009a54162c003c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/fcce66ede41ca56100b91fd4a00131ba6cf89aba", - "reference": "fcce66ede41ca56100b91fd4a00131ba6cf89aba", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/b880cebd0689a466cc4afef30009a54162c003c4", + "reference": "b880cebd0689a466cc4afef30009a54162c003c4", "shasum": "" }, "require": { @@ -5919,7 +5920,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.21" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.22" }, "funding": [ { @@ -5935,7 +5936,7 @@ "type": "tidelift" } ], - "time": "2025-04-27T15:22:02+00:00" + "time": "2025-05-09T14:04:38+00:00" }, { "name": "symfony/dotenv", @@ -6013,16 +6014,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.20", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "aa3bcf4f7674719df078e61cc8062e5b7f752031" + "reference": "ce765a2d28b3cce61de1fb916e207767a73171d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/aa3bcf4f7674719df078e61cc8062e5b7f752031", - "reference": "aa3bcf4f7674719df078e61cc8062e5b7f752031", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/ce765a2d28b3cce61de1fb916e207767a73171d1", + "reference": "ce765a2d28b3cce61de1fb916e207767a73171d1", "shasum": "" }, "require": { @@ -6068,7 +6069,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.20" + "source": "https://github.com/symfony/error-handler/tree/v6.4.22" }, "funding": [ { @@ -6084,7 +6085,7 @@ "type": "tidelift" } ], - "time": "2025-03-01T13:00:38+00:00" + "time": "2025-05-28T12:00:15+00:00" }, { "name": "symfony/event-dispatcher", @@ -6168,16 +6169,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -6191,7 +6192,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -6224,7 +6225,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -6240,7 +6241,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/expression-language", @@ -6438,16 +6439,16 @@ }, { "name": "symfony/flex", - "version": "v2.7.0", + "version": "v2.7.1", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "5d743b3b78fabe9f3146586d77b0a1f9292851fc" + "reference": "4ae50d368415a06820739e54d38a4a29d6df9155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/5d743b3b78fabe9f3146586d77b0a1f9292851fc", - "reference": "5d743b3b78fabe9f3146586d77b0a1f9292851fc", + "url": "https://api.github.com/repos/symfony/flex/zipball/4ae50d368415a06820739e54d38a4a29d6df9155", + "reference": "4ae50d368415a06820739e54d38a4a29d6df9155", "shasum": "" }, "require": { @@ -6486,7 +6487,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.7.0" + "source": "https://github.com/symfony/flex/tree/v2.7.1" }, "funding": [ { @@ -6502,20 +6503,20 @@ "type": "tidelift" } ], - "time": "2025-05-23T11:41:40+00:00" + "time": "2025-05-28T14:22:54+00:00" }, { "name": "symfony/form", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "44a0e253c16a3187299f07b8f80e23ecb000d360" + "reference": "2e2e32b7cc9e543a3f739b4968611a44ef17a2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/44a0e253c16a3187299f07b8f80e23ecb000d360", - "reference": "44a0e253c16a3187299f07b8f80e23ecb000d360", + "url": "https://api.github.com/repos/symfony/form/zipball/2e2e32b7cc9e543a3f739b4968611a44ef17a2de", + "reference": "2e2e32b7cc9e543a3f739b4968611a44ef17a2de", "shasum": "" }, "require": { @@ -6583,7 +6584,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v6.4.21" + "source": "https://github.com/symfony/form/tree/v6.4.22" }, "funding": [ { @@ -6599,20 +6600,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T15:22:02+00:00" + "time": "2025-05-04T06:52:29+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "d0b06133b00e4dd3df7f47a3188fb7baabcc6b2a" + "reference": "b1de19b2083484d0ce945977f6c6484e9e493a2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d0b06133b00e4dd3df7f47a3188fb7baabcc6b2a", - "reference": "d0b06133b00e4dd3df7f47a3188fb7baabcc6b2a", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b1de19b2083484d0ce945977f6c6484e9e493a2e", + "reference": "b1de19b2083484d0ce945977f6c6484e9e493a2e", "shasum": "" }, "require": { @@ -6732,7 +6733,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/v6.4.21" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.22" }, "funding": [ { @@ -6748,7 +6749,7 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-05-14T07:14:36+00:00" }, { "name": "symfony/http-client", @@ -6845,16 +6846,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -6867,7 +6868,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -6903,7 +6904,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -6919,20 +6920,20 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3f0c7ea41db479383b81d436b836d37168fd5b99" + "reference": "6b7c97fe1ddac8df3cc9ba6410c8abc683e148ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3f0c7ea41db479383b81d436b836d37168fd5b99", - "reference": "3f0c7ea41db479383b81d436b836d37168fd5b99", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b7c97fe1ddac8df3cc9ba6410c8abc683e148ae", + "reference": "6b7c97fe1ddac8df3cc9ba6410c8abc683e148ae", "shasum": "" }, "require": { @@ -6980,7 +6981,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.21" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.22" }, "funding": [ { @@ -6996,20 +6997,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-05-11T15:36:20+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "983ca05eec6623920d24ec0f1005f487d3734a0c" + "reference": "15c105b839a7cfa1bc0989c091bfb6477f23b673" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/983ca05eec6623920d24ec0f1005f487d3734a0c", - "reference": "983ca05eec6623920d24ec0f1005f487d3734a0c", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/15c105b839a7cfa1bc0989c091bfb6477f23b673", + "reference": "15c105b839a7cfa1bc0989c091bfb6477f23b673", "shasum": "" }, "require": { @@ -7094,7 +7095,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.21" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.22" }, "funding": [ { @@ -7110,20 +7111,20 @@ "type": "tidelift" } ], - "time": "2025-05-02T08:46:38+00:00" + "time": "2025-05-29T07:23:40+00:00" }, { "name": "symfony/intl", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "b248d227fa10fd6345efd4c1c74efaa1c1de6f76" + "reference": "aaecb52f18a6f95766a239ca0a6cc0df983d92cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/b248d227fa10fd6345efd4c1c74efaa1c1de6f76", - "reference": "b248d227fa10fd6345efd4c1c74efaa1c1de6f76", + "url": "https://api.github.com/repos/symfony/intl/zipball/aaecb52f18a6f95766a239ca0a6cc0df983d92cc", + "reference": "aaecb52f18a6f95766a239ca0a6cc0df983d92cc", "shasum": "" }, "require": { @@ -7177,7 +7178,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.4.21" + "source": "https://github.com/symfony/intl/tree/v6.4.22" }, "funding": [ { @@ -7193,7 +7194,7 @@ "type": "tidelift" } ], - "time": "2025-04-07T19:02:30+00:00" + "time": "2025-05-04T12:02:38+00:00" }, { "name": "symfony/mailer", @@ -8108,16 +8109,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.18", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68" + "reference": "1f5234e8457164a3a0038a4c0a4ba27876a9c670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e9bfc94953019089acdfb9be51c1b9142c4afa68", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68", + "url": "https://api.github.com/repos/symfony/routing/zipball/1f5234e8457164a3a0038a4c0a4ba27876a9c670", + "reference": "1f5234e8457164a3a0038a4c0a4ba27876a9c670", "shasum": "" }, "require": { @@ -8171,7 +8172,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.18" + "source": "https://github.com/symfony/routing/tree/v6.4.22" }, "funding": [ { @@ -8187,20 +8188,20 @@ "type": "tidelift" } ], - "time": "2025-01-09T08:51:02+00:00" + "time": "2025-04-27T16:08:38+00:00" }, { "name": "symfony/runtime", - "version": "v6.4.14", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "4facd4174f45cd37c65860403412b67c7381136a" + "reference": "832c3ce3b810509815050434ccb7ead68d06395b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/4facd4174f45cd37c65860403412b67c7381136a", - "reference": "4facd4174f45cd37c65860403412b67c7381136a", + "url": "https://api.github.com/repos/symfony/runtime/zipball/832c3ce3b810509815050434ccb7ead68d06395b", + "reference": "832c3ce3b810509815050434ccb7ead68d06395b", "shasum": "" }, "require": { @@ -8250,7 +8251,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v6.4.14" + "source": "https://github.com/symfony/runtime/tree/v6.4.22" }, "funding": [ { @@ -8266,20 +8267,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T16:39:55+00:00" + "time": "2025-05-07T21:15:03+00:00" }, { "name": "symfony/security-bundle", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "99b656ff6046ef217d4e3f852940de7e22489849" + "reference": "671ab5339a1e53923bfb8069bf984a47a412f612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/99b656ff6046ef217d4e3f852940de7e22489849", - "reference": "99b656ff6046ef217d4e3f852940de7e22489849", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/671ab5339a1e53923bfb8069bf984a47a412f612", + "reference": "671ab5339a1e53923bfb8069bf984a47a412f612", "shasum": "" }, "require": { @@ -8362,7 +8363,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/v6.4.21" + "source": "https://github.com/symfony/security-bundle/tree/v6.4.22" }, "funding": [ { @@ -8378,20 +8379,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-05-09T21:27:20+00:00" }, { "name": "symfony/security-core", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "c6e70da38436a9a49ed39d9cbead1ecf760f0fbd" + "reference": "110483f4e0106cf4bb63ed0479f6a5d09ab24a9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/c6e70da38436a9a49ed39d9cbead1ecf760f0fbd", - "reference": "c6e70da38436a9a49ed39d9cbead1ecf760f0fbd", + "url": "https://api.github.com/repos/symfony/security-core/zipball/110483f4e0106cf4bb63ed0479f6a5d09ab24a9e", + "reference": "110483f4e0106cf4bb63ed0479f6a5d09ab24a9e", "shasum": "" }, "require": { @@ -8448,7 +8449,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.4.21" + "source": "https://github.com/symfony/security-core/tree/v6.4.22" }, "funding": [ { @@ -8464,7 +8465,7 @@ "type": "tidelift" } ], - "time": "2025-04-17T07:43:34+00:00" + "time": "2025-05-20T14:15:13+00:00" }, { "name": "symfony/security-csrf", @@ -8536,16 +8537,16 @@ }, { "name": "symfony/security-http", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "67d0edaf6702c3192f27ad483df9a875c9a1f1a2" + "reference": "786c8eeee44b07419264ede2a795e8f490113dc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/67d0edaf6702c3192f27ad483df9a875c9a1f1a2", - "reference": "67d0edaf6702c3192f27ad483df9a875c9a1f1a2", + "url": "https://api.github.com/repos/symfony/security-http/zipball/786c8eeee44b07419264ede2a795e8f490113dc2", + "reference": "786c8eeee44b07419264ede2a795e8f490113dc2", "shasum": "" }, "require": { @@ -8604,7 +8605,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.4.21" + "source": "https://github.com/symfony/security-http/tree/v6.4.22" }, "funding": [ { @@ -8620,20 +8621,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:58:34+00:00" + "time": "2025-05-09T07:11:47+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "c45f8f7763afb11e85772c0c1debb8f272c17f51" + "reference": "b836df93e9ea07d1d3ada58a679ef205d54b64d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/c45f8f7763afb11e85772c0c1debb8f272c17f51", - "reference": "c45f8f7763afb11e85772c0c1debb8f272c17f51", + "url": "https://api.github.com/repos/symfony/serializer/zipball/b836df93e9ea07d1d3ada58a679ef205d54b64d1", + "reference": "b836df93e9ea07d1d3ada58a679ef205d54b64d1", "shasum": "" }, "require": { @@ -8702,7 +8703,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/v6.4.21" + "source": "https://github.com/symfony/serializer/tree/v6.4.22" }, "funding": [ { @@ -8718,20 +8719,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-05-12T08:02:50+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -8749,7 +8750,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -8785,7 +8786,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -8801,7 +8802,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/stopwatch", @@ -8953,16 +8954,16 @@ }, { "name": "symfony/translation", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bb92ea5588396b319ba43283a5a3087a034cb29c" + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bb92ea5588396b319ba43283a5a3087a034cb29c", - "reference": "bb92ea5588396b319ba43283a5a3087a034cb29c", + "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad", "shasum": "" }, "require": { @@ -9028,7 +9029,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.21" + "source": "https://github.com/symfony/translation/tree/v6.4.22" }, "funding": [ { @@ -9044,20 +9045,20 @@ "type": "tidelift" } ], - "time": "2025-04-07T19:02:30+00:00" + "time": "2025-05-29T07:06:44+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -9070,7 +9071,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -9106,7 +9107,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -9122,20 +9123,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "0457b7944bf1cc9c846c98d4923b5379ec6afc09" + "reference": "04ab306a2f2c9dbd46f4363383812954f704af9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/0457b7944bf1cc9c846c98d4923b5379ec6afc09", - "reference": "0457b7944bf1cc9c846c98d4923b5379ec6afc09", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/04ab306a2f2c9dbd46f4363383812954f704af9d", + "reference": "04ab306a2f2c9dbd46f4363383812954f704af9d", "shasum": "" }, "require": { @@ -9215,7 +9216,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.21" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.22" }, "funding": [ { @@ -9231,7 +9232,7 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-05-16T08:23:44+00:00" }, { "name": "symfony/twig-bundle", @@ -9319,16 +9320,16 @@ }, { "name": "symfony/validator", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "47610116f476595b90c368ff2a22514050712785" + "reference": "4c5fbccb2d8f64017c8dada6473701a5c8539716" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/47610116f476595b90c368ff2a22514050712785", - "reference": "47610116f476595b90c368ff2a22514050712785", + "url": "https://api.github.com/repos/symfony/validator/zipball/4c5fbccb2d8f64017c8dada6473701a5c8539716", + "reference": "4c5fbccb2d8f64017c8dada6473701a5c8539716", "shasum": "" }, "require": { @@ -9396,7 +9397,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.21" + "source": "https://github.com/symfony/validator/tree/v6.4.22" }, "funding": [ { @@ -9412,7 +9413,7 @@ "type": "tidelift" } ], - "time": "2025-04-30T18:50:04+00:00" + "time": "2025-05-29T07:03:46+00:00" }, { "name": "symfony/var-dumper", @@ -9501,16 +9502,16 @@ }, { "name": "symfony/var-exporter", - "version": "v6.4.21", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "717e7544aa99752c54ecba5c0e17459c48317472" + "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/717e7544aa99752c54ecba5c0e17459c48317472", - "reference": "717e7544aa99752c54ecba5c0e17459c48317472", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f28cf841f5654955c9f88ceaf4b9dc29571988a9", + "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9", "shasum": "" }, "require": { @@ -9558,7 +9559,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.21" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.22" }, "funding": [ { @@ -9574,7 +9575,7 @@ "type": "tidelift" } ], - "time": "2025-04-27T21:06:26+00:00" + "time": "2025-05-14T13:00:13+00:00" }, { "name": "symfony/webpack-encore-bundle", diff --git a/src/API/Serializer/ValidationFailedExceptionErrorHandler.php b/src/API/Serializer/ValidationFailedExceptionErrorHandler.php index d2c9868b..1ace17aa 100644 --- a/src/API/Serializer/ValidationFailedExceptionErrorHandler.php +++ b/src/API/Serializer/ValidationFailedExceptionErrorHandler.php @@ -63,24 +63,6 @@ final class ValidationFailedExceptionErrorHandler implements SubscribingHandlerI } public function serializeValidationExceptionToJson(JsonSerializationVisitor $visitor, ValidationFailedException $exception, array $type, Context $context) - { - $errors = []; - - /** @var ConstraintViolationInterface $error */ - foreach (iterator_to_array($exception->getViolations()) as $error) { - $errors[$error->getPropertyPath()]['errors'][] = $this->getErrorMessage($error); - } - - return [ - 'code' => '400', - 'message' => $this->translator->trans($exception->getMessage(), [], 'validators'), - 'errors' => [ - 'children' => $errors - ], - ]; - } - - private function getErrorMessage(ConstraintViolationInterface $error): string { $locale = \Locale::getDefault(); /** @var User $user */ @@ -90,6 +72,24 @@ final class ValidationFailedExceptionErrorHandler implements SubscribingHandlerI $locale = $user->getLanguage(); } + $errors = []; + + /** @var ConstraintViolationInterface $error */ + foreach (iterator_to_array($exception->getViolations()) as $error) { + $errors[$error->getPropertyPath()]['errors'][] = $this->getErrorMessage($error, $locale); + } + + return [ + 'code' => '400', + 'message' => $this->translator->trans($exception->getMessage(), [], 'validators', $locale), + 'errors' => [ + 'children' => $errors + ], + ]; + } + + private function getErrorMessage(ConstraintViolationInterface $error, string $locale): string + { if (null !== $error->getPlural()) { return $this->translator->trans($error->getMessageTemplate(), ['%count%' => $error->getPlural()] + $error->getParameters(), 'validators', $locale); } diff --git a/src/API/TimesheetController.php b/src/API/TimesheetController.php index cf058df6..c317a45f 100644 --- a/src/API/TimesheetController.php +++ b/src/API/TimesheetController.php @@ -315,7 +315,7 @@ final class TimesheetController extends BaseApiController if ($form->isValid()) { try { - $this->service->saveNewTimesheet($timesheet); + $this->service->saveTimesheet($timesheet); $view = new View($timesheet, 200); @@ -372,7 +372,7 @@ final class TimesheetController extends BaseApiController return $this->viewHandler->handle($view); } - $this->service->updateTimesheet($timesheet); + $this->service->saveTimesheet($timesheet); $view = new View($timesheet, Response::HTTP_OK); $view->getContext()->setGroups(self::GROUPS_ENTITY); @@ -547,7 +547,7 @@ final class TimesheetController extends BaseApiController $copyTimesheet = clone $timesheet; $this->dispatcher->dispatch(new TimesheetDuplicatePreEvent($copyTimesheet, $timesheet)); - $this->service->saveNewTimesheet($copyTimesheet); + $this->service->saveTimesheet($copyTimesheet); $this->dispatcher->dispatch(new TimesheetDuplicatePostEvent($copyTimesheet, $timesheet)); $view = new View($copyTimesheet, 200); @@ -571,7 +571,7 @@ final class TimesheetController extends BaseApiController $timesheet->setExported(!$timesheet->isExported()); - $this->service->updateTimesheet($timesheet); + $this->service->saveTimesheet($timesheet); $view = new View($timesheet, 200); $view->getContext()->setGroups(self::GROUPS_ENTITY); @@ -601,7 +601,7 @@ final class TimesheetController extends BaseApiController $meta->setValue($paramFetcher->get('value')); - $this->service->updateTimesheet($timesheet); + $this->service->saveTimesheet($timesheet); $view = new View($timesheet, 200); $view->getContext()->setGroups(self::GROUPS_ENTITY); diff --git a/src/Activity/ActivityService.php b/src/Activity/ActivityService.php index 3a429e7f..f862d900 100644 --- a/src/Activity/ActivityService.php +++ b/src/Activity/ActivityService.php @@ -97,7 +97,7 @@ class ActivityService $errors = $this->validator->validate($activity, null, $groups); if ($errors->count() > 0) { - throw new ValidationFailedException($errors, 'Validation Failed'); + throw new ValidationFailedException($errors); } } diff --git a/src/Constants.php b/src/Constants.php index ec7c800a..698f2ec2 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -17,11 +17,11 @@ final class Constants /** * The current release version */ - public const VERSION = '2.35.1'; + public const VERSION = '2.36.0'; /** * The current release: major * 10000 + minor * 100 + patch */ - public const VERSION_ID = 23501; + public const VERSION_ID = 23600; /** * The software name */ diff --git a/src/Controller/TimesheetAbstractController.php b/src/Controller/TimesheetAbstractController.php index 0568c0bc..4f7e58de 100644 --- a/src/Controller/TimesheetAbstractController.php +++ b/src/Controller/TimesheetAbstractController.php @@ -152,7 +152,7 @@ abstract class TimesheetAbstractController extends AbstractController if ($editForm->isSubmitted() && $editForm->isValid()) { try { - $this->service->updateTimesheet($entry); + $this->service->saveTimesheet($entry); $this->flashSuccess('action.update.success'); return $this->redirectToRoute($this->getTimesheetRoute()); @@ -184,7 +184,7 @@ abstract class TimesheetAbstractController extends AbstractController if ($createForm->isSubmitted() && $createForm->isValid()) { try { - $this->service->saveNewTimesheet($entry); + $this->service->saveTimesheet($entry); $this->flashSuccess('action.update.success'); return $this->redirectToRoute($this->getTimesheetRoute()); @@ -216,7 +216,7 @@ abstract class TimesheetAbstractController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { try { $this->dispatcher->dispatch(new TimesheetDuplicatePreEvent($copyTimesheet, $timesheet)); - $this->service->saveNewTimesheet($copyTimesheet); + $this->service->saveTimesheet($copyTimesheet); $this->dispatcher->dispatch(new TimesheetDuplicatePostEvent($copyTimesheet, $timesheet)); $this->flashSuccess('action.update.success'); diff --git a/src/Controller/TimesheetTeamController.php b/src/Controller/TimesheetTeamController.php index 63ad00c0..22e768ad 100644 --- a/src/Controller/TimesheetTeamController.php +++ b/src/Controller/TimesheetTeamController.php @@ -126,7 +126,7 @@ final class TimesheetTeamController extends TimesheetAbstractController } foreach ($newTimesheets as $newTimesheet) { - $this->service->saveNewTimesheet($newTimesheet); + $this->service->saveTimesheet($newTimesheet); } $this->flashSuccess('action.update.success'); diff --git a/src/Customer/CustomerService.php b/src/Customer/CustomerService.php index 51d0b762..c59150e5 100644 --- a/src/Customer/CustomerService.php +++ b/src/Customer/CustomerService.php @@ -101,7 +101,7 @@ final class CustomerService $errors = $this->validator->validate($customer, null, $groups); if ($errors->count() > 0) { - throw new ValidationFailedException($errors, 'Validation Failed'); + throw new ValidationFailedException($errors); } } diff --git a/src/Form/TimesheetEditForm.php b/src/Form/TimesheetEditForm.php index ac6dfe3b..67c85acd 100644 --- a/src/Form/TimesheetEditForm.php +++ b/src/Form/TimesheetEditForm.php @@ -59,8 +59,7 @@ class TimesheetEditForm extends AbstractType $timezone = $options['timezone']; $isNew = true; - if (isset($options['data'])) { - /** @var Timesheet $entry */ + if (isset($options['data']) && $options['data'] instanceof Timesheet) { $entry = $options['data']; $activity = $entry->getActivity(); @@ -338,7 +337,7 @@ class TimesheetEditForm extends AbstractType function (FormEvent $event) { /** @var Timesheet|null $timesheet */ $timesheet = $event->getData(); - if (null === $timesheet || $timesheet->isRunning()) { + if (null === $timesheet || ($timesheet instanceof Timesheet && $timesheet->isRunning())) { $event->getForm()->get('duration')->setData(null); } } diff --git a/src/Invoice/InvoiceModel.php b/src/Invoice/InvoiceModel.php index 625dbf4b..94108949 100644 --- a/src/Invoice/InvoiceModel.php +++ b/src/Invoice/InvoiceModel.php @@ -218,9 +218,7 @@ final class InvoiceModel } /** - * Returns the user who is currently creating the invoice. - * - * @return User|null + * Returns the user currently creating the invoice. */ public function getUser(): ?User { diff --git a/src/Project/ProjectService.php b/src/Project/ProjectService.php index 83130af1..bd991f0b 100644 --- a/src/Project/ProjectService.php +++ b/src/Project/ProjectService.php @@ -105,7 +105,7 @@ final class ProjectService $errors = $this->validator->validate($project, null, $groups); if ($errors->count() > 0) { - throw new ValidationFailedException($errors, 'Validation Failed'); + throw new ValidationFailedException($errors); } } diff --git a/src/Saml/SamlAuthenticator.php b/src/Saml/SamlAuthenticator.php index 45bb7674..a41e8673 100644 --- a/src/Saml/SamlAuthenticator.php +++ b/src/Saml/SamlAuthenticator.php @@ -12,6 +12,7 @@ namespace App\Saml; use App\Configuration\SamlConfigurationInterface; use App\Saml\Security\SamlAuthenticationFailureHandler; use App\Saml\Security\SamlAuthenticationSuccessHandler; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -40,7 +41,8 @@ class SamlAuthenticator extends AbstractAuthenticator private readonly SamlAuthenticationFailureHandler $failureHandler, private readonly SamlAuthFactory $samlAuthFactory, private readonly SamlProvider $samlProvider, - private readonly SamlConfigurationInterface $configuration + private readonly SamlConfigurationInterface $configuration, + private readonly LoggerInterface $logger ) { } @@ -86,6 +88,7 @@ class SamlAuthenticator extends AbstractAuthenticator // file_put_contents(__DIR__ . '/../../var/log/saml.xml', $oneLoginAuth->getLastResponseXML()); if (\count($oneLoginAuth->getErrors()) > 0) { + $this->logger->critical('SAML login failed: ' . $oneLoginAuth->getLastErrorReason()); throw new AuthenticationException($oneLoginAuth->getLastErrorReason()); } @@ -102,7 +105,9 @@ class SamlAuthenticator extends AbstractAuthenticator if (isset($this->options['username_attribute'])) { if (!\array_key_exists($this->options['username_attribute'], $attributes)) { - throw new \Exception(\sprintf("Attribute '%s' not found in SAML data", $this->options['username_attribute'])); + $errorMessage = \sprintf("Attribute '%s' not found in SAML data", $this->options['username_attribute']); + $this->logger->critical($errorMessage); + throw new \Exception($errorMessage); } $username = $attributes[$this->options['username_attribute']][0]; diff --git a/src/Timesheet/TimesheetService.php b/src/Timesheet/TimesheetService.php index 6a8ef9ec..ae0707cc 100644 --- a/src/Timesheet/TimesheetService.php +++ b/src/Timesheet/TimesheetService.php @@ -45,12 +45,12 @@ final class TimesheetService private array $doNotValidateCodes = []; public function __construct( - private SystemConfiguration $configuration, - private TimesheetRepository $repository, - private TrackingModeService $trackingModeService, - private EventDispatcherInterface $dispatcher, - private AuthorizationCheckerInterface $auth, - private ValidatorInterface $validator + private readonly SystemConfiguration $configuration, + private readonly TimesheetRepository $repository, + private readonly TrackingModeService $trackingModeService, + private readonly EventDispatcherInterface $dispatcher, + private readonly AuthorizationCheckerInterface $auth, + private readonly ValidatorInterface $validator ) { } @@ -101,7 +101,7 @@ final class TimesheetService public function restartTimesheet(Timesheet $timesheet, Timesheet $copyFrom): Timesheet { $this->dispatcher->dispatch(new TimesheetRestartPreEvent($timesheet, $copyFrom)); - $this->saveNewTimesheet($timesheet); + $this->saveNewTimesheet($timesheet); // @phpstan-ignore method.deprecated $this->dispatcher->dispatch(new TimesheetRestartPostEvent($timesheet, $copyFrom)); return $timesheet; @@ -111,6 +111,7 @@ final class TimesheetService * @throws ValidationFailedException for invalid timesheets or running timesheets that should be stopped * @throws InvalidArgumentException for already persisted timesheets * @throws AccessDeniedException if user is not allowed to start timesheet + * @deprecated since 2.36.0 - use saveTimesheet() instead */ public function saveNewTimesheet(Timesheet $timesheet): Timesheet { @@ -150,12 +151,19 @@ final class TimesheetService return $timesheet; } + public function saveTimesheet(Timesheet $timesheet): Timesheet + { + if ($timesheet->getId() === null) { + return $this->saveNewTimesheet($timesheet); // @phpstan-ignore method.deprecated + } else { + return $this->updateTimesheet($timesheet); // @phpstan-ignore method.deprecated + } + } + /** - * Does NOT validate the given timesheet! + * Does NOT validate the given timesheet. * - * @param Timesheet $timesheet - * @return Timesheet - * @throws \Exception + * @deprecated since 2.36.0 - use saveTimesheet() instead */ public function updateTimesheet(Timesheet $timesheet): Timesheet { @@ -245,7 +253,7 @@ final class TimesheetService continue; } - throw new ValidationFailedException($errors, 'Validation Failed'); + throw new ValidationFailedException($errors); } } } diff --git a/src/User/UserService.php b/src/User/UserService.php index 1852a240..baa8aedf 100644 --- a/src/User/UserService.php +++ b/src/User/UserService.php @@ -112,7 +112,7 @@ class UserService $errors = $this->validator->validate($user, null, $groups); if ($errors->count() > 0) { - throw new ValidationFailedException($errors, 'Validation Failed'); + throw new ValidationFailedException($errors); } } diff --git a/src/Validator/Constraints/TimesheetNegativeDuration.php b/src/Validator/Constraints/TimesheetNegativeDuration.php new file mode 100644 index 00000000..2e884ee1 --- /dev/null +++ b/src/Validator/Constraints/TimesheetNegativeDuration.php @@ -0,0 +1,26 @@ + 'Duration cannot be negative.', + ]; + + public string $message = 'Duration cannot be negative.'; + + public function getTargets(): string + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/Validator/Constraints/TimesheetNegativeDurationValidator.php b/src/Validator/Constraints/TimesheetNegativeDurationValidator.php new file mode 100644 index 00000000..72bc8f50 --- /dev/null +++ b/src/Validator/Constraints/TimesheetNegativeDurationValidator.php @@ -0,0 +1,43 @@ +isRunning()) { + return; + } + + $duration = $value->getCalculatedDuration(); + + if ($duration !== null && $duration < 0) { + $this->context->buildViolation($constraint->message) + ->atPath('duration') + ->setTranslationDomain('validators') + ->setCode(TimesheetNegativeDuration::NEGATIVE_DURATION_ERROR) + ->addViolation(); + } + } +} diff --git a/src/Validator/Constraints/TimesheetZeroDurationValidator.php b/src/Validator/Constraints/TimesheetZeroDurationValidator.php index 347ff439..bfdd6bad 100644 --- a/src/Validator/Constraints/TimesheetZeroDurationValidator.php +++ b/src/Validator/Constraints/TimesheetZeroDurationValidator.php @@ -44,7 +44,7 @@ final class TimesheetZeroDurationValidator extends ConstraintValidator $duration = $value->getCalculatedDuration(); } - if ($duration <= 0) { + if ($duration === 0) { $this->context->buildViolation($constraint->message) ->atPath('duration') ->setTranslationDomain('validators') diff --git a/src/Validator/ValidationException.php b/src/Validator/ValidationException.php index 1dd242ba..4c34d3dd 100644 --- a/src/Validator/ValidationException.php +++ b/src/Validator/ValidationException.php @@ -14,7 +14,7 @@ final class ValidationException extends \RuntimeException public function __construct(string $message = null) { if ($message === null) { - $message = 'Validation failed'; + $message = 'Validation Failed'; } parent::__construct($message, 400); } diff --git a/src/Validator/ValidationFailedException.php b/src/Validator/ValidationFailedException.php index 5150ebac..24940b22 100644 --- a/src/Validator/ValidationFailedException.php +++ b/src/Validator/ValidationFailedException.php @@ -13,10 +13,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; final class ValidationFailedException extends \RuntimeException { - public function __construct(private ConstraintViolationListInterface $violations, ?string $message = null) + public function __construct(private readonly ConstraintViolationListInterface $violations, ?string $message = null) { if ($message === null) { - $message = 'Validation failed'; + $message = 'Validation Failed'; } parent::__construct($message, 400); } diff --git a/tests/Entity/TimesheetValidationTest.php b/tests/Entity/TimesheetValidationTest.php index 16e724ee..12402162 100644 --- a/tests/Entity/TimesheetValidationTest.php +++ b/tests/Entity/TimesheetValidationTest.php @@ -251,7 +251,7 @@ class TimesheetValidationTest extends KernelTestCase $entity->setBegin($begin); $entity->setEnd($end); - $this->assertHasViolationForField($entity, 'end_date'); + $this->assertHasViolationForField($entity, ['end_date', 'duration']); // allow same begin and end $entity = $this->getEntity(); diff --git a/tests/Timesheet/TimesheetServiceTest.php b/tests/Timesheet/TimesheetServiceTest.php index c9784bbe..b5d773ba 100644 --- a/tests/Timesheet/TimesheetServiceTest.php +++ b/tests/Timesheet/TimesheetServiceTest.php @@ -65,6 +65,9 @@ class TimesheetServiceTest extends TestCase return $service; } + /** + * @group legacy + */ public function testCannotSavePersistedTimesheetAsNew(): void { $timesheet = $this->createMock(Timesheet::class); @@ -75,7 +78,7 @@ class TimesheetServiceTest extends TestCase $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Cannot create timesheet, already persisted'); - $sut->saveNewTimesheet($timesheet); + $sut->saveNewTimesheet($timesheet); // @phpstan-ignore method.deprecated } public function testCannotStartTimesheet(): void @@ -88,7 +91,7 @@ class TimesheetServiceTest extends TestCase $this->expectException(AccessDeniedHttpException::class); $this->expectExceptionMessage('You are not allowed to start this timesheet record'); - $sut->saveNewTimesheet(new Timesheet()); + $sut->saveTimesheet(new Timesheet()); } public function testSaveNewTimesheetHasValidationError(): void @@ -107,7 +110,7 @@ class TimesheetServiceTest extends TestCase $this->expectException(ValidationFailedException::class); $this->expectExceptionMessage('Validation Failed'); - $sut->saveNewTimesheet(new Timesheet()); + $sut->saveTimesheet(new Timesheet()); } public function testSaveNewTimesheetStopsActiveRecords(): void @@ -134,7 +137,7 @@ class TimesheetServiceTest extends TestCase $sut = $this->getSut($authorizationChecker, null, null, $repository); - $sut->saveNewTimesheet($newTimesheet); + $sut->saveTimesheet($newTimesheet); } public function testSaveNewTimesheetFixesTimezone(): void @@ -155,11 +158,14 @@ class TimesheetServiceTest extends TestCase $authorizationChecker->expects($this->once())->method('isGranted')->willReturn(true); $sut = $this->getSut($authorizationChecker); - $sut->saveNewTimesheet($timesheet); + $sut->saveTimesheet($timesheet); self::assertEquals('Europe/Paris', $timesheet->getTimezone()); } + /** + * @group legacy + */ public function testUpdateTimesheetFixesTimezone(): void { $user = new User(); @@ -176,7 +182,7 @@ class TimesheetServiceTest extends TestCase $sut = $this->getSut(); - $sut->updateTimesheet($timesheet); + $sut->updateTimesheet($timesheet); // @phpstan-ignore method.deprecated self::assertEquals('Europe/Paris', $timesheet->getTimezone()); } diff --git a/tests/Validator/Constraints/TimesheetNegativeDurationTest.php b/tests/Validator/Constraints/TimesheetNegativeDurationTest.php new file mode 100644 index 00000000..242b44b9 --- /dev/null +++ b/tests/Validator/Constraints/TimesheetNegativeDurationTest.php @@ -0,0 +1,25 @@ + + */ +class TimesheetNegativeDurationValidatorTest extends ConstraintValidatorTestCase +{ + protected function createValidator(): TimesheetNegativeDurationValidator + { + return new TimesheetNegativeDurationValidator(); + } + + public function testConstraintIsInvalid(): void + { + $this->expectException(UnexpectedTypeException::class); + + $this->validator->validate(new Timesheet(), new NotBlank()); + } + + public function testInvalidValueThrowsException(): void + { + $this->expectException(UnexpectedTypeException::class); + + $this->validator->validate(new NotBlank(), new TimesheetNegativeDuration(['message' => 'Duration cannot be negative.'])); + } + + public function testNegativeDurationIsNotAllowed(): void + { + $begin = new \DateTime(); + $timesheet = new Timesheet(); + $timesheet->setBegin(clone $begin); + $timesheet->setEnd(clone $begin); + $timesheet->setBreak(3600); + + $this->validator->validate($timesheet, new TimesheetNegativeDuration(['message' => 'Duration cannot be negative.'])); + + $this->buildViolation('Duration cannot be negative.') + ->atPath('property.path.duration') + ->setCode(TimesheetNegativeDuration::NEGATIVE_DURATION_ERROR) + ->assertRaised(); + } + + public function testZeroDurationIsAllowed(): void + { + $begin = new \DateTime(); + $timesheet = new Timesheet(); + $timesheet->setBegin(clone $begin); + $timesheet->setEnd(clone $begin); + + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $this->validator->validate($timesheet, new TimesheetNegativeDuration(['message' => 'Duration cannot be negative.'])); + + $this->assertNoViolation(); + } + + public function testDoesNotTriggerOnRunningTimesheet(): void + { + $begin = new \DateTime(); + $timesheet = new Timesheet(); + $timesheet->setBegin(clone $begin); + $timesheet->setBreak(3600); + + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $this->validator->validate($timesheet, new TimesheetNegativeDuration(['message' => 'Duration cannot be negative.'])); + + $this->assertNoViolation(); + } +} diff --git a/tests/Validator/ValidationExceptionTest.php b/tests/Validator/ValidationExceptionTest.php index 9d960aa5..d0dd60a3 100644 --- a/tests/Validator/ValidationExceptionTest.php +++ b/tests/Validator/ValidationExceptionTest.php @@ -21,7 +21,7 @@ class ValidationExceptionTest extends TestCase { $sut = new ValidationException(); self::assertEquals(400, $sut->getCode()); - self::assertEquals('Validation failed', $sut->getMessage()); + self::assertEquals('Validation Failed', $sut->getMessage()); } public function testConstruct(): void diff --git a/tests/Validator/ValidationFailedExceptionTest.php b/tests/Validator/ValidationFailedExceptionTest.php index 67c96d2d..2cc9f4e8 100644 --- a/tests/Validator/ValidationFailedExceptionTest.php +++ b/tests/Validator/ValidationFailedExceptionTest.php @@ -23,7 +23,7 @@ class ValidationFailedExceptionTest extends TestCase $list = new ConstraintViolationList(); $sut = new ValidationFailedException($list); self::assertEquals(400, $sut->getCode()); - self::assertEquals('Validation failed', $sut->getMessage()); + self::assertEquals('Validation Failed', $sut->getMessage()); self::assertSame($list, $sut->getViolations()); } diff --git a/translations/validators.de.xlf b/translations/validators.de.xlf index 370e6723..3544ac33 100644 --- a/translations/validators.de.xlf +++ b/translations/validators.de.xlf @@ -162,6 +162,10 @@ Selected period cannot be locked: unconfirmed absence requests are pending. Ausgewählter Zeitraum kann nicht gesperrt werden: unbestätigte Abwesenheitsanträge stehen an. + + Duration cannot be negative. + Die Dauer kann nicht negativ sein. + diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 7d5cf84d..e3df7631 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -162,6 +162,10 @@ Selected period cannot be locked: unconfirmed absence requests are pending. Selected period cannot be locked: unconfirmed absence requests are pending. + + Duration cannot be negative. + Duration cannot be negative. +