Generated
+174
-171
@@ -8,7 +8,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/doctrine-common",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/doctrine-common.git",
|
||||
@@ -89,13 +89,13 @@
|
||||
"rest"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/doctrine-common/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/doctrine-common/tree/v4.0.18"
|
||||
},
|
||||
"time": "2024-09-21T11:01:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/doctrine-orm",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/doctrine-orm.git",
|
||||
@@ -173,13 +173,13 @@
|
||||
"rest"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/doctrine-orm/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/doctrine-orm/tree/v4.0.18"
|
||||
},
|
||||
"time": "2024-12-13T13:34:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/documentation",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/documentation.git",
|
||||
@@ -234,13 +234,13 @@
|
||||
],
|
||||
"description": "API Platform documentation controller.",
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/documentation/tree/v4.1.0-beta.1"
|
||||
"source": "https://github.com/api-platform/documentation/tree/v4.1.0-beta.2"
|
||||
},
|
||||
"time": "2024-09-21T11:01:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/http-cache",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/http-cache.git",
|
||||
@@ -311,13 +311,13 @@
|
||||
"rest"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/http-cache/tree/v4.1.0-beta.1"
|
||||
"source": "https://github.com/api-platform/http-cache/tree/v4.1.0-beta.2"
|
||||
},
|
||||
"time": "2024-10-28T09:21:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/hydra",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/hydra.git",
|
||||
@@ -395,13 +395,13 @@
|
||||
"rest"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/hydra/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/hydra/tree/v4.0.18"
|
||||
},
|
||||
"time": "2024-11-06T10:50:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/json-schema",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/json-schema.git",
|
||||
@@ -479,7 +479,7 @@
|
||||
},
|
||||
{
|
||||
"name": "api-platform/jsonld",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/jsonld.git",
|
||||
@@ -550,13 +550,13 @@
|
||||
"rest"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/jsonld/tree/v4.1.0-beta.1"
|
||||
"source": "https://github.com/api-platform/jsonld/tree/v4.1.0-beta.2"
|
||||
},
|
||||
"time": "2024-12-19T16:01:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/metadata",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/metadata.git",
|
||||
@@ -646,22 +646,22 @@
|
||||
"swagger"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/metadata/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/metadata/tree/v4.0.18"
|
||||
},
|
||||
"time": "2025-02-07T15:44:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/openapi",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/openapi.git",
|
||||
"reference": "3145c45ad6e668e20beff2653c1f245296c33d1c"
|
||||
"reference": "9d96b31ad3eb524a2cb6f89f835deca858beacd0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/api-platform/openapi/zipball/3145c45ad6e668e20beff2653c1f245296c33d1c",
|
||||
"reference": "3145c45ad6e668e20beff2653c1f245296c33d1c",
|
||||
"url": "https://api.github.com/repos/api-platform/openapi/zipball/9d96b31ad3eb524a2cb6f89f835deca858beacd0",
|
||||
"reference": "9d96b31ad3eb524a2cb6f89f835deca858beacd0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -731,13 +731,13 @@
|
||||
"swagger"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/openapi/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/openapi/tree/v4.0.18"
|
||||
},
|
||||
"time": "2025-01-09T14:23:11+00:00"
|
||||
"time": "2025-02-21T09:07:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/serializer",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/serializer.git",
|
||||
@@ -821,13 +821,13 @@
|
||||
"serializer"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/serializer/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/serializer/tree/v4.1.0-beta.2"
|
||||
},
|
||||
"time": "2025-02-06T09:32:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/state",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/state.git",
|
||||
@@ -911,22 +911,22 @@
|
||||
"swagger"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/state/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/state/tree/v4.0.18"
|
||||
},
|
||||
"time": "2025-01-13T10:20:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/symfony",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/symfony.git",
|
||||
"reference": "ea372108b0367ce027d28beef0dbadb454e81ca1"
|
||||
"reference": "83bae6046fc3aaa7ebc0f78f91ce2e4143b5840d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/api-platform/symfony/zipball/ea372108b0367ce027d28beef0dbadb454e81ca1",
|
||||
"reference": "ea372108b0367ce027d28beef0dbadb454e81ca1",
|
||||
"url": "https://api.github.com/repos/api-platform/symfony/zipball/83bae6046fc3aaa7ebc0f78f91ce2e4143b5840d",
|
||||
"reference": "83bae6046fc3aaa7ebc0f78f91ce2e4143b5840d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1034,13 +1034,13 @@
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/api-platform/symfony/tree/v4.0.17"
|
||||
"source": "https://github.com/api-platform/symfony/tree/v4.0.18"
|
||||
},
|
||||
"time": "2025-02-06T09:04:42+00:00"
|
||||
"time": "2025-02-14T09:53:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "api-platform/validator",
|
||||
"version": "v4.0.17",
|
||||
"version": "v4.0.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/api-platform/validator.git",
|
||||
@@ -2872,25 +2872,24 @@
|
||||
},
|
||||
{
|
||||
"name": "gedmo/doctrine-extensions",
|
||||
"version": "v3.17.1",
|
||||
"version": "v3.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine-extensions/DoctrineExtensions.git",
|
||||
"reference": "eabb45018c5a4362b46c5beae3881261da89f900"
|
||||
"reference": "5b0b8a442d19e6701ae64535dc08f7944e2895d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/eabb45018c5a4362b46c5beae3881261da89f900",
|
||||
"reference": "eabb45018c5a4362b46c5beae3881261da89f900",
|
||||
"url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/5b0b8a442d19e6701ae64535dc08f7944e2895d2",
|
||||
"reference": "5b0b8a442d19e6701ae64535dc08f7944e2895d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/transliterator": "^1.2",
|
||||
"doctrine/collections": "^1.2 || ^2.0",
|
||||
"doctrine/common": "^2.13 || ^3.0",
|
||||
"doctrine/deprecations": "^1.0",
|
||||
"doctrine/event-manager": "^1.2 || ^2.0",
|
||||
"doctrine/persistence": "^2.2 || ^3.0",
|
||||
"doctrine/persistence": "^2.2 || ^3.0 || ^4.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"psr/clock": "^1",
|
||||
@@ -2898,24 +2897,26 @@
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >=3.0",
|
||||
"doctrine/common": "<2.13 || >=4.0",
|
||||
"doctrine/dbal": "<3.7 || >=5.0",
|
||||
"doctrine/mongodb-odm": "<2.3 || >=3.0",
|
||||
"doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1 || >=4.0"
|
||||
"doctrine/orm": "<2.20 || >=3.0 <3.3 || >=4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13 || ^2.0",
|
||||
"doctrine/cache": "^1.11 || ^2.0",
|
||||
"doctrine/common": "^2.13 || ^3.0",
|
||||
"doctrine/dbal": "^3.7 || ^4.0",
|
||||
"doctrine/doctrine-bundle": "^2.3",
|
||||
"doctrine/mongodb-odm": "^2.3",
|
||||
"doctrine/orm": "^2.14.0 || ^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.14.0",
|
||||
"doctrine/orm": "^2.20 || ^3.3",
|
||||
"friendsofphp/php-cs-fixer": "^3.70",
|
||||
"nesbot/carbon": "^2.71 || ^3.0",
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"phpstan/phpstan-doctrine": "^1.4",
|
||||
"phpstan/phpstan-phpunit": "^1.4",
|
||||
"phpstan/phpstan": "^2.1.1",
|
||||
"phpstan/phpstan-doctrine": "^2.0.1",
|
||||
"phpstan/phpstan-phpunit": "^2.0.3",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"rector/rector": "^1.1",
|
||||
"rector/rector": "^2.0.6",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/phpunit-bridge": "^6.0 || ^7.0",
|
||||
@@ -2977,7 +2978,7 @@
|
||||
"support": {
|
||||
"email": "gediminas.morkevicius@gmail.com",
|
||||
"issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues",
|
||||
"source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.17.1",
|
||||
"source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.19.0",
|
||||
"wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc"
|
||||
},
|
||||
"funding": [
|
||||
@@ -2998,20 +2999,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-07T22:30:27+00:00"
|
||||
"time": "2025-02-24T22:12:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "goteo/benzina",
|
||||
"version": "0.3",
|
||||
"version": "0.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoteoFoundation/benzina.git",
|
||||
"reference": "83cc705b963868dc481f6dd7c6d699b72b537586"
|
||||
"url": "https://github.com/goteo/benzina.git",
|
||||
"reference": "c19f8671285f1050a11582f8dd17f87888cb727c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GoteoFoundation/benzina/zipball/83cc705b963868dc481f6dd7c6d699b72b537586",
|
||||
"reference": "83cc705b963868dc481f6dd7c6d699b72b537586",
|
||||
"url": "https://api.github.com/repos/goteo/benzina/zipball/c19f8671285f1050a11582f8dd17f87888cb727c",
|
||||
"reference": "c19f8671285f1050a11582f8dd17f87888cb727c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3054,10 +3055,10 @@
|
||||
],
|
||||
"description": "Migration tool to pump v3 data into a v4 schema",
|
||||
"support": {
|
||||
"issues": "https://github.com/GoteoFoundation/benzina/issues",
|
||||
"source": "https://github.com/GoteoFoundation/benzina/tree/0.3"
|
||||
"issues": "https://github.com/goteo/benzina/issues",
|
||||
"source": "https://github.com/goteo/benzina/tree/0.3.1"
|
||||
},
|
||||
"time": "2025-02-13T20:29:02+00:00"
|
||||
"time": "2025-02-21T15:13:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jolicode/automapper",
|
||||
@@ -4559,16 +4560,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache",
|
||||
"version": "v6.4.16",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/cache.git",
|
||||
"reference": "70d60e9a3603108563010f8592dff15a6f15dfae"
|
||||
"reference": "b209751ed25f735ea90ca4c9c969d9413a17dfee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/cache/zipball/70d60e9a3603108563010f8592dff15a6f15dfae",
|
||||
"reference": "70d60e9a3603108563010f8592dff15a6f15dfae",
|
||||
"url": "https://api.github.com/repos/symfony/cache/zipball/b209751ed25f735ea90ca4c9c969d9413a17dfee",
|
||||
"reference": "b209751ed25f735ea90ca4c9c969d9413a17dfee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4635,7 +4636,7 @@
|
||||
"psr6"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/cache/tree/v6.4.16"
|
||||
"source": "https://github.com/symfony/cache/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4651,7 +4652,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-20T10:10:54+00:00"
|
||||
"time": "2025-01-22T14:13:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache-contracts",
|
||||
@@ -5122,21 +5123,21 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/doctrine-bridge",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/doctrine-bridge.git",
|
||||
"reference": "2ba7e747a944b69f9f583c35173560afebbff995"
|
||||
"reference": "fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/2ba7e747a944b69f9f583c35173560afebbff995",
|
||||
"reference": "2ba7e747a944b69f9f583c35173560afebbff995",
|
||||
"url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6",
|
||||
"reference": "fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/event-manager": "^1.2|^2",
|
||||
"doctrine/persistence": "^3.1",
|
||||
"doctrine/persistence": "^2.5|^3.1|^4",
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
@@ -5210,7 +5211,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.17"
|
||||
"source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5226,7 +5227,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-18T10:42:42+00:00"
|
||||
"time": "2025-01-25T08:04:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dotenv",
|
||||
@@ -5304,16 +5305,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/error-handler",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/error-handler.git",
|
||||
"reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c"
|
||||
"reference": "e8d3b5b1975e67812a54388b1ba8e9ec28eb770e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/37ad2380e8c1a8cf62a1200a5c10080b679b446c",
|
||||
"reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/e8d3b5b1975e67812a54388b1ba8e9ec28eb770e",
|
||||
"reference": "e8d3b5b1975e67812a54388b1ba8e9ec28eb770e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5359,7 +5360,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.17"
|
||||
"source": "https://github.com/symfony/error-handler/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5375,7 +5376,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-06T13:30:51+00:00"
|
||||
"time": "2025-01-06T09:38:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
@@ -5797,16 +5798,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/framework-bundle",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/framework-bundle.git",
|
||||
"reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f"
|
||||
"reference": "91df8ee37543ebc01756c9e5eaf94d1878ff1ccd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/framework-bundle/zipball/17d8ae2e7aa77154f942e8ac48849ac718b0963f",
|
||||
"reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f",
|
||||
"url": "https://api.github.com/repos/symfony/framework-bundle/zipball/91df8ee37543ebc01756c9e5eaf94d1878ff1ccd",
|
||||
"reference": "91df8ee37543ebc01756c9e5eaf94d1878ff1ccd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5926,7 +5927,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.17"
|
||||
"source": "https://github.com/symfony/framework-bundle/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5942,20 +5943,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-19T14:08:41+00:00"
|
||||
"time": "2025-01-28T18:47:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509"
|
||||
"reference": "394b440934056b8d9d6ba250001458e9d7998b7f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/88898d842eb29d7e1a903724c94e90a6ca9c0509",
|
||||
"reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/394b440934056b8d9d6ba250001458e9d7998b7f",
|
||||
"reference": "394b440934056b8d9d6ba250001458e9d7998b7f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6019,7 +6020,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v6.4.17"
|
||||
"source": "https://github.com/symfony/http-client/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6035,7 +6036,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-18T12:18:31+00:00"
|
||||
"time": "2025-01-28T15:49:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
@@ -6117,16 +6118,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v6.4.16",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57"
|
||||
"reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57",
|
||||
"reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0492d6217e5ab48f51fca76f64cf8e78919d0db",
|
||||
"reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6174,7 +6175,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.16"
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6190,20 +6191,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-13T18:58:10+00:00"
|
||||
"time": "2025-01-09T15:48:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710"
|
||||
"reference": "fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/c5647393c5ce11833d13e4b70fff4b571d4ac710",
|
||||
"reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7",
|
||||
"reference": "fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6288,7 +6289,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.17"
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6304,7 +6305,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-31T14:49:31+00:00"
|
||||
"time": "2025-01-29T07:25:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/intl",
|
||||
@@ -7096,16 +7097,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/property-access",
|
||||
"version": "v6.4.13",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/property-access.git",
|
||||
"reference": "8cc779d88d12e440adaa26387bcfc25744064afe"
|
||||
"reference": "80e0378f2f058b60d87dedc3c760caec882e992c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/property-access/zipball/8cc779d88d12e440adaa26387bcfc25744064afe",
|
||||
"reference": "8cc779d88d12e440adaa26387bcfc25744064afe",
|
||||
"url": "https://api.github.com/repos/symfony/property-access/zipball/80e0378f2f058b60d87dedc3c760caec882e992c",
|
||||
"reference": "80e0378f2f058b60d87dedc3c760caec882e992c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7153,7 +7154,7 @@
|
||||
"reflection"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/property-access/tree/v6.4.13"
|
||||
"source": "https://github.com/symfony/property-access/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7169,20 +7170,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:18:03+00:00"
|
||||
"time": "2024-12-16T14:42:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/property-info",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/property-info.git",
|
||||
"reference": "38b125d78e67668159f75383a293ec0c5d3f2963"
|
||||
"reference": "94d18e5cc11a37fd92856d38b61d9cdf72536a1e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/property-info/zipball/38b125d78e67668159f75383a293ec0c5d3f2963",
|
||||
"reference": "38b125d78e67668159f75383a293ec0c5d3f2963",
|
||||
"url": "https://api.github.com/repos/symfony/property-info/zipball/94d18e5cc11a37fd92856d38b61d9cdf72536a1e",
|
||||
"reference": "94d18e5cc11a37fd92856d38b61d9cdf72536a1e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7193,7 +7194,9 @@
|
||||
"doctrine/annotations": "<1.12",
|
||||
"phpdocumentor/reflection-docblock": "<5.2",
|
||||
"phpdocumentor/type-resolver": "<1.5.1",
|
||||
"symfony/dependency-injection": "<5.4|>=6.0,<6.4"
|
||||
"symfony/cache": "<5.4",
|
||||
"symfony/dependency-injection": "<5.4|>=6.0,<6.4",
|
||||
"symfony/serializer": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.12|^2",
|
||||
@@ -7237,7 +7240,7 @@
|
||||
"validator"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/property-info/tree/v6.4.17"
|
||||
"source": "https://github.com/symfony/property-info/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7253,20 +7256,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-26T19:01:29+00:00"
|
||||
"time": "2025-01-21T10:52:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v6.4.16",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220"
|
||||
"reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220",
|
||||
"reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/e9bfc94953019089acdfb9be51c1b9142c4afa68",
|
||||
"reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7320,7 +7323,7 @@
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/routing/tree/v6.4.16"
|
||||
"source": "https://github.com/symfony/routing/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7336,7 +7339,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-13T15:31:34+00:00"
|
||||
"time": "2025-01-09T08:51:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/runtime",
|
||||
@@ -7531,16 +7534,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-core",
|
||||
"version": "v6.4.16",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/security-core.git",
|
||||
"reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0"
|
||||
"reference": "0ae7ae716968e00287ab9b7768405e0dc9cad109"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/security-core/zipball/19cdb7de86e556202ab16e0cffd1a97348231bc0",
|
||||
"reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0",
|
||||
"url": "https://api.github.com/repos/symfony/security-core/zipball/0ae7ae716968e00287ab9b7768405e0dc9cad109",
|
||||
"reference": "0ae7ae716968e00287ab9b7768405e0dc9cad109",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7597,7 +7600,7 @@
|
||||
"description": "Symfony Security Component - Core Library",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/security-core/tree/v6.4.16"
|
||||
"source": "https://github.com/symfony/security-core/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7613,7 +7616,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-27T09:48:51+00:00"
|
||||
"time": "2025-01-22T20:59:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-csrf",
|
||||
@@ -7685,16 +7688,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-http",
|
||||
"version": "v6.4.15",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/security-http.git",
|
||||
"reference": "ded1e078f952e686b058d9eac98e497bea47b308"
|
||||
"reference": "54f2ccce1f3822eee3fb3a85debd9a67d12762b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/security-http/zipball/ded1e078f952e686b058d9eac98e497bea47b308",
|
||||
"reference": "ded1e078f952e686b058d9eac98e497bea47b308",
|
||||
"url": "https://api.github.com/repos/symfony/security-http/zipball/54f2ccce1f3822eee3fb3a85debd9a67d12762b8",
|
||||
"reference": "54f2ccce1f3822eee3fb3a85debd9a67d12762b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7753,7 +7756,7 @@
|
||||
"description": "Symfony Security Component - HTTP Integration",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/security-http/tree/v6.4.15"
|
||||
"source": "https://github.com/symfony/security-http/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7769,20 +7772,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-13T13:40:18+00:00"
|
||||
"time": "2025-01-28T14:53:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/serializer",
|
||||
"version": "v6.4.15",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/serializer.git",
|
||||
"reference": "9d862d66198f3c2e30404228629ef4c18d5d608e"
|
||||
"reference": "6ad986f62276da4c8c69754decfaa445a89cb6e3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/serializer/zipball/9d862d66198f3c2e30404228629ef4c18d5d608e",
|
||||
"reference": "9d862d66198f3c2e30404228629ef4c18d5d608e",
|
||||
"url": "https://api.github.com/repos/symfony/serializer/zipball/6ad986f62276da4c8c69754decfaa445a89cb6e3",
|
||||
"reference": "6ad986f62276da4c8c69754decfaa445a89cb6e3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7851,7 +7854,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.15"
|
||||
"source": "https://github.com/symfony/serializer/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7867,7 +7870,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-23T13:25:59+00:00"
|
||||
"time": "2025-01-28T18:47:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -8522,16 +8525,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/validator",
|
||||
"version": "v6.4.17",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/validator.git",
|
||||
"reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c"
|
||||
"reference": "ce20367d07b2592202e9c266b16a93fa50145207"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/validator/zipball/a3c19a0e542d427c207e22242043ef35b5b99a2c",
|
||||
"reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c",
|
||||
"url": "https://api.github.com/repos/symfony/validator/zipball/ce20367d07b2592202e9c266b16a93fa50145207",
|
||||
"reference": "ce20367d07b2592202e9c266b16a93fa50145207",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8599,7 +8602,7 @@
|
||||
"description": "Provides tools to validate values",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/validator/tree/v6.4.17"
|
||||
"source": "https://github.com/symfony/validator/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8615,20 +8618,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-29T12:50:19+00:00"
|
||||
"time": "2025-01-27T16:05:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v6.4.15",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80"
|
||||
"reference": "4ad10cf8b020e77ba665305bb7804389884b4837"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80",
|
||||
"reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/4ad10cf8b020e77ba665305bb7804389884b4837",
|
||||
"reference": "4ad10cf8b020e77ba665305bb7804389884b4837",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8684,7 +8687,7 @@
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v6.4.15"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8700,7 +8703,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-08T15:28:48+00:00"
|
||||
"time": "2025-01-17T11:26:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
@@ -8864,16 +8867,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v6.4.13",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9"
|
||||
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
|
||||
"reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
|
||||
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8916,7 +8919,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v6.4.13"
|
||||
"source": "https://github.com/symfony/yaml/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8932,7 +8935,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:18:03+00:00"
|
||||
"time": "2025-01-07T09:44:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
@@ -9511,16 +9514,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.69.1",
|
||||
"version": "v3.70.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "13b0c0eede38c11cd674b080f2b485d0f14ffa9f"
|
||||
"reference": "2ecd5aae0edc937f0d5aa4a22d1d705c6b2e084e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/13b0c0eede38c11cd674b080f2b485d0f14ffa9f",
|
||||
"reference": "13b0c0eede38c11cd674b080f2b485d0f14ffa9f",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2ecd5aae0edc937f0d5aa4a22d1d705c6b2e084e",
|
||||
"reference": "2ecd5aae0edc937f0d5aa4a22d1d705c6b2e084e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9602,7 +9605,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.69.1"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.70.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9610,7 +9613,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-18T23:57:43+00:00"
|
||||
"time": "2025-02-22T23:30:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
@@ -11903,16 +11906,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v6.4.16",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
"reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d"
|
||||
"reference": "fd07959d3e8992795029bdab3605c2e8e895034e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4304e6ad5c894a9c72831ad459f627bfd35d766d",
|
||||
"reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/fd07959d3e8992795029bdab3605c2e8e895034e",
|
||||
"reference": "fd07959d3e8992795029bdab3605c2e8e895034e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11950,7 +11953,7 @@
|
||||
"description": "Eases DOM navigation for HTML and XML documents",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dom-crawler/tree/v6.4.16"
|
||||
"source": "https://github.com/symfony/dom-crawler/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11966,7 +11969,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-13T15:06:22+00:00"
|
||||
"time": "2025-01-09T15:35:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/maker-bundle",
|
||||
@@ -12381,16 +12384,16 @@
|
||||
},
|
||||
{
|
||||
"name": "zenstruck/foundry",
|
||||
"version": "v2.3.4",
|
||||
"version": "v2.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zenstruck/foundry.git",
|
||||
"reference": "19d4f79767fa6505ad4092cdb9542de0a747cd85"
|
||||
"reference": "acd5dfc270bb77d48d62090d27a63d085fddf813"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zenstruck/foundry/zipball/19d4f79767fa6505ad4092cdb9542de0a747cd85",
|
||||
"reference": "19d4f79767fa6505ad4092cdb9542de0a747cd85",
|
||||
"url": "https://api.github.com/repos/zenstruck/foundry/zipball/acd5dfc270bb77d48d62090d27a63d085fddf813",
|
||||
"reference": "acd5dfc270bb77d48d62090d27a63d085fddf813",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -12474,7 +12477,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/zenstruck/foundry/issues",
|
||||
"source": "https://github.com/zenstruck/foundry/tree/v2.3.4"
|
||||
"source": "https://github.com/zenstruck/foundry/tree/v2.3.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -12482,7 +12485,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-14T11:35:54+00:00"
|
||||
"time": "2025-02-24T13:07:31+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
||||
@@ -13,7 +13,7 @@ security:
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User\User
|
||||
property: username
|
||||
property: handle
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource\User;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Entity\User\Organization;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Sometimes an Organization is behind a User account,
|
||||
* we keep their data separated from the User record to allow for other User types,
|
||||
* like the ones owned by an individual, to exist all at the same level.\
|
||||
* \
|
||||
* All Organization records are only visible to their respective owners and platform admins.
|
||||
* Sensitive data is encrypted before being stored in the database.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'Organization',
|
||||
security: 'is_granted("ORGANIZATION_VIEW", object)',
|
||||
stateOptions: new Options(entityClass: Organization::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
uriTemplate: '/users/{id}/organization',
|
||||
uriVariables: [
|
||||
'id' => new API\Link(
|
||||
fromClass: UserApiResource::class,
|
||||
fromProperty: 'organization',
|
||||
description: 'User identifier'
|
||||
),
|
||||
]
|
||||
)]
|
||||
#[API\Get()]
|
||||
#[API\Patch(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
class OrganizationApiResource
|
||||
{
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public UserApiResource $user;
|
||||
|
||||
/**
|
||||
* ID for tax purposes. e.g: NIF (formerly CIF), Umsatzsteuer-Id, EID, etc.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
#[Assert\NotBlank()]
|
||||
public string $taxId;
|
||||
|
||||
/**
|
||||
* Organization legal name before government,
|
||||
* as it appears on legal documents issued by or for this organization.\
|
||||
* Will be used as last option for the display name of the User.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
#[Assert\NotBlank()]
|
||||
public string $legalName;
|
||||
|
||||
/**
|
||||
* The name under which the organization presents itself.\
|
||||
* Might be similar to the legal name or completely different.\
|
||||
* Will be used as display name for the User when present.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
public string $businessName;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource\User;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Entity\User\Person;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
|
||||
/**
|
||||
* Most times a real person is behind a User account,
|
||||
* we keep their data separated from the User record to allow for other User types,
|
||||
* like the ones owned by an organization, to exist all at the same level.\
|
||||
* \
|
||||
* All Person records are only visible to their respective owners and platform admins.
|
||||
* Sensitive personal data is encrypted before being stored in the database.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'Person',
|
||||
security: 'is_granted("PERSON_VIEW", object)',
|
||||
stateOptions: new Options(entityClass: Person::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
uriTemplate: '/users/{id}/person',
|
||||
uriVariables: [
|
||||
'id' => new API\Link(
|
||||
fromClass: UserApiResource::class,
|
||||
fromProperty: 'person',
|
||||
description: 'User identifier'
|
||||
),
|
||||
]
|
||||
)]
|
||||
#[API\Get()]
|
||||
#[API\Patch(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
class PersonApiResource
|
||||
{
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public UserApiResource $user;
|
||||
|
||||
/**
|
||||
* Personal ID for tax purposes. e.g: NIF, Steuer-ID, SSN, ITIN, etc.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
public string $taxId;
|
||||
|
||||
/**
|
||||
* First-part of the name of the person,
|
||||
* in most western conventions this is the given name(s). e.g: John, Juan, etc.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
public string $firstName;
|
||||
|
||||
/**
|
||||
* Last-part of the name of the person,
|
||||
* in most western conventions this is the family name(s). e.g: Smith, Herrera García, etc.
|
||||
*/
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
public string $lastName;
|
||||
}
|
||||
@@ -2,17 +2,19 @@
|
||||
|
||||
namespace App\ApiResource\User;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\Dto\UserSignupDto;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Filter\OrderedLikeFilter;
|
||||
use App\Filter\UserQueryFilter;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\Mapping\Transformer\UserDisplayNameMapTransformer;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use App\State\User\UserSignupProcessor;
|
||||
use App\State\User\UserStateProcessor;
|
||||
use AutoMapper\Attribute\MapFrom;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -22,13 +24,13 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
shortName: 'User',
|
||||
stateOptions: new Options(entityClass: User::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
processor: UserStateProcessor::class,
|
||||
)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(input: UserSignupDto::class, processor: UserSignupProcessor::class)]
|
||||
#[API\Get()]
|
||||
#[API\Patch(securityPostDenormalize: 'is_granted("USER_EDIT", object)')]
|
||||
#[API\Delete(securityPostDenormalize: 'is_granted("USER_EDIT", object)')]
|
||||
#[API\Patch(securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)')]
|
||||
#[API\Delete(securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)')]
|
||||
#[API\ApiFilter(filterClass: UserQueryFilter::class, properties: ['query'])]
|
||||
class UserApiResource
|
||||
{
|
||||
@@ -39,9 +41,6 @@ class UserApiResource
|
||||
#[Assert\Email()]
|
||||
public string $email;
|
||||
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public bool $emailConfirmed;
|
||||
|
||||
/**
|
||||
* A unique, non white space, byte-safe string identifier for this User.
|
||||
*/
|
||||
@@ -49,22 +48,48 @@ class UserApiResource
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 4, max: 30)]
|
||||
#[Assert\Regex('/^[a-z0-9_]+$/')]
|
||||
public string $username;
|
||||
public string $handle;
|
||||
|
||||
/**
|
||||
* Display name chosen by the User.
|
||||
* URL to the avatar image of this User.
|
||||
*/
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'partial')]
|
||||
public string $name;
|
||||
#[Assert\Url()]
|
||||
public string $avatar;
|
||||
|
||||
/**
|
||||
* A list of the roles assigned to this User. Admin scopped property.
|
||||
* Is this User for an individual acting on their own or a group of individuals?
|
||||
*/
|
||||
#[API\ApiProperty(securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)')]
|
||||
public UserType $type;
|
||||
|
||||
/**
|
||||
* A list of the roles assigned to this User. Admin scoped property.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[API\ApiProperty(securityPostDenormalize: 'is_granted("ROLE_ADMIN")')]
|
||||
#[API\ApiProperty(
|
||||
security: 'is_granted("ROLE_ADMIN")',
|
||||
securityPostDenormalize: 'is_granted("ROLE_ADMIN")'
|
||||
)]
|
||||
public array $roles;
|
||||
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[MapFrom(User::class, transformer: UserDisplayNameMapTransformer::class)]
|
||||
public string $displayName;
|
||||
|
||||
/**
|
||||
* For `individual` User types: personal data about the User themselves.\
|
||||
* For `organization` User types: data for the organization representative or person managing the User.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public PersonApiResource $person;
|
||||
|
||||
/**
|
||||
* For `organization` User types only. Legal entity data.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public ?OrganizationApiResource $organization = null;
|
||||
|
||||
/**
|
||||
* The Accounting for this User monetary movements.
|
||||
*/
|
||||
@@ -78,4 +103,16 @@ class UserApiResource
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public array $projects;
|
||||
|
||||
/**
|
||||
* Has this User confirmed their email address?
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public bool $emailConfirmed;
|
||||
|
||||
/**
|
||||
* A flag determined by the platform for Users who are known to be active.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public bool $active;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Benzina;
|
||||
|
||||
use App\Entity\User\User;
|
||||
use App\Service\UserService;
|
||||
use Goteo\Benzina\Pump\AbstractPump;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
@@ -27,11 +28,10 @@ class UsersPump extends AbstractPump
|
||||
public function pump(mixed $record): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername($this->getUsername($record));
|
||||
$user->setHandle($this->buildHandle($record));
|
||||
$user->setPassword($record['password'] ?? '');
|
||||
$user->setEmail($record['email']);
|
||||
$user->setEmailConfirmed(false);
|
||||
$user->setName($record['name']);
|
||||
$user->setActive(false);
|
||||
$user->setMigrated(true);
|
||||
$user->setMigratedId($record['id']);
|
||||
@@ -42,40 +42,17 @@ class UsersPump extends AbstractPump
|
||||
++$this->userCount;
|
||||
}
|
||||
|
||||
private function normalizeUsername(string $username): ?string
|
||||
private function buildHandle(array $record): string
|
||||
{
|
||||
// If email remove provider
|
||||
if (\str_contains($username, '@') && \preg_match('/^[\w]+[^@]/', $username, $matches)) {
|
||||
$username = $matches[0];
|
||||
}
|
||||
|
||||
// Only lowercase a-z, numbers and underscore in usernames
|
||||
$username = \preg_replace('/[^a-z0-9_]/', '_', \strtolower($username));
|
||||
|
||||
// Min length 4
|
||||
$username = \str_pad($username, 4, '_');
|
||||
|
||||
// Max length 30
|
||||
$username = \substr($username, 0, 30);
|
||||
|
||||
if (strlen(str_replace('_', '', $username)) < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
private function getUsername(array $record): string
|
||||
{
|
||||
$username = $this->normalizeUsername($record['id']);
|
||||
|
||||
if ($username === null) {
|
||||
$username = $this->normalizeUsername($record['email']);
|
||||
try {
|
||||
$handle = UserService::asHandle($record['id']);
|
||||
} catch (\Exception $e) {
|
||||
$handle = UserService::asHandle($record['email']);
|
||||
}
|
||||
|
||||
$sequenceNumber = \substr($this->userCount, -2, 2);
|
||||
|
||||
return \sprintf('%s_%s', $username, $sequenceNumber);
|
||||
return \sprintf('%s_%s', $handle, $sequenceNumber);
|
||||
}
|
||||
|
||||
private function getDateCreated(array $record): \DateTime
|
||||
|
||||
@@ -2,28 +2,29 @@
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Entity\User\UserType;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
final class UserSignupDto
|
||||
{
|
||||
/**
|
||||
* A valid e-mail address for the new User.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Email()]
|
||||
public string $email;
|
||||
|
||||
/**
|
||||
* The auth password for the new User. Plaintext string,
|
||||
* will be hashed by the API.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 8)]
|
||||
public string $password;
|
||||
|
||||
/**
|
||||
* A unique, non white space, byte-safe string identifier for this User.
|
||||
* Is this User for an individual acting on their own or a group of individuals?
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 4, max: 30)]
|
||||
#[Assert\Regex('/^[a-z0-9_]+$/')]
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* Display name chosen by the User.
|
||||
*/
|
||||
public string $name;
|
||||
public UserType $type = UserType::Individual;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
final class UserTokenLoginDto
|
||||
{
|
||||
/**
|
||||
* The identifier (email, username) of the User to be authenticated.
|
||||
* The identifier (email, handle) of the User to be authenticated.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public readonly string $identifier;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\User;
|
||||
|
||||
use Ambta\DoctrineEncryptBundle\Configuration\Encrypted;
|
||||
use App\Mapping\Provider\OrganizationMapProvider;
|
||||
use App\Repository\User\OrganizationRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[MapProvider(OrganizationMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: OrganizationRepository::class)]
|
||||
class Organization
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\OneToOne(inversedBy: 'organization', cascade: ['persist', 'remove'])]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user = null;
|
||||
|
||||
/**
|
||||
* ID for tax purposes. e.g: NIF (formerly CIF), Umsatzsteuer-Id, EID, etc.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Encrypted()]
|
||||
private ?string $taxId = null;
|
||||
|
||||
/**
|
||||
* Organization legal name before government,
|
||||
* as it appears on legal documents issued by or for this organization.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $legalName = null;
|
||||
|
||||
/**
|
||||
* The name under which the organization presents itself.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $businessName = null;
|
||||
|
||||
public static function for(User $user): Organization
|
||||
{
|
||||
$org = new Organization();
|
||||
$org->setUser($user);
|
||||
|
||||
return $org;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxId(): ?string
|
||||
{
|
||||
return $this->taxId;
|
||||
}
|
||||
|
||||
public function setTaxId(string $taxId): static
|
||||
{
|
||||
$this->taxId = $taxId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLegalName(): ?string
|
||||
{
|
||||
return $this->legalName;
|
||||
}
|
||||
|
||||
public function setLegalName(string $legalName): static
|
||||
{
|
||||
$this->legalName = $legalName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBusinessName(): ?string
|
||||
{
|
||||
return $this->businessName;
|
||||
}
|
||||
|
||||
public function setBusinessName(?string $businessName): static
|
||||
{
|
||||
$this->businessName = $businessName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\User;
|
||||
|
||||
use Ambta\DoctrineEncryptBundle\Configuration\Encrypted;
|
||||
use App\Mapping\Provider\PersonMapProvider;
|
||||
use App\Repository\User\PersonRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Person is the detailed data of an individual behind a User,
|
||||
* we keep their data separated from the User record to allow for other User types,
|
||||
* like the ones owned by an organization, to exist all at the same level.
|
||||
* \
|
||||
* Sensitive personal data is encrypted before being stored on the database.
|
||||
*/
|
||||
#[MapProvider(PersonMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: PersonRepository::class)]
|
||||
class Person
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\OneToOne(inversedBy: 'person', cascade: ['persist', 'remove'])]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user = null;
|
||||
|
||||
/**
|
||||
* Personal ID for tax purposes. e.g: NIF, Steuernummer, TIN ID, etc.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Encrypted]
|
||||
private ?string $taxId = null;
|
||||
|
||||
/**
|
||||
* First-part of the name of the person, usually the given name(s). e.g: John, Juan.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $firstName = null;
|
||||
|
||||
/**
|
||||
* Last-part of the name of the person, usually the family name(s). e.g: Smith, Herrera García.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $lastName = null;
|
||||
|
||||
public static function for(User $user): Person
|
||||
{
|
||||
$person = new Person();
|
||||
$person->setUser($user);
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxId(): ?string
|
||||
{
|
||||
return $this->taxId;
|
||||
}
|
||||
|
||||
public function setTaxId(?string $taxId): static
|
||||
{
|
||||
$this->taxId = $taxId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function setFirstName(string $firstName): static
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function setLastName(string $lastName): static
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+147
-120
@@ -13,6 +13,7 @@ use App\Repository\User\UserRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
@@ -30,7 +31,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\Index(fields: ['migratedId'])]
|
||||
#[UniqueEntity('email', message: 'This email address is already registered.')]
|
||||
#[UniqueEntity('username', message: 'This usernames already exists.')]
|
||||
#[UniqueEntity('handle', message: 'This handle is already in use.')]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface, AccountingOwnerInterface
|
||||
{
|
||||
use MigratedEntity;
|
||||
@@ -47,23 +48,17 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
private ?string $email = null;
|
||||
|
||||
/**
|
||||
* Has this User confirmed their email address?
|
||||
* @var string The user password
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private ?bool $emailConfirmed = null;
|
||||
private ?string $password = null;
|
||||
|
||||
/**
|
||||
* Human readable, non white space, unique string.
|
||||
*/
|
||||
#[Gedmo\Versioned]
|
||||
#[ORM\Column(length: 255, unique: true)]
|
||||
private ?string $username = null;
|
||||
|
||||
/**
|
||||
* @var list<string> The user roles. Admin only property.
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
private ?string $handle = null;
|
||||
|
||||
#[ORM\OneToOne(inversedBy: 'user', cascade: ['persist'])]
|
||||
private ?Accounting $accounting = null;
|
||||
@@ -80,6 +75,24 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: UserToken::class, orphanRemoval: true)]
|
||||
private Collection $tokens;
|
||||
|
||||
#[ORM\OneToOne(mappedBy: 'user', cascade: ['persist', 'remove'])]
|
||||
private ?Person $person = null;
|
||||
|
||||
#[ORM\OneToOne(mappedBy: 'user', cascade: ['persist', 'remove'])]
|
||||
private ?Organization $organization = null;
|
||||
|
||||
/**
|
||||
* @var list<string> The user roles. Admin only property.
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
/**
|
||||
* Has this User confirmed their email address?
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private ?bool $emailConfirmed = null;
|
||||
|
||||
/**
|
||||
* A flag determined by the platform for Users who are known to be active.
|
||||
*/
|
||||
@@ -87,35 +100,27 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
private ?bool $active = null;
|
||||
|
||||
/**
|
||||
* Path to the Users's avatar image.
|
||||
* Is this user for an individual acting on their own or a larger group of individuals?
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[ORM\Column(enumType: UserType::class)]
|
||||
private UserType $type = UserType::Individual;
|
||||
|
||||
/**
|
||||
* URL to the avatar image of this User.
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $avatar = null;
|
||||
|
||||
/**
|
||||
* Conventional name of the person owning this User.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\OneToOne(mappedBy: 'user', cascade: ['persist', 'remove'])]
|
||||
private ?UserPersonal $personalData = null;
|
||||
|
||||
/**
|
||||
* @var string The user password
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounting = Accounting::of($this);
|
||||
|
||||
$this->projects = new ArrayCollection();
|
||||
$this->tokens = new ArrayCollection();
|
||||
$this->person = Person::for($this);
|
||||
|
||||
$this->emailConfirmed = false;
|
||||
$this->active = false;
|
||||
|
||||
$this->tokens = new ArrayCollection();
|
||||
$this->projects = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -130,7 +135,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->username;
|
||||
return (string) $this->handle;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
@@ -145,57 +150,36 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isEmailConfirmed(): ?bool
|
||||
{
|
||||
return $this->emailConfirmed;
|
||||
}
|
||||
|
||||
public function setEmailConfirmed(bool $emailConfirmed): static
|
||||
{
|
||||
$this->emailConfirmed = $emailConfirmed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function setUsername(?string $username): static
|
||||
{
|
||||
$this->username = strtolower($username);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*
|
||||
* @return list<string>
|
||||
* @see PasswordAuthenticatedUserInterface
|
||||
*/
|
||||
public function getRoles(): array
|
||||
public function getPassword(): string
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
public function setRoles(array $roles): static
|
||||
public function setPassword(string $password): static
|
||||
{
|
||||
$this->roles = $roles;
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasRoles(array $roles): bool
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
return count(array_intersect($this->getRoles(), $roles)) > 0;
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
}
|
||||
|
||||
public function getHandle(): ?string
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function setHandle(?string $handle): static
|
||||
{
|
||||
$this->handle = strtolower($handle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAccounting(): ?Accounting
|
||||
@@ -270,6 +254,81 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPerson(): ?Person
|
||||
{
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
public function setPerson(Person $person): static
|
||||
{
|
||||
// set the owning side of the relation if necessary
|
||||
if ($person->getUser() !== $this) {
|
||||
$person->setUser($this);
|
||||
}
|
||||
|
||||
$this->person = $person;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrganization(): ?Organization
|
||||
{
|
||||
return $this->organization;
|
||||
}
|
||||
|
||||
public function setOrganization(Organization $organization): static
|
||||
{
|
||||
// set the owning side of the relation if necessary
|
||||
if ($organization->getUser() !== $this) {
|
||||
$organization->setUser($this);
|
||||
}
|
||||
|
||||
$this->organization = $organization;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
public function setRoles(array $roles): static
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasRoles(array $roles): bool
|
||||
{
|
||||
return count(array_intersect($this->getRoles(), $roles)) > 0;
|
||||
}
|
||||
|
||||
public function isEmailConfirmed(): ?bool
|
||||
{
|
||||
return $this->emailConfirmed;
|
||||
}
|
||||
|
||||
public function setEmailConfirmed(bool $emailConfirmed): static
|
||||
{
|
||||
$this->emailConfirmed = $emailConfirmed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isActive(): ?bool
|
||||
{
|
||||
return $this->active;
|
||||
@@ -282,6 +341,23 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): UserType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isType(UserType $type): bool
|
||||
{
|
||||
return $type === $this->getType();
|
||||
}
|
||||
|
||||
public function setType(UserType $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvatar(): ?string
|
||||
{
|
||||
return $this->avatar;
|
||||
@@ -293,53 +369,4 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(?string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPersonalData(): ?UserPersonal
|
||||
{
|
||||
return $this->personalData;
|
||||
}
|
||||
|
||||
public function setPersonalData(UserPersonal $personalData): static
|
||||
{
|
||||
// set the owning side of the relation if necessary
|
||||
if ($personalData->getUser() !== $this) {
|
||||
$personalData->setUser($this);
|
||||
}
|
||||
|
||||
$this->personalData = $personalData;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PasswordAuthenticatedUserInterface
|
||||
*/
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(string $password): static
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\User;
|
||||
|
||||
use Ambta\DoctrineEncryptBundle\Configuration\Encrypted;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use App\Repository\User\UserPersonalRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* UserPersonal is the detailed data of a User.
|
||||
*
|
||||
* The personal data of a user will be kept encrypted
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: UserPersonalRepository::class)]
|
||||
#[ApiResource]
|
||||
class UserPersonal
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\OneToOne(inversedBy: 'personalData', cascade: ['persist', 'remove'])]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user = null;
|
||||
|
||||
/**
|
||||
* The Identity Document number of the User.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Encrypted]
|
||||
private ?string $identityDocument = null;
|
||||
|
||||
/**
|
||||
* The type of indentity document that the User has, ie: Passport, DNI, NIE, etc.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Encrypted]
|
||||
private ?string $identityDocumentType = null;
|
||||
|
||||
/**
|
||||
* The Postal Code of where the User lives.
|
||||
*/
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Encrypted]
|
||||
private ?string $postalCode = null;
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIdentityDocument(): ?string
|
||||
{
|
||||
return $this->identityDocument;
|
||||
}
|
||||
|
||||
public function setIdentityDocument(?string $identityDocument): static
|
||||
{
|
||||
$this->identityDocument = $identityDocument;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIndetityDocumentType(): ?string
|
||||
{
|
||||
return $this->identityDocumentType;
|
||||
}
|
||||
|
||||
public function setIndetityDocumentType(?string $identityDocumentType): static
|
||||
{
|
||||
$this->identityDocumentType = $identityDocumentType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPostalCode(): ?string
|
||||
{
|
||||
return $this->postalCode;
|
||||
}
|
||||
|
||||
public function setPostalCode(?string $postalCode): static
|
||||
{
|
||||
$this->postalCode = $postalCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\User;
|
||||
|
||||
enum UserType: string
|
||||
{
|
||||
/**
|
||||
* People who sign-up on their own and do not represent any other legal or natural person.
|
||||
*/
|
||||
case Individual = 'individual';
|
||||
|
||||
/**
|
||||
* Sometimes organizations want to sign-up and represent themselves.
|
||||
*/
|
||||
case Organization = 'organization';
|
||||
}
|
||||
@@ -42,8 +42,7 @@ final class UserQueryFilter extends AbstractFilter
|
||||
|
||||
$queryBuilder
|
||||
->andWhere(sprintf('%s.email LIKE :%s', $rootAlias, $parameterName))
|
||||
->orWhere(sprintf('%s.name LIKE :%s', $rootAlias, $parameterName))
|
||||
->orWhere(sprintf('%s.username LIKE :%s', $rootAlias, $parameterName))
|
||||
->orWhere(sprintf('%s.handle LIKE :%s', $rootAlias, $parameterName))
|
||||
->setParameter($parameterName, $value);
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ final class UserQueryFilter extends AbstractFilter
|
||||
'property' => $property,
|
||||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Query Users by email, name or username. Fuzzy.',
|
||||
'description' => 'Query Users by email or handle. Fuzzy.',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mapping\Provider;
|
||||
|
||||
use App\ApiResource\User\OrganizationApiResource;
|
||||
use App\Repository\User\OrganizationRepository;
|
||||
use AutoMapper\Provider\ProviderInterface;
|
||||
|
||||
class OrganizationMapProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private OrganizationRepository $orgRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param OrganizationApiResource $source
|
||||
*/
|
||||
public function provide(string $targetType, mixed $source, array $context): object|array|null
|
||||
{
|
||||
return $this->orgRepository->find($source->user->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mapping\Provider;
|
||||
|
||||
use App\ApiResource\User\PersonApiResource;
|
||||
use App\Repository\User\PersonRepository;
|
||||
use AutoMapper\Provider\ProviderInterface;
|
||||
|
||||
class PersonMapProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private PersonRepository $personRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param PersonApiResource $source
|
||||
*/
|
||||
public function provide(string $targetType, mixed $source, array $context): object|array|null
|
||||
{
|
||||
return $this->personRepository->find($source->user->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mapping\Transformer;
|
||||
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface;
|
||||
|
||||
class UserDisplayNameMapTransformer implements PropertyTransformerInterface
|
||||
{
|
||||
/**
|
||||
* @param User $source
|
||||
*/
|
||||
public function transform(mixed $value, object|array $source, array $context): mixed
|
||||
{
|
||||
switch ($source->getType()) {
|
||||
case UserType::Individual:
|
||||
return $this->individualDisplayName($source);
|
||||
case UserType::Organization:
|
||||
return $this->organizationDisplayName($source);
|
||||
}
|
||||
}
|
||||
|
||||
private function individualDisplayName(User $user): string
|
||||
{
|
||||
$person = $user->getPerson();
|
||||
|
||||
$name = \sprintf(
|
||||
'%s %s',
|
||||
$person->getFirstName(),
|
||||
$person->getLastName()
|
||||
);
|
||||
|
||||
return \trim($name);
|
||||
}
|
||||
|
||||
private function organizationDisplayName(User $user): string
|
||||
{
|
||||
$org = $user->getOrganization();
|
||||
|
||||
$name = $org->getBusinessName();
|
||||
|
||||
if (empty($name)) {
|
||||
$name = $org->getBusinessName();
|
||||
}
|
||||
|
||||
return $name ?? '';
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ curl -X 'GET' \
|
||||
|
||||
UserTokens exist under an User's scope, when you obtain a token this will only grant you as much permissions as the User under which it was created.
|
||||
|
||||
To obtain a UserToken you must send a POST request to [/v4/user_tokens](/v4/user_tokens) with the User login credentials (username and password) in the payload. If the credentials are correct a UserToken will be created, the `token` property value of which you must include in future requests.
|
||||
To obtain a UserToken you must send a POST request to [/v4/user_tokens](/v4/user_tokens) with the User login credentials (identifier and password) in the payload. If the credentials are correct a UserToken will be created, the `token` property value of which you must include in future requests.
|
||||
|
||||
Users can delete UserTokens owned by them at any moment, revoking your application's access to the v4 API on their behalf. When a UserToken fails to authenticate a request you will receive a 401 Unauthorized response from the API, at which point your application must look to get a new UserToken from the User.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ trait RedocExtensionsTrait
|
||||
$groups = [
|
||||
[
|
||||
'name' => 'Users',
|
||||
'tags' => ['User', 'UserToken', 'UserPersonal'],
|
||||
'tags' => ['User', 'UserToken', 'Person', 'Organization'],
|
||||
],
|
||||
[
|
||||
'name' => 'Projects',
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\User;
|
||||
|
||||
use App\Entity\User\Organization;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Organization>
|
||||
*/
|
||||
class OrganizationRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Organization::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Organization[] Returns an array of Organization objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('o.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Organization
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\User;
|
||||
|
||||
use App\Entity\User\Person;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Person>
|
||||
*
|
||||
* @method Person|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Person|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Person[] findAll()
|
||||
* @method Person[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class PersonRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Person::class);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\User;
|
||||
|
||||
use App\Entity\User\UserPersonal;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UserPersonal>
|
||||
*
|
||||
* @method UserPersonal|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method UserPersonal|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method UserPersonal[] findAll()
|
||||
* @method UserPersonal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class UserPersonalRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserPersonal::class);
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,14 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
|
||||
/**
|
||||
* Find an User by any of it's unique properties.
|
||||
*
|
||||
* @param string $identifier An id, email or username
|
||||
* @param string $identifier An id, email or handle
|
||||
*/
|
||||
public function findOneByIdentifier(string $identifier): ?User
|
||||
{
|
||||
return $this->createQueryBuilder('u')
|
||||
->andWhere('u.id = :val')
|
||||
->orWhere('u.email = :val')
|
||||
->orWhere('u.username = :val')
|
||||
->orWhere('u.handle = :val')
|
||||
->setParameter('val', $identifier)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
@@ -66,6 +66,20 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[] Returns an array of User objects
|
||||
*/
|
||||
public function findLike(string $handle): array
|
||||
{
|
||||
return $this->createQueryBuilder('u')
|
||||
->andWhere('u.handle LIKE :handle')
|
||||
->setParameter('handle', \sprintf('%%%s%%', $handle))
|
||||
->orderBy('u.id', 'ASC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return User[] Returns an array of User objects
|
||||
// */
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\ApiResource\User\OrganizationApiResource;
|
||||
use App\Entity\User\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
final class OrganizationVoter extends Voter
|
||||
{
|
||||
public const EDIT = 'ORGANIZATION_EDIT';
|
||||
public const VIEW = 'ORGANIZATION_VIEW';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::EDIT, self::VIEW])
|
||||
&& $subject instanceof OrganizationApiResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OrganizationApiResource $subject
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($attribute) {
|
||||
case self::EDIT:
|
||||
return $subject->user->id === $user->getId();
|
||||
case self::VIEW:
|
||||
return $user->hasRoles(['ROLE_ADMIN'])
|
||||
|| $subject->user->id === $user->getId();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\ApiResource\User\PersonApiResource;
|
||||
use App\Entity\User\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
final class PersonVoter extends Voter
|
||||
{
|
||||
public const EDIT = 'PERSON_EDIT';
|
||||
public const VIEW = 'PERSON_VIEW';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::EDIT, self::VIEW])
|
||||
&& $subject instanceof PersonApiResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PersonApiResource $subject
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($attribute) {
|
||||
case self::EDIT:
|
||||
return $subject->user->id === $user->getId();
|
||||
case self::VIEW:
|
||||
return $user->hasRoles(['ROLE_ADMIN'])
|
||||
|| $subject->user->id === $user->getId();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class AuthService
|
||||
}
|
||||
|
||||
return $this->userRepository->findOneBy(
|
||||
['username' => $loggedInUser->getUserIdentifier()]
|
||||
['handle' => $loggedInUser->getUserIdentifier()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
class UserService
|
||||
{
|
||||
/**
|
||||
* Normalizes a string into a handle-valid string.
|
||||
*
|
||||
* @return string A handle-valid string
|
||||
*
|
||||
* @throws \Exception If the given value could not be normalized into a compliant handle string
|
||||
*/
|
||||
public static function asHandle(string $value): string
|
||||
{
|
||||
// If email remove provider
|
||||
if (\str_contains($value, '@') && \preg_match('/^[^@]+/', $value, $matches)) {
|
||||
$value = $matches[0];
|
||||
}
|
||||
|
||||
// Only lowercase a-z, numbers and underscore in user handles
|
||||
$value = \preg_replace('/[^a-z0-9_]/', '_', \strtolower($value));
|
||||
|
||||
// Min length 4
|
||||
$value = \str_pad($value, 4, '_');
|
||||
|
||||
// Max length 30
|
||||
$value = \substr($value, 0, 30);
|
||||
|
||||
if (strlen(str_replace('_', '', $value)) < 1) {
|
||||
throw new \Exception(\sprintf('The string \'%s\' could not be safely normalized for a handle', $value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,12 @@ use ApiPlatform\Metadata as API;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Dto\UserSignupDto;
|
||||
use App\Entity\User\Organization;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Mapping\AutoMapper;
|
||||
use App\Repository\User\UserRepository;
|
||||
use App\Service\UserService;
|
||||
use App\State\EntityStateProcessor;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
@@ -16,6 +20,7 @@ final class UserSignupProcessor implements ProcessorInterface
|
||||
public function __construct(
|
||||
private AutoMapper $autoMapper,
|
||||
private EntityStateProcessor $entityStateProcessor,
|
||||
private UserRepository $userRepository,
|
||||
private UserPasswordHasherInterface $userPasswordHasher,
|
||||
) {}
|
||||
|
||||
@@ -28,9 +33,30 @@ final class UserSignupProcessor implements ProcessorInterface
|
||||
{
|
||||
/** @var User */
|
||||
$user = $this->autoMapper->map($data, User::class);
|
||||
|
||||
$user->setHandle($this->buildHandle($data));
|
||||
$user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password));
|
||||
|
||||
if ($user->isType(UserType::Organization)) {
|
||||
$user->setOrganization(Organization::for($user));
|
||||
}
|
||||
|
||||
$user = $this->entityStateProcessor->process($user, $operation, $uriVariables, $context);
|
||||
|
||||
return $this->autoMapper->map($user, UserApiResource::class);
|
||||
}
|
||||
|
||||
private function buildHandle(UserSignupDto $data): string
|
||||
{
|
||||
$base = UserService::asHandle($data->email);
|
||||
|
||||
$users = $this->userRepository->findLike($base);
|
||||
$usersCount = \count($users);
|
||||
|
||||
if ($usersCount < 1) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
return \sprintf('%s_%02d', $base, $usersCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,18 @@ namespace App\State\User;
|
||||
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\User\Organization;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Mapping\AutoMapper;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class UserStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private AutoMapper $autoMapper,
|
||||
#[Autowire(service: PersistProcessor::class)]
|
||||
private ProcessorInterface $innerProcessor,
|
||||
private UserPasswordHasherInterface $userPasswordHasher,
|
||||
private ProcessorInterface $persistProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -23,6 +26,15 @@ class UserStateProcessor implements ProcessorInterface
|
||||
*/
|
||||
public function process(mixed $data, API\Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
return $data;
|
||||
/** @var User */
|
||||
$user = $this->autoMapper->map($data, User::class);
|
||||
|
||||
if ($user->isType(UserType::Organization) && $user->getOrganization() === null) {
|
||||
$user->setOrganization(Organization::for($user));
|
||||
}
|
||||
|
||||
$user = $this->persistProcessor->process($user, $operation, $uriVariables, $context);
|
||||
|
||||
return $this->autoMapper->map($user, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class ProjectApiTest extends ApiTestCase
|
||||
$this->assertJsonContains(['member' => []]);
|
||||
|
||||
$owner = new User();
|
||||
$owner->setUsername('test_user');
|
||||
$owner->setHandle('test_user');
|
||||
$owner->setEmail('testuser@example.com');
|
||||
$owner->setPassword('projectapitestuserpassword');
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class WalletServiceTest extends KernelTestCase
|
||||
private function getUser(): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('wallettestuser');
|
||||
$user->setHandle('wallettestuser');
|
||||
$user->setEmail('testuser@wallet.com');
|
||||
$user->setPassword('wallettestpassword');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user