diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..5fc9dabf08335277fec837e46322dbe5cec18afc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +**/*.log +**/*.md +**/*.php~ +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.editorconfig +.env.*.local +.env.local +.env.local.php +.php_cs.cache +bin/* +!bin/console +config/jwt +helm/ +public/bundles/ +var/ +vendor/ diff --git a/.env b/.env index 519f16440649cf8aa04828a0f3fa3c4f248a7410..43917ce8e317637cf755e9f54a6ec1d721ae2d36 100644 --- a/.env +++ b/.env @@ -28,7 +28,7 @@ CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$ # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" # Configure your db driver and server_version in config/packages/doctrine.yaml -#DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name +# DATABASE_URL=mysql://limaju:limaju@db:3306/limaju DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db ###< doctrine/doctrine-bundle ### @@ -38,3 +38,9 @@ DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db # MESSENGER_TRANSPORT_DSN=doctrine://default # MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages ###< symfony/messenger ### + +###> lexik/jwt-authentication-bundle ### +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=3a25d422a7bce186ad01ab6ef8efddfe +###< lexik/jwt-authentication-bundle ### diff --git a/.gitignore b/.gitignore index 216241a76d21858095b6ff0e0665bba84f0fafe2..eb351be8f7aab7c18441ffaa1eb9ef5bcc847187 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ /.env.local /.env.local.php /.env.*.local +/config/jwt/private.pem +/config/jwt/public.pem /public/bundles/ /var/ /vendor/ @@ -24,3 +26,7 @@ behat.yml ###> symfony/web-server-bundle ### /.web-server-pid ###< symfony/web-server-bundle ### + +###> lexik/jwt-authentication-bundle ### +/config/jwt/*.pem +###< lexik/jwt-authentication-bundle ### diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b565a9cd773eb1622c6f4760913b951f4429a9a..fa14cff7eb062c46631a5d9b73860fc9a2cf5b1c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,8 +38,8 @@ test: script: # Configure the feature suite with the defaults - cp behat.yml.dist behat.yml - # Run the feature suite - - vendor/bin/behat -vv --tags=~noci + # Run the feature suite, ignore Works in Progress and Not Continuous Integration + - vendor/bin/behat -vv --tags='~wip&&~noci' # We've also set this using gitlab's CI Settings UI # coverage: '/^\s*Lines:\s*(\d+\.\d+)%/' diff --git a/Dockerfile b/Dockerfile index d7fe3900d095a1618a5c7476b855bf7d18dc7f7b..10b7c6264c50f1c23cc727817562ef1e57f452e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN set -eux; \ docker-php-ext-install -j$(nproc) \ intl \ zip \ + pdo_mysql \ ; \ pecl install \ apcu-${APCU_VERSION} \ @@ -140,3 +141,13 @@ FROM nginx:${NGINX_VERSION}-alpine AS symfony_h2-proxy RUN mkdir -p /etc/nginx/ssl/ COPY --from=symfony_h2-proxy-cert server.key server.crt /etc/nginx/ssl/ COPY ./docker/h2-proxy/default.conf /etc/nginx/conf.d/default.conf + +# Dockerfile +FROM symfony_php as symfony_php_dev + +ARG XDEBUG_VERSION=2.8.0 +RUN set -eux; \ + apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \ + pecl install xdebug-$XDEBUG_VERSION; \ + docker-php-ext-enable xdebug; \ + apk del .build-deps \ No newline at end of file diff --git a/README.md b/README.md index e64c1b37884130411ae654569f0a7d50dfdf6f1b..a3e9f797b1df254608f221cfb8ec325df8a9b857 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,19 @@ Get [Composer](https://getcomposer.org). composer install +### Via docker + +Install Docker Compose. Run `doccker-compose up`. + + +### References + +* JWT : https://api-platform.com/docs/core/jwt/#jwt-authentication +* docker-compose : https://github.com/dunglas/symfony-docker +* Some others : + * https://symfonycasts.com/screencast/api-platform-security/encode-user-password + + ## Browse generated doc bin/console server:run diff --git a/bin/tmpfs4var b/bin/tmpfs4var new file mode 100755 index 0000000000000000000000000000000000000000..152490515e347259baa9b7831949fd4e532f4bd4 --- /dev/null +++ b/bin/tmpfs4var @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# Use this to keep the feature suite from blowing your hard drive to smithereens. +# 512m should cover our use-case. We'll see… Tweak at will. +# +# Unmount when done with: +# umount ./var +# Content of ./var will be lost! + +USER_ID=$(id -u `whoami`) +GROUP_ID=$(id -g `whoami`) + +sudo mount -t tmpfs -o size=512m,mode=0755,uid=$USER_ID,gid=$GROUP_ID tmpfs ./var diff --git a/composer.json b/composer.json index 120efa8dba97bef70ea326b30e1779eff1452294..9667b9f3f864992faa143fc5aa2758b8c89ba7fd 100644 --- a/composer.json +++ b/composer.json @@ -12,13 +12,15 @@ "ext-xml": "*", "api-platform/api-pack": "1.2.*", "api-platform/core": "2.4.*", - "msgphp/user-bundle": "^0.13.0", + "lexik/jwt-authentication-bundle": "^2.8", + "matthewbaggett/clamp": "^1.1", + "ramsey/uuid": "^3 || ^4", "ramsey/uuid-doctrine": "1.5.*", "sensio/framework-extra-bundle": "5.3.*", "stof/doctrine-extensions-bundle": "1.3.*", "symfony/console": "4.3.*", "symfony/dotenv": "4.3.*", - "symfony/flex": "^1.3.1", + "symfony/flex": "^1.9.10", "symfony/form": "4.3.*", "symfony/framework-bundle": "4.3.*", "symfony/messenger": "4.3.*", @@ -28,15 +30,19 @@ "symfony/yaml": "4.3.*" }, "require-dev": { + "behat/behat": "^3.7", "behat/symfony2-extension": "2.1.*", + "behatch/contexts": "^3.3", "daraeman/wordtonumber": "dev-i18n-french", "doctrine/doctrine-fixtures-bundle": "3.1.*", "liip/functional-test-bundle": "~2.0@alpha", "nelmio/api-doc-bundle": "3.4.*", "phpunit/phpunit": "7.5.*", "softonic/graphql-client": "1.2.*", + "symfony/debug-bundle": "4.3.*", "symfony/maker-bundle": "^1.13", "symfony/monolog-bundle": "^3.4", + "symfony/profiler-pack": "^1.0", "symfony/var-dumper": "4.3.*", "symfony/web-server-bundle": "4.3.*" }, diff --git a/composer.lock b/composer.lock index 8a2d74240bf5f45aa67058a27891cd544ddb1122..d06434a2e4e47277198b0a2e752e2ce6ba4ef1ef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,30 +4,30 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8b0b87b053c743ead503b74f63298f08", + "content-hash": "74dfb775b9049cc49cbac5439fecf924", "packages": [ { "name": "api-platform/api-pack", - "version": "v1.2.0", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/api-platform/api-pack.git", - "reference": "9e3e7421415c747e676778f211434674324dcfa9" + "reference": "ca7ca22f30bcfcdb2493ec9e8b7b5dba1271608a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/api-pack/zipball/9e3e7421415c747e676778f211434674324dcfa9", - "reference": "9e3e7421415c747e676778f211434674324dcfa9", + "url": "https://api.github.com/repos/api-platform/api-pack/zipball/ca7ca22f30bcfcdb2493ec9e8b7b5dba1271608a", + "reference": "ca7ca22f30bcfcdb2493ec9e8b7b5dba1271608a", "shasum": "" }, "require": { "api-platform/core": "^2.1", "doctrine/annotations": "^1.0", - "doctrine/doctrine-bundle": "^1.6", + "doctrine/doctrine-bundle": "^1.6 || ^2.0", "doctrine/orm": "^2.4.5", - "nelmio/cors-bundle": "^1.5", + "nelmio/cors-bundle": "^1.5 || ^2.0", "php": "^7.0", - "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", + "phpdocumentor/reflection-docblock": "^3.0 || ^4.0 || ^5.0", "symfony/asset": "*", "symfony/expression-language": "*", "symfony/security-bundle": "*", @@ -40,7 +40,7 @@ "MIT" ], "description": "A pack for API Platform", - "time": "2019-01-12T11:48:48+00:00" + "time": "2020-04-01T16:40:06+00:00" }, { "name": "api-platform/core", @@ -188,16 +188,16 @@ }, { "name": "behat/transliterator", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/Behat/Transliterator.git", - "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c" + "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Transliterator/zipball/826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c", - "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", + "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", "shasum": "" }, "require": { @@ -205,7 +205,8 @@ }, "require-dev": { "chuyskywalker/rolling-curl": "^3.1", - "php-yaoi/php-yaoi": "^1.0" + "php-yaoi/php-yaoi": "^1.0", + "phpunit/phpunit": "^4.8.36|^6.3" }, "type": "library", "extra": { @@ -214,8 +215,8 @@ } }, "autoload": { - "psr-0": { - "Behat\\Transliterator": "src/" + "psr-4": { + "Behat\\Transliterator\\": "src/Behat/Transliterator" } }, "notification-url": "https://packagist.org/downloads/", @@ -228,34 +229,105 @@ "slug", "transliterator" ], - "time": "2017-04-04T11:38:05+00:00" + "time": "2020-01-14T16:39:13+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-08-25T05:50:16+00:00" }, { "name": "doctrine/annotations", - "version": "v1.7.0", + "version": "1.10.4", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb" + "reference": "bfe91e31984e2ba76df1c1339681770401ec262f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fa4c4e861e809d6a1103bd620cce63ed91aedfeb", - "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/bfe91e31984e2ba76df1c1339681770401ec262f", + "reference": "bfe91e31984e2ba76df1c1339681770401ec262f", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": "^7.1" + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^7.5@dev" + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^9.1.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -296,24 +368,24 @@ "docblock", "parser" ], - "time": "2019-08-08T18:11:40+00:00" + "time": "2020-08-10T19:35:50+00:00" }, { "name": "doctrine/cache", - "version": "1.10.0", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" + "reference": "13e3381b25847283a91948d04640543941309727" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", + "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", + "reference": "13e3381b25847283a91948d04640543941309727", "shasum": "" }, "require": { - "php": "~7.1" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" @@ -378,37 +450,46 @@ "redis", "xcache" ], - "time": "2019-11-29T15:36:20+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-07-07T18:54:01+00:00" }, { "name": "doctrine/collections", - "version": "v1.6.2", + "version": "1.6.7", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", + "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", "phpstan/phpstan-shim": "^0.9.2", "phpunit/phpunit": "^7.0", - "vimeo/psalm": "^3.2.2" + "vimeo/psalm": "^3.8.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" @@ -419,6 +500,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -427,10 +512,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -448,20 +529,20 @@ "iterators", "php" ], - "time": "2019-06-09T13:48:14+00:00" + "time": "2020-07-27T17:53:49+00:00" }, { "name": "doctrine/common", - "version": "v2.11.0", + "version": "2.13.3", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff" + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff", - "reference": "b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff", + "url": "https://api.github.com/repos/doctrine/common/zipball/f3812c026e557892c34ef37f6ab808a6b567da7f", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f", "shasum": "" }, "require": { @@ -471,9 +552,9 @@ "doctrine/event-manager": "^1.0", "doctrine/inflector": "^1.0", "doctrine/lexer": "^1.0", - "doctrine/persistence": "^1.1", + "doctrine/persistence": "^1.3.3", "doctrine/reflection": "^1.0", - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^1.0", @@ -531,35 +612,50 @@ "doctrine", "php" ], - "time": "2019-09-10T10:10:14+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2020-06-05T16:46:05+00:00" }, { "name": "doctrine/dbal", - "version": "v2.9.2", + "version": "2.11.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" + "reference": "b88a45cbc395e850b2d958dcd7b9d49d09ff23e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/b88a45cbc395e850b2d958dcd7b9d49d09ff23e0", + "reference": "b88a45cbc395e850b2d958dcd7b9d49d09ff23e0", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.1" + "php": "^7.3" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "jetbrains/phpstorm-stubs": "^2018.1.2", - "phpstan/phpstan": "^0.10.1", - "phpunit/phpunit": "^7.4", - "symfony/console": "^2.0.5|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + "doctrine/coding-standard": "^8.1", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.12.40", + "phpunit/phpunit": "^9.4", + "psalm/plugin-phpunit": "^0.10.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.17.2" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -570,8 +666,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -584,6 +679,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -592,10 +691,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -606,39 +701,67 @@ "keywords": [ "abstraction", "database", + "db2", "dbal", + "mariadb", + "mssql", "mysql", - "persistence", + "oci8", + "oracle", + "pdo", "pgsql", - "php", - "queryobject" + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" ], - "time": "2018-12-31T03:27:51+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-10-19T02:01:37+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "1.11.2", + "version": "1.12.10", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "28101e20776d8fa20a00b54947fbae2db0d09103" + "reference": "2ee4c25a847e744e93d7fc2895e059ad9ef7d10c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/28101e20776d8fa20a00b54947fbae2db0d09103", - "reference": "28101e20776d8fa20a00b54947fbae2db0d09103", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2ee4c25a847e744e93d7fc2895e059ad9ef7d10c", + "reference": "2ee4c25a847e744e93d7fc2895e059ad9ef7d10c", "shasum": "" }, "require": { "doctrine/dbal": "^2.5.12", "doctrine/doctrine-cache-bundle": "~1.2", + "doctrine/persistence": "^1.3.3", "jdorn/sql-formatter": "^1.2.16", - "php": "^7.1", - "symfony/config": "^3.4|^4.1", - "symfony/console": "^3.4|^4.1", - "symfony/dependency-injection": "^3.4|^4.1", - "symfony/doctrine-bridge": "^3.4|^4.1", - "symfony/framework-bundle": "^3.4|^4.1" + "php": "^7.1 || ^8.0", + "symfony/cache": "^3.4.30|^4.3.3", + "symfony/config": "^3.4.30|^4.3.3", + "symfony/console": "^3.4.30|^4.3.3", + "symfony/dependency-injection": "^3.4.30|^4.3.3", + "symfony/doctrine-bridge": "^3.4.30|^4.3.3", + "symfony/framework-bundle": "^3.4.30|^4.3.3", + "symfony/service-contracts": "^1.1.1|^2.0" }, "conflict": { "doctrine/orm": "<2.6", @@ -647,15 +770,17 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "doctrine/orm": "^2.6", + "ocramius/proxy-manager": "^2.1", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "7.0", - "symfony/cache": "^3.4|^4.1", + "phpunit/phpunit": "^7.5", "symfony/phpunit-bridge": "^4.2", - "symfony/property-info": "^3.4|^4.1", - "symfony/validator": "^3.4|^4.1", - "symfony/web-profiler-bundle": "^3.4|^4.1", - "symfony/yaml": "^3.4|^4.1", - "twig/twig": "^1.34|^2.4" + "symfony/property-info": "^3.4.30|^4.3.3", + "symfony/proxy-manager-bridge": "^3.4|^4|^5", + "symfony/twig-bridge": "^3.4|^4.1", + "symfony/validator": "^3.4.30|^4.3.3", + "symfony/web-profiler-bundle": "^3.4.30|^4.3.3", + "symfony/yaml": "^3.4.30|^4.3.3", + "twig/twig": "^1.34|^2.12" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -664,7 +789,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.12.x-dev" } }, "autoload": { @@ -678,20 +803,20 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" } ], "description": "Symfony DoctrineBundle", @@ -702,43 +827,57 @@ "orm", "persistence" ], - "time": "2019-06-04T07:35:05+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2020-05-25T19:50:59+00:00" }, { "name": "doctrine/doctrine-cache-bundle", - "version": "1.3.5", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineCacheBundle.git", - "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927" + "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/5514c90d9fb595e1095e6d66ebb98ce9ef049927", - "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927", + "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/6bee2f9b339847e8a984427353670bad4e7bdccb", + "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb", "shasum": "" }, "require": { "doctrine/cache": "^1.4.2", - "doctrine/inflector": "~1.0", - "php": ">=5.3.2", - "symfony/doctrine-bridge": "~2.7|~3.3|~4.0" + "doctrine/inflector": "^1.0", + "php": "^7.1", + "symfony/doctrine-bridge": "^3.4|^4.0" }, "require-dev": { "instaclick/coding-standard": "~1.1", "instaclick/object-calisthenics-sniffs": "dev-master", "instaclick/symfony2-coding-standard": "dev-remaster", - "phpunit/phpunit": "~4.8.36|~5.6|~6.5|~7.0", + "phpunit/phpunit": "^7.0", "predis/predis": "~0.8", "satooshi/php-coveralls": "^1.0", "squizlabs/php_codesniffer": "~1.5", - "symfony/console": "~2.7|~3.3|~4.0", - "symfony/finder": "~2.7|~3.3|~4.0", - "symfony/framework-bundle": "~2.7|~3.3|~4.0", - "symfony/phpunit-bridge": "~2.7|~3.3|~4.0", - "symfony/security-acl": "~2.7|~3.3", - "symfony/validator": "~2.7|~3.3|~4.0", - "symfony/yaml": "~2.7|~3.3|~4.0" + "symfony/console": "^3.4|^4.0", + "symfony/finder": "^3.4|^4.0", + "symfony/framework-bundle": "^3.4|^4.0", + "symfony/phpunit-bridge": "^3.4|^4.0", + "symfony/security-acl": "^2.8", + "symfony/validator": "^3.4|^4.0", + "symfony/yaml": "^3.4|^4.0" }, "suggest": { "symfony/security-acl": "For using this bundle to cache ACLs" @@ -746,7 +885,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -763,8 +902,8 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Benjamin Eberlei", @@ -779,12 +918,12 @@ "email": "guilhermeblanco@hotmail.com" }, { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" } ], "description": "Symfony Bundle for Doctrine Cache", @@ -793,30 +932,30 @@ "cache", "caching" ], - "time": "2018-11-09T06:25:35+00:00" + "time": "2019-11-29T11:22:01+00:00" }, { "name": "doctrine/event-manager", - "version": "v1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "conflict": { "doctrine/common": "<2.9@dev" }, "require-dev": { - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.0" }, "type": "library", @@ -835,6 +974,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -843,10 +986,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -860,44 +999,65 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Event Manager component", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ "event", - "eventdispatcher", - "eventmanager" + "event dispatcher", + "event manager", + "event system", + "events" ], - "time": "2018-06-11T11:59:03+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" }, { "name": "doctrine/inflector", - "version": "v1.3.0", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -905,6 +1065,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -913,10 +1077,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -926,32 +1086,52 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2018-01-09T20:05:19+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T07:19:59+00:00" }, { "name": "doctrine/instantiator", - "version": "1.2.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -990,24 +1170,38 @@ "constructor", "instantiate" ], - "time": "2019-03-17T17:37:11+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" }, { "name": "doctrine/lexer", - "version": "1.1.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^7.2 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -1017,7 +1211,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -1052,37 +1246,58 @@ "parser", "php" ], - "time": "2019-07-30T19:33:28+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" }, { "name": "doctrine/orm", - "version": "v2.6.4", + "version": "2.7.4", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43" + "reference": "7d84a4998091ece4d645253ac65de9f879eeed2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43", - "reference": "b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43", + "url": "https://api.github.com/repos/doctrine/orm/zipball/7d84a4998091ece4d645253ac65de9f879eeed2f", + "reference": "7d84a4998091ece4d645253ac65de9f879eeed2f", "shasum": "" }, "require": { - "doctrine/annotations": "~1.5", - "doctrine/cache": "~1.6", - "doctrine/collections": "^1.4", - "doctrine/common": "^2.7.1", - "doctrine/dbal": "^2.6", - "doctrine/instantiator": "~1.1", + "composer/package-versions-deprecated": "^1.8", + "doctrine/annotations": "^1.8", + "doctrine/cache": "^1.9.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^2.11 || ^3.0", + "doctrine/dbal": "^2.9.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.3.3 || ^2.0", "ext-pdo": "*", "php": "^7.1", - "symfony/console": "~3.0|~4.0" + "symfony/console": "^3.0|^4.0|^5.0" }, "require-dev": { "doctrine/coding-standard": "^5.0", + "phpstan/phpstan": "^0.12.18", "phpunit/phpunit": "^7.5", - "symfony/yaml": "~3.4|~4.0" + "symfony/yaml": "^3.4|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "suggest": { "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" @@ -1093,7 +1308,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6.x-dev" + "dev-master": "2.7.x-dev" } }, "autoload": { @@ -1128,25 +1343,25 @@ } ], "description": "Object-Relational-Mapper for PHP", - "homepage": "http://www.doctrine-project.org", + "homepage": "https://www.doctrine-project.org/projects/orm.html", "keywords": [ "database", "orm" ], - "time": "2019-09-20T14:30:26+00:00" + "time": "2020-10-10T17:11:26+00:00" }, { "name": "doctrine/persistence", - "version": "1.1.1", + "version": "1.3.8", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48" + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/3da7c9d125591ca83944f477e65ed3d7b4617c48", - "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288", "shasum": "" }, "require": { @@ -1154,26 +1369,28 @@ "doctrine/cache": "^1.0", "doctrine/collections": "^1.0", "doctrine/event-manager": "^1.0", - "doctrine/reflection": "^1.0", - "php": "^7.1" + "doctrine/reflection": "^1.2", + "php": "^7.1 || ^8.0" }, "conflict": { "doctrine/common": "<2.10@dev" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "phpstan/phpstan": "^0.8", - "phpunit/phpunit": "^7.0" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^3.11" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" } }, "notification-url": "https://packagist.org/downloads/", @@ -1181,6 +1398,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1189,10 +1410,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1215,20 +1432,34 @@ "orm", "persistence" ], - "time": "2019-04-23T08:28:24+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2020-06-20T12:56:16+00:00" }, { "name": "doctrine/reflection", - "version": "v1.0.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/reflection.git", - "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6" + "reference": "55e71912dfcd824b2fdd16f2d9afe15684cfce79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/reflection/zipball/02538d3f95e88eb397a5f86274deb2c6175c2ab6", - "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6", + "url": "https://api.github.com/repos/doctrine/reflection/zipball/55e71912dfcd824b2fdd16f2d9afe15684cfce79", + "reference": "55e71912dfcd824b2fdd16f2d9afe15684cfce79", "shasum": "" }, "require": { @@ -1236,18 +1467,20 @@ "ext-tokenizer": "*", "php": "^7.1" }, + "conflict": { + "doctrine/common": "<2.9" + }, "require-dev": { - "doctrine/coding-standard": "^4.0", - "doctrine/common": "^2.8", - "phpstan/phpstan": "^0.9.2", - "phpstan/phpstan-phpunit": "^0.9.4", - "phpunit/phpunit": "^7.0", - "squizlabs/php_codesniffer": "^3.0" + "doctrine/coding-standard": "^5.0", + "doctrine/common": "^2.10", + "phpstan/phpstan": "^0.11.0", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -1260,6 +1493,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1268,10 +1505,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1285,31 +1518,35 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Reflection component", + "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.", "homepage": "https://www.doctrine-project.org/projects/reflection.html", "keywords": [ - "reflection" + "reflection", + "static" ], - "time": "2018-06-14T14:45:07+00:00" + "time": "2020-03-27T11:06:43+00:00" }, { "name": "fig/link-util", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/link-util.git", - "reference": "1a07821801a148be4add11ab0603e4af55a72fac" + "reference": "c038ee75ca13663ddc2d1f185fe6f7533c00832a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/link-util/zipball/1a07821801a148be4add11ab0603e4af55a72fac", - "reference": "1a07821801a148be4add11ab0603e4af55a72fac", + "url": "https://api.github.com/repos/php-fig/link-util/zipball/c038ee75ca13663ddc2d1f185fe6f7533c00832a", + "reference": "c038ee75ca13663ddc2d1f185fe6f7533c00832a", "shasum": "" }, "require": { "php": ">=5.5.0", "psr/link": "~1.0@dev" }, + "provide": { + "psr/link-implementation": "1.0" + }, "require-dev": { "phpunit/phpunit": "^5.1", "squizlabs/php_codesniffer": "^2.3.1" @@ -1344,20 +1581,20 @@ "psr-13", "rest" ], - "time": "2016-10-17T18:31:11+00:00" + "time": "2020-04-27T06:40:36+00:00" }, { "name": "gedmo/doctrine-extensions", - "version": "v2.4.37", + "version": "v2.4.42", "source": { "type": "git", "url": "https://github.com/Atlantic18/DoctrineExtensions.git", - "reference": "5dd471f656e46d815f063bf3f12c667649ec7ffb" + "reference": "b6c4442b4f32ce05673fbdf1fa4a2d5e315cc0a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/5dd471f656e46d815f063bf3f12c667649ec7ffb", - "reference": "5dd471f656e46d815f063bf3f12c667649ec7ffb", + "url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/b6c4442b4f32ce05673fbdf1fa4a2d5e315cc0a4", + "reference": "b6c4442b4f32ce05673fbdf1fa4a2d5e315cc0a4", "shasum": "" }, "require": { @@ -1366,14 +1603,15 @@ "php": ">=5.3.2" }, "conflict": { - "doctrine/annotations": "<1.2" + "doctrine/annotations": "<1.2", + "doctrine/mongodb-odm": ">=2.0" }, "require-dev": { "doctrine/common": ">=2.5.0", - "doctrine/mongodb-odm": ">=1.0.2", + "doctrine/mongodb-odm": ">=1.0.2 <2.0", "doctrine/orm": ">=2.5.0", - "phpunit/phpunit": "^4.8.35|^5.7|^6.5", - "symfony/yaml": "~2.6|~3.0|~4.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "symfony/yaml": "^2.6 || ^3.0 || ^4.0 || ^5.0" }, "suggest": { "doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM", @@ -1395,10 +1633,6 @@ "MIT" ], "authors": [ - { - "name": "David Buchmann", - "email": "david@liip.ch" - }, { "name": "Gediminas Morkevicius", "email": "gediminas.morkevicius@gmail.com" @@ -1406,6 +1640,10 @@ { "name": "Gustavo Falco", "email": "comfortablynumb84@gmail.com" + }, + { + "name": "David Buchmann", + "email": "david@liip.ch" } ], "description": "Doctrine2 behavioral extensions", @@ -1425,7 +1663,7 @@ "tree", "uploadable" ], - "time": "2019-03-17T18:16:12+00:00" + "time": "2020-08-21T01:27:20+00:00" }, { "name": "jdorn/sql-formatter", @@ -1478,103 +1716,113 @@ "time": "2014-01-12T16:20:24+00:00" }, { - "name": "msgphp/domain", - "version": "v0.13.0", + "name": "lcobucci/jwt", + "version": "3.3.3", "source": { "type": "git", - "url": "https://github.com/msgphp/domain.git", - "reference": "0a9b70f148e98ee1c68ec1145e15237412128cbc" + "url": "https://github.com/lcobucci/jwt.git", + "reference": "c1123697f6a2ec29162b82f170dd4a491f524773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/msgphp/domain/zipball/0a9b70f148e98ee1c68ec1145e15237412128cbc", - "reference": "0a9b70f148e98ee1c68ec1145e15237412128cbc", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c1123697f6a2ec29162b82f170dd4a491f524773", + "reference": "c1123697f6a2ec29162b82f170dd4a491f524773", "shasum": "" }, "require": { - "php": "^7.2" - }, - "conflict": { - "symfony/messenger": "<4.3" + "ext-mbstring": "*", + "ext-openssl": "*", + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/orm": "^2.6", - "ramsey/uuid": "^3.7", - "symfony/messenger": "^4.3", - "symfony/var-exporter": "^4.2" + "mikey179/vfsstream": "~1.5", + "phpmd/phpmd": "~2.2", + "phpunit/php-invoker": "~1.1", + "phpunit/phpunit": "^5.7 || ^7.3", + "squizlabs/php_codesniffer": "~2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.13-dev" + "dev-master": "3.1-dev" } }, "autoload": { "psr-4": { - "MsgPhp\\Domain\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Lcobucci\\JWT\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Roland Franssen", - "email": "franssen.roland@gmail.com" + "name": "Luís Otávio Cobucci Oblonczyk", + "email": "lcobucci@gmail.com", + "role": "Developer" } ], - "description": "A dependency-free package to help building a business domain layer", + "description": "A simple library to work with JSON Web Token and JSON Web Signature", "keywords": [ - "command query responsibility segregation", - "cqrs", - "ddd", - "domain model", - "domain-driven-design", - "entity", - "es", - "event sourcing", - "orm", - "repository", - "rich domain model" + "JWS", + "jwt" + ], + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } ], - "time": "2019-08-03T08:10:31+00:00" + "time": "2020-08-20T13:22:28+00:00" }, { - "name": "msgphp/user", - "version": "v0.13.0", + "name": "lexik/jwt-authentication-bundle", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/msgphp/user.git", - "reference": "075bbb3a2c1760a66b29abba283960242bd73d02" + "url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git", + "reference": "0b136a3b8b2125a85dc0ae64527e8c86cbe574f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/msgphp/user/zipball/075bbb3a2c1760a66b29abba283960242bd73d02", - "reference": "075bbb3a2c1760a66b29abba283960242bd73d02", + "url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/0b136a3b8b2125a85dc0ae64527e8c86cbe574f1", + "reference": "0b136a3b8b2125a85dc0ae64527e8c86cbe574f1", "shasum": "" }, "require": { - "msgphp/domain": "^0.13" + "ext-openssl": "*", + "lcobucci/jwt": "^3.2", + "namshi/jose": "^7.2", + "php": "^5.6|^7.0", + "symfony/framework-bundle": "^3.4|^4.0|^5.0", + "symfony/security-bundle": "^3.4|^4.0|^5.0" }, "require-dev": { - "doctrine/orm": "^2.6", - "symfony/form": "^3.4.24 || ^4.2.5", - "symfony/security-core": "^3.4 || ^4.2", - "symfony/validator": "^3.4 || ^4.2" + "symfony/browser-kit": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/phpunit-bridge": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, - "type": "library", + "suggest": { + "gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony", + "spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support" + }, + "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "0.13-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "MsgPhp\\User\\": "" + "Lexik\\Bundle\\JWTAuthenticationBundle\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1586,53 +1834,128 @@ ], "authors": [ { - "name": "Roland Franssen", - "email": "franssen.roland@gmail.com" + "name": "Jeremy Barthe", + "email": "j.barthe@lexik.fr", + "homepage": "https://github.com/jeremyb" + }, + { + "name": "Nicolas Cabot", + "email": "n.cabot@lexik.fr", + "homepage": "https://github.com/slashfan" + }, + { + "name": "Cedric Girard", + "email": "c.girard@lexik.fr", + "homepage": "https://github.com/cedric-g" + }, + { + "name": "Dev Lexik", + "email": "dev@lexik.fr", + "homepage": "https://github.com/lexik" + }, + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com", + "homepage": "https://github.com/chalasr" + }, + { + "name": "Lexik Community", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors" } ], - "description": "A domain layer providing basic user management", + "description": "This bundle provides JWT authentication for your Symfony REST API", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle", "keywords": [ - "ddd", - "domain-driven-design", - "rich domain model", - "security", - "user" + "Authentication", + "JWS", + "api", + "bundle", + "jwt", + "rest", + "symfony" + ], + "funding": [ + { + "url": "https://github.com/chalasr", + "type": "github" + } ], - "time": "2019-08-03T08:10:31+00:00" + "time": "2020-06-14T13:20:35+00:00" }, { - "name": "msgphp/user-bundle", - "version": "v0.13.0", + "name": "matthewbaggett/clamp", + "version": "v1.1", "source": { "type": "git", - "url": "https://github.com/msgphp/user-bundle.git", - "reference": "262d15de66b8380d40e4c8f3869b696bf8ed60b9" + "url": "https://github.com/matthewbaggett/php-clamp.git", + "reference": "1c43a3a628827756cd0f362703d73ad5644ded7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/msgphp/user-bundle/zipball/262d15de66b8380d40e4c8f3869b696bf8ed60b9", - "reference": "262d15de66b8380d40e4c8f3869b696bf8ed60b9", + "url": "https://api.github.com/repos/matthewbaggett/php-clamp/zipball/1c43a3a628827756cd0f362703d73ad5644ded7e", + "reference": "1c43a3a628827756cd0f362703d73ad5644ded7e", "shasum": "" }, - "require": { - "msgphp/user": "^0.13", - "symfony/config": "^3.4 || ^4.2", - "symfony/dependency-injection": "^3.4 || ^4.2", - "symfony/http-kernel": "^3.4 || ^4.2" + "require-dev": { + "phpunit/phpunit": "^4.8", + "satooshi/php-coveralls": "^2.0.0" }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "0.13-dev" + "type": "library", + "autoload": { + "files": [ + "src/Clamp.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Baggett", + "email": "matthew@baggett.me" } + ], + "description": "PHP clamp function to clamp a number between a minimum and a maximum.", + "time": "2018-02-07T14:50:03+00:00" + }, + { + "name": "namshi/jose", + "version": "7.2.3", + "source": { + "type": "git", + "url": "https://github.com/namshi/jose.git", + "reference": "89a24d7eb3040e285dd5925fcad992378b82bcff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/namshi/jose/zipball/89a24d7eb3040e285dd5925fcad992378b82bcff", + "reference": "89a24d7eb3040e285dd5925fcad992378b82bcff", + "shasum": "" + }, + "require": { + "ext-date": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-spl": "*", + "php": ">=5.5", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpseclib/phpseclib": "^2.0", + "phpunit/phpunit": "^4.5|^5.0", + "satooshi/php-coveralls": "^1.0" + }, + "suggest": { + "ext-openssl": "Allows to use OpenSSL as crypto engine.", + "phpseclib/phpseclib": "Allows to use Phpseclib as crypto engine, use version ^2.0." }, + "type": "library", "autoload": { "psr-4": { - "MsgPhp\\UserBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Namshi\\JOSE\\": "src/Namshi/JOSE/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1640,43 +1963,50 @@ ], "authors": [ { - "name": "Roland Franssen", - "email": "franssen.roland@gmail.com" + "name": "Alessandro Nadalin", + "email": "alessandro.nadalin@gmail.com" + }, + { + "name": "Alessandro Cinelli (cirpo)", + "email": "alessandro.cinelli@gmail.com" } ], - "description": "Basic user management (the `User` domain)", + "description": "JSON Object Signing and Encryption library for PHP.", "keywords": [ - "security", - "user" + "JSON Web Signature", + "JSON Web Token", + "JWS", + "json", + "jwt", + "token" ], - "time": "2019-08-03T08:10:31+00:00" + "time": "2016-12-05T07:27:31+00:00" }, { "name": "nelmio/cors-bundle", - "version": "1.5.6", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "10a24c10f242440211ed31075e74f81661c690d9" + "reference": "be4d5824caebc86da9e224e935e02e1201b3ea54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/10a24c10f242440211ed31075e74f81661c690d9", - "reference": "10a24c10f242440211ed31075e74f81661c690d9", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/be4d5824caebc86da9e224e935e02e1201b3ea54", + "reference": "be4d5824caebc86da9e224e935e02e1201b3ea54", "shasum": "" }, "require": { - "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0" + "symfony/framework-bundle": "^4.3 || ^5.0" }, "require-dev": { - "matthiasnoback/symfony-dependency-injection-test": "^1.0 || ^2.0", - "mockery/mockery": "^0.9 || ^1.0", - "symfony/phpunit-bridge": "^2.7 || ^3.0 || ^4.0" + "mockery/mockery": "^1.2", + "symfony/phpunit-bridge": "^4.3 || ^5.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1701,38 +2031,35 @@ "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" } ], - "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony2 application", + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", "keywords": [ "api", "cors", "crossdomain" ], - "time": "2019-06-17T08:53:14+00:00" + "time": "2020-07-22T11:44:28+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -1759,20 +2086,20 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.2", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { @@ -1784,6 +2111,7 @@ "require-dev": { "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", "phpunit/phpunit": "^6.4" }, "type": "library", @@ -1810,35 +2138,33 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-09-12T14:27:41+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", "shasum": "" }, "require": { - "php": "^7.1", + "php": "^7.2 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-tokenizer": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { @@ -1857,7 +2183,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "time": "2020-09-17T18:55:26+00:00" }, { "name": "psr/cache", @@ -2005,16 +2331,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { @@ -2023,7 +2349,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2048,48 +2374,50 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2020-03-23T09:12:05+00:00" }, { "name": "ramsey/uuid", - "version": "3.8.0", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", "symfony/polyfill-ctype": "^1.8" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" }, "suggest": { "ext-ctype": "Provides support for PHP Ctype functions", "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, @@ -2102,13 +2430,21 @@ "autoload": { "psr-4": { "Ramsey\\Uuid\\": "src/" - } + }, + "files": [ + "src/functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, { "name": "Marijn Huizendveld", "email": "marijn.huizendveld@gmail.com" @@ -2116,11 +2452,6 @@ { "name": "Thibaud Fabre", "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" } ], "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", @@ -2130,7 +2461,7 @@ "identifier", "uuid" ], - "time": "2018-07-19T23:38:55+00:00" + "time": "2020-02-21T04:36:14+00:00" }, { "name": "ramsey/uuid-doctrine", @@ -2329,16 +2660,16 @@ }, { "name": "symfony/asset", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "3f97e57596884f7b9158d564a533112a0d19dbdd" + "reference": "5bdbd8878b69e3be16d036890ea3081172ea28c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/3f97e57596884f7b9158d564a533112a0d19dbdd", - "reference": "3f97e57596884f7b9158d564a533112a0d19dbdd", + "url": "https://api.github.com/repos/symfony/asset/zipball/5bdbd8878b69e3be16d036890ea3081172ea28c5", + "reference": "5bdbd8878b69e3be16d036890ea3081172ea28c5", "shasum": "" }, "require": { @@ -2381,20 +2712,20 @@ ], "description": "Symfony Asset Component", "homepage": "https://symfony.com", - "time": "2019-08-03T21:50:52+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/cache", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "1d8f7fee990c586f275cde1a9fc883d6b1e2d43e" + "reference": "8794ccf68ac341fc19311919d2287f7557bfccba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/1d8f7fee990c586f275cde1a9fc883d6b1e2d43e", - "reference": "1d8f7fee990c586f275cde1a9fc883d6b1e2d43e", + "url": "https://api.github.com/repos/symfony/cache/zipball/8794ccf68ac341fc19311919d2287f7557bfccba", + "reference": "8794ccf68ac341fc19311919d2287f7557bfccba", "shasum": "" }, "require": { @@ -2459,24 +2790,24 @@ "caching", "psr6" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-27T09:15:09+00:00" }, { "name": "symfony/cache-contracts", - "version": "v1.1.5", + "version": "v1.1.10", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db" + "reference": "8d5489c10ef90aa7413e4921fc3c0520e24cbed7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", - "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/8d5489c10ef90aa7413e4921fc3c0520e24cbed7", + "reference": "8d5489c10ef90aa7413e4921fc3c0520e24cbed7", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.1.3", "psr/cache": "^1.0" }, "suggest": { @@ -2486,6 +2817,10 @@ "extra": { "branch-alias": { "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2517,20 +2852,34 @@ "interoperability", "standards" ], - "time": "2019-06-13T11:15:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" }, { "name": "symfony/config", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece" + "reference": "7b7d5d35a5ba5a62f2c6c69f574e36595e587d11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/07d49c0f823e0bc367c6d84e35b61419188a5ece", - "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece", + "url": "https://api.github.com/repos/symfony/config/zipball/7b7d5d35a5ba5a62f2c6c69f574e36595e587d11", + "reference": "7b7d5d35a5ba5a62f2c6c69f574e36595e587d11", "shasum": "" }, "require": { @@ -2581,20 +2930,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/console", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "de63799239b3881b8a08f8481b22348f77ed7b36" + "reference": "82aeab8f852a63e83d781617841237944392cd45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36", - "reference": "de63799239b3881b8a08f8481b22348f77ed7b36", + "url": "https://api.github.com/repos/symfony/console/zipball/82aeab8f852a63e83d781617841237944392cd45", + "reference": "82aeab8f852a63e83d781617841237944392cd45", "shasum": "" }, "require": { @@ -2656,20 +3005,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-25T12:32:28+00:00" }, { "name": "symfony/debug", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced" + "reference": "8e362996356f2555d493ee3d8285424853955e8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/afcdea44a2e399c1e4b52246ec8d54c715393ced", - "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced", + "url": "https://api.github.com/repos/symfony/debug/zipball/8e362996356f2555d493ee3d8285424853955e8f", + "reference": "8e362996356f2555d493ee3d8285424853955e8f", "shasum": "" }, "require": { @@ -2712,20 +3061,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:27:59+00:00" + "time": "2020-01-25T12:32:28+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "d3ad14b66ac773ba6123622eb9b5b010165fe3d9" + "reference": "468bfb60a60b7caa03e4722c43f5359df47b4349" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d3ad14b66ac773ba6123622eb9b5b010165fe3d9", - "reference": "d3ad14b66ac773ba6123622eb9b5b010165fe3d9", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/468bfb60a60b7caa03e4722c43f5359df47b4349", + "reference": "468bfb60a60b7caa03e4722c43f5359df47b4349", "shasum": "" }, "require": { @@ -2785,25 +3134,25 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-08-26T16:27:33+00:00" + "time": "2020-01-14T16:43:06+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "d2967b2b43788bd3a7cddeb8bd4567e142b3821c" + "reference": "64cbcf5635fb1b38131332d0b3784faeec9a9e9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/d2967b2b43788bd3a7cddeb8bd4567e142b3821c", - "reference": "d2967b2b43788bd3a7cddeb8bd4567e142b3821c", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/64cbcf5635fb1b38131332d0b3784faeec9a9e9a", + "reference": "64cbcf5635fb1b38131332d0b3784faeec9a9e9a", "shasum": "" }, "require": { "doctrine/event-manager": "~1.0", - "doctrine/persistence": "~1.0", + "doctrine/persistence": "^1.3", "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", @@ -2813,6 +3162,7 @@ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/dependency-injection": "<3.4", "symfony/form": "<4.3", + "symfony/http-kernel": "<4.3.7", "symfony/messenger": "<4.3" }, "require-dev": { @@ -2827,7 +3177,7 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/form": "~4.3", - "symfony/http-kernel": "~3.4|~4.0", + "symfony/http-kernel": "^4.3.7", "symfony/messenger": "~4.3", "symfony/property-access": "~3.4|~4.0", "symfony/property-info": "~3.4|~4.0", @@ -2875,27 +3225,27 @@ ], "description": "Symfony Doctrine Bridge", "homepage": "https://symfony.com", - "time": "2019-08-26T11:29:20+00:00" + "time": "2020-01-23T10:55:13+00:00" }, { "name": "symfony/dotenv", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "1785b18148a016b8f4e6a612291188d568e1f9cd" + "reference": "3e41dc2a3c517819b23cb4d1c95f5116182a8dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1785b18148a016b8f4e6a612291188d568e1f9cd", - "reference": "1785b18148a016b8f4e6a612291188d568e1f9cd", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/3e41dc2a3c517819b23cb4d1c95f5116182a8dd0", + "reference": "3e41dc2a3c517819b23cb4d1c95f5116182a8dd0", "shasum": "" }, "require": { "php": "^7.1.3" }, "require-dev": { - "symfony/process": "~3.4|~4.0" + "symfony/process": "^3.4.2|^4.0" }, "type": "library", "extra": { @@ -2932,20 +3282,20 @@ "env", "environment" ], - "time": "2019-08-03T21:50:52+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2" + "reference": "75f99d7489409207d09c6cd75a6c773ccbb516d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/429d0a1451d4c9c4abe1959b2986b88794b9b7d2", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/75f99d7489409207d09c6cd75a6c773ccbb516d5", + "reference": "75f99d7489409207d09c6cd75a6c773ccbb516d5", "shasum": "" }, "require": { @@ -3002,24 +3352,24 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-09T13:17:05+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.5", + "version": "v1.1.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c" + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.1.3" }, "suggest": { "psr/event-dispatcher": "", @@ -3029,6 +3379,10 @@ "extra": { "branch-alias": { "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3060,20 +3414,34 @@ "interoperability", "standards" ], - "time": "2019-06-20T06:46:26+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" }, { "name": "symfony/expression-language", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "c8b47d8820d3bf75f757eec8a2647584c14cf0c6" + "reference": "fcc3e2085e4832f52bed94d72663962f650dfb23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/c8b47d8820d3bf75f757eec8a2647584c14cf0c6", - "reference": "c8b47d8820d3bf75f757eec8a2647584c14cf0c6", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/fcc3e2085e4832f52bed94d72663962f650dfb23", + "reference": "fcc3e2085e4832f52bed94d72663962f650dfb23", "shasum": "" }, "require": { @@ -3111,20 +3479,20 @@ ], "description": "Symfony ExpressionLanguage Component", "homepage": "https://symfony.com", - "time": "2019-08-08T09:29:19+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/filesystem", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + "reference": "fdc0ac5e64f7555818411a17993bb24be4270769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fdc0ac5e64f7555818411a17993bb24be4270769", + "reference": "fdc0ac5e64f7555818411a17993bb24be4270769", "shasum": "" }, "require": { @@ -3161,20 +3529,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "time": "2020-01-21T08:20:29+00:00" }, { "name": "symfony/finder", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" + "reference": "8174c13b932c71f10cdd8dfcd8f5e494f1e7003d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", - "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "url": "https://api.github.com/repos/symfony/finder/zipball/8174c13b932c71f10cdd8dfcd8f5e494f1e7003d", + "reference": "8174c13b932c71f10cdd8dfcd8f5e494f1e7003d", "shasum": "" }, "require": { @@ -3210,36 +3578,36 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-08-14T12:26:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/flex", - "version": "v1.4.6", + "version": "v1.9.10", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "133e649fdf08aeb8741be1ba955ccbe5cd17c696" + "reference": "7335ec033995aa34133e621627333368f260b626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/133e649fdf08aeb8741be1ba955ccbe5cd17c696", - "reference": "133e649fdf08aeb8741be1ba955ccbe5cd17c696", + "url": "https://api.github.com/repos/symfony/flex/zipball/7335ec033995aa34133e621627333368f260b626", + "reference": "7335ec033995aa34133e621627333368f260b626", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0", - "php": "^7.0" + "composer-plugin-api": "^1.0|^2.0", + "php": ">=7.1" }, "require-dev": { - "composer/composer": "^1.0.2", - "symfony/dotenv": "^3.4|^4.0", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8", - "symfony/process": "^2.7|^3.0|^4.0" + "composer/composer": "^1.0.2|^2.0", + "symfony/dotenv": "^4.4|^5.0", + "symfony/phpunit-bridge": "^4.4|^5.0", + "symfony/process": "^3.4|^4.4|^5.0" }, "type": "composer-plugin", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-main": "1.9-dev" }, "class": "Symfony\\Flex\\Flex" }, @@ -3259,20 +3627,34 @@ } ], "description": "Composer plugin for Symfony", - "time": "2019-09-19T14:55:57+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-14T17:41:54+00:00" }, { "name": "symfony/form", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "eba11fd575e791d72030cb59215a9948791f1e74" + "reference": "05fac4992a100642806a50cc0c84fb4a8a326c14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/eba11fd575e791d72030cb59215a9948791f1e74", - "reference": "eba11fd575e791d72030cb59215a9948791f1e74", + "url": "https://api.github.com/repos/symfony/form/zipball/05fac4992a100642806a50cc0c84fb4a8a326c14", + "reference": "05fac4992a100642806a50cc0c84fb4a8a326c14", "shasum": "" }, "require": { @@ -3343,20 +3725,20 @@ ], "description": "Symfony Form Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/framework-bundle", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "0fd8e354cef6b3da666e585d7ae75aeea2423833" + "reference": "4ea8f63e005af800e140c1bcfb1f748a006defd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/0fd8e354cef6b3da666e585d7ae75aeea2423833", - "reference": "0fd8e354cef6b3da666e585d7ae75aeea2423833", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/4ea8f63e005af800e140c1bcfb1f748a006defd1", + "reference": "4ea8f63e005af800e140c1bcfb1f748a006defd1", "shasum": "" }, "require": { @@ -3374,6 +3756,7 @@ "symfony/routing": "^4.3" }, "conflict": { + "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.1", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", @@ -3382,15 +3765,15 @@ "symfony/console": "<4.3", "symfony/dom-crawler": "<4.3", "symfony/dotenv": "<4.2", - "symfony/form": "<4.3", - "symfony/messenger": "<4.3", + "symfony/form": "<4.3.5", + "symfony/messenger": "<4.3.6", "symfony/property-info": "<3.4", "symfony/serializer": "<4.2", "symfony/stopwatch": "<3.4", - "symfony/translation": "<4.3", + "symfony/translation": "<4.3.6", "symfony/twig-bridge": "<4.1.1", "symfony/validator": "<4.1", - "symfony/workflow": "<4.3" + "symfony/workflow": "<4.3.6" }, "require-dev": { "doctrine/annotations": "~1.7", @@ -3403,11 +3786,11 @@ "symfony/css-selector": "~3.4|~4.0", "symfony/dom-crawler": "^4.3", "symfony/expression-language": "~3.4|~4.0", - "symfony/form": "^4.3.4", + "symfony/form": "^4.3.5", "symfony/http-client": "^4.3", "symfony/lock": "~3.4|~4.0", "symfony/mailer": "^4.3", - "symfony/messenger": "^4.3", + "symfony/messenger": "^4.3.6", "symfony/mime": "^4.3", "symfony/polyfill-intl-icu": "~1.0", "symfony/process": "~3.4|~4.0", @@ -3417,12 +3800,12 @@ "symfony/serializer": "^4.3", "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.3", + "symfony/translation": "^4.3.7", "symfony/twig-bundle": "~2.8|~3.2|~4.0", "symfony/validator": "^4.1", "symfony/var-dumper": "^4.3", "symfony/web-link": "~3.4|~4.0", - "symfony/workflow": "^4.3", + "symfony/workflow": "^4.3.6", "symfony/yaml": "~3.4|~4.0", "twig/twig": "~1.41|~2.10" }, @@ -3466,20 +3849,20 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-14T14:28:35+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc" + "reference": "90af881cedc857dca17990cae96f37727b8ae1d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d804bea118ff340a12e22a79f9c7e7eb56b35adc", - "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/90af881cedc857dca17990cae96f37727b8ae1d6", + "reference": "90af881cedc857dca17990cae96f37727b8ae1d6", "shasum": "" }, "require": { @@ -3521,20 +3904,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-31T09:10:37+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52" + "reference": "fcd8fe5b98d435da856b310a01a4f281668607c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5e0fc71be03d52cd00c423061cfd300bd6f92a52", - "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fcd8fe5b98d435da856b310a01a4f281668607c0", + "reference": "fcd8fe5b98d435da856b310a01a4f281668607c0", "shasum": "" }, "require": { @@ -3613,20 +3996,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-08-26T16:47:42+00:00" + "time": "2020-01-31T12:38:38+00:00" }, { "name": "symfony/inflector", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", - "reference": "b25a8dc15fada858432efa083c1ecd2cef5991a7" + "reference": "8c699257379098d26fa400edad29f703b380efcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/inflector/zipball/b25a8dc15fada858432efa083c1ecd2cef5991a7", - "reference": "b25a8dc15fada858432efa083c1ecd2cef5991a7", + "url": "https://api.github.com/repos/symfony/inflector/zipball/8c699257379098d26fa400edad29f703b380efcf", + "reference": "8c699257379098d26fa400edad29f703b380efcf", "shasum": "" }, "require": { @@ -3671,20 +4054,20 @@ "symfony", "words" ], - "time": "2019-08-06T18:44:23+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/intl", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "8db5505703c5bdb23d524fd994dad2f781966538" + "reference": "2d139d02ddae582c382d30cccd2ee4c814043518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/8db5505703c5bdb23d524fd994dad2f781966538", - "reference": "8db5505703c5bdb23d524fd994dad2f781966538", + "url": "https://api.github.com/repos/symfony/intl/zipball/2d139d02ddae582c382d30cccd2ee4c814043518", + "reference": "2d139d02ddae582c382d30cccd2ee4c814043518", "shasum": "" }, "require": { @@ -3746,20 +4129,20 @@ "l10n", "localization" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/messenger", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "ac9ab05acc8eba0ac311eb8511484f0c5155cde2" + "reference": "c22639327e2c7c1dd4de1156038a7ecc21ffbf60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/ac9ab05acc8eba0ac311eb8511484f0c5155cde2", - "reference": "ac9ab05acc8eba0ac311eb8511484f0c5155cde2", + "url": "https://api.github.com/repos/symfony/messenger/zipball/c22639327e2c7c1dd4de1156038a7ecc21ffbf60", + "reference": "c22639327e2c7c1dd4de1156038a7ecc21ffbf60", "shasum": "" }, "require": { @@ -3767,11 +4150,12 @@ "psr/log": "~1.0" }, "conflict": { + "doctrine/persistence": "<1.3", "symfony/debug": "<4.1", "symfony/event-dispatcher": "<4.3" }, "require-dev": { - "doctrine/dbal": "^2.5", + "doctrine/dbal": "^2.6", "psr/cache": "~1.0", "symfony/console": "~3.4|~4.0", "symfony/debug": "~4.1", @@ -3820,20 +4204,20 @@ ], "description": "Symfony Messenger Component", "homepage": "https://symfony.com", - "time": "2019-08-23T06:45:45+00:00" + "time": "2020-01-29T12:59:11+00:00" }, { "name": "symfony/mime", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "987a05df1c6ac259b34008b932551353f4f408df" + "reference": "50f65ca2a6c33702728024d33e4b9461f67623c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/987a05df1c6ac259b34008b932551353f4f408df", - "reference": "987a05df1c6ac259b34008b932551353f4f408df", + "url": "https://api.github.com/repos/symfony/mime/zipball/50f65ca2a6c33702728024d33e4b9461f67623c4", + "reference": "50f65ca2a6c33702728024d33e4b9461f67623c4", "shasum": "" }, "require": { @@ -3879,20 +4263,20 @@ "mime", "mime-type" ], - "time": "2019-08-22T08:16:11+00:00" + "time": "2020-01-01T11:51:43+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "81c2e120522a42f623233968244baebd6b36cb6a" + "reference": "3438c6fe65a9794b0e9f3686d0e3771412a2c47a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/81c2e120522a42f623233968244baebd6b36cb6a", - "reference": "81c2e120522a42f623233968244baebd6b36cb6a", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3438c6fe65a9794b0e9f3686d0e3771412a2c47a", + "reference": "3438c6fe65a9794b0e9f3686d0e3771412a2c47a", "shasum": "" }, "require": { @@ -3933,20 +4317,20 @@ "configuration", "options" ], - "time": "2019-08-08T09:29:19+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.12.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "66810b9d6eb4af54d543867909d65ab9af654d7e" + "reference": "4e45a6e39041a9cc78835b11abc47874ae302a55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/66810b9d6eb4af54d543867909d65ab9af654d7e", - "reference": "66810b9d6eb4af54d543867909d65ab9af654d7e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/4e45a6e39041a9cc78835b11abc47874ae302a55", + "reference": "4e45a6e39041a9cc78835b11abc47874ae302a55", "shasum": "" }, "require": { @@ -3959,7 +4343,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3991,26 +4379,123 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.12.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" + "reference": "5dcab1bc7146cf8c1beaa4502a3d9be344334251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/5dcab1bc7146cf8c1beaa4502a3d9be344334251", + "reference": "5dcab1bc7146cf8c1beaa4502a3d9be344334251", "shasum": "" }, "require": { "php": ">=5.3.3", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.9" + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php70": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-08-04T06:02:08+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" }, "suggest": { "ext-intl": "For best performance" @@ -4018,15 +4503,22 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "files": [ "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4035,38 +4527,52 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", "intl", + "normalizer", "polyfill", "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", "shasum": "" }, "require": { @@ -4078,7 +4584,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4112,20 +4622,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.12.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "04ce3335667451138df4307d6a9b61565560199e" + "reference": "639447d008615574653fb3bc60d1986d7172eaae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", - "reference": "04ce3335667451138df4307d6a9b61565560199e", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae", + "reference": "639447d008615574653fb3bc60d1986d7172eaae", "shasum": "" }, "require": { @@ -4134,7 +4658,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4167,20 +4695,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.12.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", "shasum": "" }, "require": { @@ -4189,7 +4731,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4225,20 +4771,34 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/property-access", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "bb0c302375ffeef60c31e72a4539611b7f787565" + "reference": "28ecead27bd17937b3f904396b026a8e3915d0cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/bb0c302375ffeef60c31e72a4539611b7f787565", - "reference": "bb0c302375ffeef60c31e72a4539611b7f787565", + "url": "https://api.github.com/repos/symfony/property-access/zipball/28ecead27bd17937b3f904396b026a8e3915d0cd", + "reference": "28ecead27bd17937b3f904396b026a8e3915d0cd", "shasum": "" }, "require": { @@ -4292,20 +4852,20 @@ "property path", "reflection" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/property-info", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "29546235b37f7bd279c763314cd4c962aedd1e6d" + "reference": "169aafe8f2a01ec50fb324f5d24bbd61a5799df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/29546235b37f7bd279c763314cd4c962aedd1e6d", - "reference": "29546235b37f7bd279c763314cd4c962aedd1e6d", + "url": "https://api.github.com/repos/symfony/property-info/zipball/169aafe8f2a01ec50fb324f5d24bbd61a5799df1", + "reference": "169aafe8f2a01ec50fb324f5d24bbd61a5799df1", "shasum": "" }, "require": { @@ -4368,20 +4928,20 @@ "type", "validator" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/routing", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f" + "reference": "6cc4b6a92e3c623b2c7e56180728839b0caf8564" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", - "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", + "url": "https://api.github.com/repos/symfony/routing/zipball/6cc4b6a92e3c623b2c7e56180728839b0caf8564", + "reference": "6cc4b6a92e3c623b2c7e56180728839b0caf8564", "shasum": "" }, "require": { @@ -4444,20 +5004,20 @@ "uri", "url" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-08T14:00:15+00:00" }, { "name": "symfony/security-bundle", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "97ba8648e718999793e79ab4d1f1582c8d19be9d" + "reference": "1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/97ba8648e718999793e79ab4d1f1582c8d19be9d", - "reference": "97ba8648e718999793e79ab4d1f1582c8d19be9d", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85", + "reference": "1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85", "shasum": "" }, "require": { @@ -4469,7 +5029,7 @@ "symfony/security-core": "~4.3", "symfony/security-csrf": "~4.2", "symfony/security-guard": "~4.2", - "symfony/security-http": "^4.3" + "symfony/security-http": "~4.3.10|^4.4.3" }, "conflict": { "symfony/browser-kit": "<4.2", @@ -4528,20 +5088,20 @@ ], "description": "Symfony SecurityBundle", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/security-core", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "a8c67a8bc6bd8012c5d6b70cb030ca3422476caa" + "reference": "8d008438e4bbdf04086d1048d51cc1b5dfac2046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/a8c67a8bc6bd8012c5d6b70cb030ca3422476caa", - "reference": "a8c67a8bc6bd8012c5d6b70cb030ca3422476caa", + "url": "https://api.github.com/repos/symfony/security-core/zipball/8d008438e4bbdf04086d1048d51cc1b5dfac2046", + "reference": "8d008438e4bbdf04086d1048d51cc1b5dfac2046", "shasum": "" }, "require": { @@ -4600,20 +5160,20 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-31T09:10:37+00:00" }, { "name": "symfony/security-csrf", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "d218ba086ef4a68081f3dd5ec11611f5d64d58f3" + "reference": "9e435026ab45f073880d1fbe0e1b17e7df6bf642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/d218ba086ef4a68081f3dd5ec11611f5d64d58f3", - "reference": "d218ba086ef4a68081f3dd5ec11611f5d64d58f3", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/9e435026ab45f073880d1fbe0e1b17e7df6bf642", + "reference": "9e435026ab45f073880d1fbe0e1b17e7df6bf642", "shasum": "" }, "require": { @@ -4659,20 +5219,20 @@ ], "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", - "time": "2019-08-13T06:39:03+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/security-guard", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/security-guard.git", - "reference": "cf06aa4f8ea38a769476c4f5989f1dc400a308a1" + "reference": "5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/cf06aa4f8ea38a769476c4f5989f1dc400a308a1", - "reference": "cf06aa4f8ea38a769476c4f5989f1dc400a308a1", + "url": "https://api.github.com/repos/symfony/security-guard/zipball/5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438", + "reference": "5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438", "shasum": "" }, "require": { @@ -4713,20 +5273,20 @@ ], "description": "Symfony Security Component - Guard", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T18:57:41+00:00" }, { "name": "symfony/security-http", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "65281f9b7c7a77cccaa5b89026ef2a02940dc2cc" + "reference": "2b4b8632956c680400006376fad0a4f9889d4be1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/65281f9b7c7a77cccaa5b89026ef2a02940dc2cc", - "reference": "65281f9b7c7a77cccaa5b89026ef2a02940dc2cc", + "url": "https://api.github.com/repos/symfony/security-http/zipball/2b4b8632956c680400006376fad0a4f9889d4be1", + "reference": "2b4b8632956c680400006376fad0a4f9889d4be1", "shasum": "" }, "require": { @@ -4778,20 +5338,20 @@ ], "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-31T09:10:37+00:00" }, { "name": "symfony/serializer", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6" + "reference": "cd4f545209e1f3d408b5adda729c59bfd714a1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6", - "reference": "702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6", + "url": "https://api.github.com/repos/symfony/serializer/zipball/cd4f545209e1f3d408b5adda729c59bfd714a1a5", + "reference": "cd4f545209e1f3d408b5adda729c59bfd714a1a5", "shasum": "" }, "require": { @@ -4858,24 +5418,24 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/service-contracts", - "version": "v1.1.6", + "version": "v1.1.9", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3" + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3", - "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b776d18b303a39f56c63747bcb977ad4b27aca26", + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.1.3", "psr/container": "^1.0" }, "suggest": { @@ -4885,6 +5445,10 @@ "extra": { "branch-alias": { "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4916,20 +5480,34 @@ "interoperability", "standards" ], - "time": "2019-08-20T14:44:19+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" }, { "name": "symfony/templating", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/templating.git", - "reference": "15407776e1fe250ed3fa1c1a679482c60c2affe3" + "reference": "f566e0d4624eb4a4ec5847fdb46808ffc4168a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/templating/zipball/15407776e1fe250ed3fa1c1a679482c60c2affe3", - "reference": "15407776e1fe250ed3fa1c1a679482c60c2affe3", + "url": "https://api.github.com/repos/symfony/templating/zipball/f566e0d4624eb4a4ec5847fdb46808ffc4168a0c", + "reference": "f566e0d4624eb4a4ec5847fdb46808ffc4168a0c", "shasum": "" }, "require": { @@ -4972,24 +5550,24 @@ ], "description": "Symfony Templating Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/translation-contracts", - "version": "v1.1.6", + "version": "v1.1.10", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a" + "reference": "84180a25fad31e23bebd26ca09d89464f082cacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", - "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/84180a25fad31e23bebd26ca09d89464f082cacc", + "reference": "84180a25fad31e23bebd26ca09d89464f082cacc", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.1.3" }, "suggest": { "symfony/translation-implementation": "" @@ -4998,6 +5576,10 @@ "extra": { "branch-alias": { "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5029,20 +5611,34 @@ "interoperability", "standards" ], - "time": "2019-08-02T12:15:04+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" }, { "name": "symfony/twig-bridge", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "cd6c551dc5d62b520d1a973fb4cb2c46bfc00b62" + "reference": "9574613b74ed066f775eaf94bb15476ef58609de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/cd6c551dc5d62b520d1a973fb4cb2c46bfc00b62", - "reference": "cd6c551dc5d62b520d1a973fb4cb2c46bfc00b62", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9574613b74ed066f775eaf94bb15476ef58609de", + "reference": "9574613b74ed066f775eaf94bb15476ef58609de", "shasum": "" }, "require": { @@ -5052,7 +5648,7 @@ }, "conflict": { "symfony/console": "<3.4", - "symfony/form": "<4.3.4", + "symfony/form": "<4.3.5", "symfony/http-foundation": "<4.3", "symfony/translation": "<4.2", "symfony/workflow": "<4.3" @@ -5065,7 +5661,7 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/form": "^4.3.4", + "symfony/form": "^4.3.5", "symfony/http-foundation": "~4.3", "symfony/http-kernel": "~3.4|~4.0", "symfony/mime": "~4.3", @@ -5130,7 +5726,7 @@ ], "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/twig-bundle", @@ -5211,16 +5807,16 @@ }, { "name": "symfony/validator", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "173b483999c2acad8e040633105733318dcc8a83" + "reference": "0d2dcf4ae26db5b6781f40fcab9785f427ee7fa4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/173b483999c2acad8e040633105733318dcc8a83", - "reference": "173b483999c2acad8e040633105733318dcc8a83", + "url": "https://api.github.com/repos/symfony/validator/zipball/0d2dcf4ae26db5b6781f40fcab9785f427ee7fa4", + "reference": "0d2dcf4ae26db5b6781f40fcab9785f427ee7fa4", "shasum": "" }, "require": { @@ -5300,20 +5896,20 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2019-08-26T09:28:48+00:00" + "time": "2020-01-31T09:10:37+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2" + "reference": "563f728784ea09c8154ea57cf8192ae5d6f0d277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d5b4e2d334c1d80e42876c7d489896cfd37562f2", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/563f728784ea09c8154ea57cf8192ae5d6f0d277", + "reference": "563f728784ea09c8154ea57cf8192ae5d6f0d277", "shasum": "" }, "require": { @@ -5360,20 +5956,20 @@ "instantiate", "serialize" ], - "time": "2019-08-22T07:33:08+00:00" + "time": "2020-01-01T11:51:43+00:00" }, { "name": "symfony/web-link", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "4bd0ce7c54d604300deee8eb1b1beda856fbba20" + "reference": "5f47af858f264ce1d29388fb2a05f54e3c687794" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/4bd0ce7c54d604300deee8eb1b1beda856fbba20", - "reference": "4bd0ce7c54d604300deee8eb1b1beda856fbba20", + "url": "https://api.github.com/repos/symfony/web-link/zipball/5f47af858f264ce1d29388fb2a05f54e3c687794", + "reference": "5f47af858f264ce1d29388fb2a05f54e3c687794", "shasum": "" }, "require": { @@ -5433,20 +6029,20 @@ "psr13", "push" ], - "time": "2019-08-08T09:29:19+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/yaml", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686" + "reference": "8e0a95493b734ca8195acf3e1f3d89e88b957db5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", - "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8e0a95493b734ca8195acf3e1f3d89e88b957db5", + "reference": "8e0a95493b734ca8195acf3e1f3d89e88b957db5", "shasum": "" }, "require": { @@ -5492,35 +6088,35 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:27:59+00:00" + "time": "2020-01-21T11:09:03+00:00" }, { "name": "twig/twig", - "version": "v2.12.5", + "version": "v2.13.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "18772e0190734944277ee97a02a9a6c6555fcd94" + "reference": "57e96259776ddcacf1814885fc3950460c8e18ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/18772e0190734944277ee97a02a9a6c6555fcd94", - "reference": "18772e0190734944277ee97a02a9a6c6555fcd94", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/57e96259776ddcacf1814885fc3950460c8e18ef", + "reference": "57e96259776ddcacf1814885fc3950460c8e18ef", "shasum": "" }, "require": { - "php": "^7.0", + "php": ">=7.1.3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.0" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12-dev" + "dev-master": "2.13-dev" } }, "autoload": { @@ -5557,35 +6153,44 @@ "keywords": [ "templating" ], - "time": "2020-02-11T15:31:23+00:00" + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2020-08-05T15:09:04+00:00" }, { "name": "webmozart/assert", - "version": "1.5.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", + "php": "^5.3.3 || ^7.0 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -5607,7 +6212,7 @@ "check", "validate" ], - "time": "2019-08-24T08:43:50+00:00" + "time": "2020-07-08T17:02:28+00:00" }, { "name": "willdurand/negotiation", @@ -5665,37 +6270,39 @@ "packages-dev": [ { "name": "behat/behat", - "version": "v3.5.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/Behat/Behat.git", - "reference": "e4bce688be0c2029dc1700e46058d86428c63cab" + "reference": "08052f739619a9e9f62f457a67302f0715e6dd13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Behat/zipball/e4bce688be0c2029dc1700e46058d86428c63cab", - "reference": "e4bce688be0c2029dc1700e46058d86428c63cab", + "url": "https://api.github.com/repos/Behat/Behat/zipball/08052f739619a9e9f62f457a67302f0715e6dd13", + "reference": "08052f739619a9e9f62f457a67302f0715e6dd13", "shasum": "" }, "require": { - "behat/gherkin": "^4.5.1", + "behat/gherkin": "^4.6.0", "behat/transliterator": "^1.2", - "container-interop/container-interop": "^1.2", "ext-mbstring": "*", "php": ">=5.3.3", "psr/container": "^1.0", - "symfony/class-loader": "~2.1||~3.0", - "symfony/config": "~2.3||~3.0||~4.0", - "symfony/console": "~2.7.40||^2.8.33||~3.3.15||^3.4.3||^4.0.3", - "symfony/dependency-injection": "~2.1||~3.0||~4.0", - "symfony/event-dispatcher": "~2.1||~3.0||~4.0", - "symfony/translation": "~2.3||~3.0||~4.0", - "symfony/yaml": "~2.1||~3.0||~4.0" + "symfony/config": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", + "symfony/console": "^2.7.51 || ^2.8.33 || ^3.3.15 || ^3.4.3 || ^4.0.3 || ^5.0", + "symfony/dependency-injection": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", + "symfony/event-dispatcher": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", + "symfony/translation": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", + "symfony/yaml": "^2.7.51 || ^3.0 || ^4.0 || ^5.0" }, "require-dev": { + "container-interop/container-interop": "^1.2", "herrera-io/box": "~1.6.1", - "phpunit/phpunit": "^4.8.36|^6.3", - "symfony/process": "~2.5|~3.0|~4.0" + "phpunit/phpunit": "^4.8.36 || ^6.5.14 || ^7.5.20", + "symfony/process": "~2.5 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-dom": "Needed to output test results in JUnit format." }, "bin": [ "bin/behat" @@ -5703,13 +6310,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.5.x-dev" + "dev-master": "3.6.x-dev" } }, "autoload": { - "psr-0": { - "Behat\\Behat": "src/", - "Behat\\Testwork": "src/" + "psr-4": { + "Behat\\Behat\\": "src/Behat/Behat/", + "Behat\\Testwork\\": "src/Behat/Testwork/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5739,20 +6346,20 @@ "symfony", "testing" ], - "time": "2018-08-10T18:56:51+00:00" + "time": "2020-06-03T13:08:44+00:00" }, { "name": "behat/gherkin", - "version": "v4.6.0", + "version": "v4.6.2", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07" + "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07", - "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31", + "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31", "shasum": "" }, "require": { @@ -5798,7 +6405,68 @@ "gherkin", "parser" ], - "time": "2019-01-16T14:22:17+00:00" + "time": "2020-03-17T14:03:26+00:00" + }, + { + "name": "behat/mink", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/Mink.git", + "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/07c6a9fe3fa98c2de074b25d9ed26c22904e3887", + "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "^2.7|^3.0|^4.0|^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20", + "symfony/debug": "^2.7|^3.0|^4.0", + "symfony/phpunit-bridge": "^3.4.38 || ^5.0.5" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)", + "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Browser controller/emulator abstraction for PHP", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "time": "2020-03-11T15:45:53+00:00" }, { "name": "behat/symfony2-extension", @@ -5862,36 +6530,61 @@ "time": "2018-04-20T15:48:23+00:00" }, { - "name": "container-interop/container-interop", - "version": "1.2.0", + "name": "behatch/contexts", + "version": "3.3.0", "source": { "type": "git", - "url": "https://github.com/container-interop/container-interop.git", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + "url": "https://github.com/Behatch/contexts.git", + "reference": "e45284b1dddb9dce78e186a56683527802bccd47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "url": "https://api.github.com/repos/Behatch/contexts/zipball/e45284b1dddb9dce78e186a56683527802bccd47", + "reference": "e45284b1dddb9dce78e186a56683527802bccd47", "shasum": "" }, "require": { - "psr/container": "^1.0" + "behat/behat": "^3.0.13", + "friends-of-behat/mink-extension": "^2.3.1", + "justinrainbow/json-schema": "^5.0", + "php": ">=5.5", + "symfony/dom-crawler": "^2.4|^3.0|^4.0|^5.0", + "symfony/http-foundation": "^2.3|^3.0|^4.0|^5.0", + "symfony/property-access": "^2.3|^3.0|^4.0|^5.0" + }, + "replace": { + "sanpi/behatch-contexts": "self.version" + }, + "require-dev": { + "atoum/atoum": "^2.8|^3.0", + "behat/mink-goutte-driver": "^1.1", + "behat/mink-selenium2-driver": "^1.4@dev", + "fabpot/goutte": "^3.2", + "guzzlehttp/guzzle": "^6.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, "autoload": { "psr-4": { - "Interop\\Container\\": "src/Interop/Container/" + "Behatch\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "beerware" + ], + "description": "Behatch contexts", + "keywords": [ + "BDD", + "Behat", + "Context", + "Symfony2" ], - "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "homepage": "https://github.com/container-interop/container-interop", - "abandoned": "psr/container", - "time": "2017-02-14T19:40:03+00:00" + "time": "2020-02-27T08:40:50+00:00" }, { "name": "daraeman/WordToNumber", @@ -5933,32 +6626,35 @@ }, { "name": "doctrine/data-fixtures", - "version": "v1.3.2", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "09b16943b27f3d80d63988d100ff256148c2f78b" + "reference": "16a03fadb5473f49aad70384002dfd5012fe680e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/09b16943b27f3d80d63988d100ff256148c2f78b", - "reference": "09b16943b27f3d80d63988d100ff256148c2f78b", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/16a03fadb5473f49aad70384002dfd5012fe680e", + "reference": "16a03fadb5473f49aad70384002dfd5012fe680e", "shasum": "" }, "require": { - "doctrine/common": "~2.2", - "php": "^7.1" + "doctrine/common": "^2.13|^3.0", + "doctrine/persistence": "^1.3.3|^2.0", + "php": "^7.2 || ^8.0" }, "conflict": { "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { + "doctrine/coding-standard": "^6.0", "doctrine/dbal": "^2.5.4", - "doctrine/orm": "^2.5.4", + "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", + "doctrine/orm": "^2.7.0", "phpunit/phpunit": "^7.0" }, "suggest": { - "alcaeus/mongo-php-adapter": "For using MongoDB ODM with PHP 7", + "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures", "doctrine/orm": "For loading ORM fixtures", "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures" @@ -5966,7 +6662,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -5989,7 +6685,21 @@ "keywords": [ "database" ], - "time": "2019-07-10T18:30:35+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdata-fixtures", + "type": "tidelift" + } + ], + "time": "2020-09-01T07:13:28+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", @@ -6093,29 +6803,90 @@ "description": "A php library to manipulate Swagger specifications", "time": "2018-07-27T06:40:00+00:00" }, + { + "name": "friends-of-behat/mink-extension", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfBehat/MinkExtension.git", + "reference": "80f7849ba53867181b7e412df9210e12fba50177" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfBehat/MinkExtension/zipball/80f7849ba53867181b7e412df9210e12fba50177", + "reference": "80f7849ba53867181b7e412df9210e12fba50177", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.5", + "behat/mink": "^1.5", + "php": ">=5.3.2", + "symfony/config": "^2.7|^3.0|^4.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "^1.1", + "phpspec/phpspec": "^2.0" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\MinkExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com" + }, + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Mink extension for Behat", + "homepage": "http://extensions.behat.org/mink", + "keywords": [ + "browser", + "gui", + "test", + "web" + ], + "time": "2018-02-06T15:36:30+00:00" + }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.5.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { "psr/log": "Required for using the Log middleware" @@ -6123,16 +6894,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "6.5-dev" } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6156,27 +6927,27 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2020-06-16T21:01:06+00:00" }, { "name": "guzzlehttp/promises", - "version": "v1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + "reference": "60d379c243457e073cff02bc323a2a86cb355631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", + "reference": "60d379c243457e073cff02bc323a2a86cb355631", "shasum": "" }, "require": { - "php": ">=5.5.0" + "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "^4.0" + "symfony/phpunit-bridge": "^4.4 || ^5.1" }, "type": "library", "extra": { @@ -6207,20 +6978,20 @@ "keywords": [ "promise" ], - "time": "2016-12-20T10:07:11+00:00" + "time": "2020-09-30T07:37:28+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", "shasum": "" }, "require": { @@ -6233,15 +7004,15 @@ }, "require-dev": { "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" }, "suggest": { - "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -6278,24 +7049,90 @@ "uri", "url" ], - "time": "2019-07-01T23:21:34+00:00" + "time": "2020-09-30T07:37:11+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.10", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2020-05-27T16:41:55+00:00" }, { "name": "league/oauth2-client", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "cc114abc622a53af969e8664722e84ca36257530" + "reference": "d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/cc114abc622a53af969e8664722e84ca36257530", - "reference": "cc114abc622a53af969e8664722e84ca36257530", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb", + "reference": "d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", "paragonie/random_compat": "^1|^2|^9.99", "php": "^5.6|^7.0" }, @@ -6345,7 +7182,7 @@ "oauth2", "single sign on" ], - "time": "2018-11-22T18:33:57+00:00" + "time": "2020-07-18T17:54:32+00:00" }, { "name": "liip/functional-test-bundle", @@ -6426,16 +7263,16 @@ }, { "name": "monolog/monolog", - "version": "1.25.1", + "version": "1.25.5", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf" + "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf", - "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1817faadd1846cd08be9a49e905dc68823bc38c0", + "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0", "shasum": "" }, "require": { @@ -6449,11 +7286,10 @@ "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", + "php-parallel-lint/php-parallel-lint": "^1.0", "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", "ruflin/elastica": ">=0.90 <3.0", "sentry/sentry": "^0.13", "swiftmailer/swiftmailer": "^5.3|^6.0" @@ -6500,24 +7336,34 @@ "logging", "psr-3" ], - "time": "2019-09-06T13:49:17+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-07-23T08:35:51+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "replace": { "myclabs/deep-copy": "self.version" @@ -6548,7 +7394,13 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" }, { "name": "nelmio/api-doc-bundle", @@ -6635,16 +7487,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.2.4", + "version": "v4.10.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4" + "reference": "658f1be311a230e0907f5dfe0213742aff0596de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/97e59c7a16464196a8b9c77c47df68e4a39a45c4", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/658f1be311a230e0907f5dfe0213742aff0596de", + "reference": "658f1be311a230e0907f5dfe0213742aff0596de", "shasum": "" }, "require": { @@ -6652,7 +7504,8 @@ "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -6660,7 +7513,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.9-dev" } }, "autoload": { @@ -6682,7 +7535,7 @@ "parser", "php" ], - "time": "2019-09-01T07:51:21+00:00" + "time": "2020-09-26T10:30:38+00:00" }, { "name": "phar-io/manifest", @@ -6788,33 +7641,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.1", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -6847,7 +7700,7 @@ "spy", "stub" ], - "time": "2019-06-13T12:50:23+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7099,20 +7952,21 @@ "keywords": [ "tokenizer" ], + "abandoned": true, "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.16", + "version": "7.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661" + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/316afa6888d2562e04aeb67ea7f2017a0eb41661", - "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", "shasum": "" }, "require": { @@ -7183,7 +8037,7 @@ "testing", "xunit" ], - "time": "2019-09-14T09:08:39+00:00" + "time": "2020-01-08T08:45:45+00:00" }, { "name": "psr/http-message", @@ -7442,16 +8296,16 @@ }, { "name": "sebastian/environment", - "version": "4.2.2", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", "shasum": "" }, "require": { @@ -7491,7 +8345,7 @@ "environment", "hhvm" ], - "time": "2019-05-05T09:05:15+00:00" + "time": "2019-11-20T08:46:58+00:00" }, { "name": "sebastian/exporter", @@ -7886,16 +8740,16 @@ }, { "name": "softonic/guzzle-oauth2-middleware", - "version": "1.2.2", + "version": "1.3", "source": { "type": "git", "url": "https://github.com/softonic/guzzle-oauth2-middleware.git", - "reference": "4051b3cfe5fe20581b207b05d903e49b1ccd72f1" + "reference": "25bad4609f969615a69da2530b783f231cf2b0c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/softonic/guzzle-oauth2-middleware/zipball/4051b3cfe5fe20581b207b05d903e49b1ccd72f1", - "reference": "4051b3cfe5fe20581b207b05d903e49b1ccd72f1", + "url": "https://api.github.com/repos/softonic/guzzle-oauth2-middleware/zipball/25bad4609f969615a69da2530b783f231cf2b0c7", + "reference": "25bad4609f969615a69da2530b783f231cf2b0c7", "shasum": "" }, "require": { @@ -7924,20 +8778,20 @@ "middleware", "oauth2" ], - "time": "2019-08-07T08:34:15+00:00" + "time": "2020-01-23T14:35:38+00:00" }, { "name": "symfony/browser-kit", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "9e5dddb637b13db82e35695a8603fe6e118cc119" + "reference": "66d301ce3458b522e3b1f2a76ecfccd1834dcf90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9e5dddb637b13db82e35695a8603fe6e118cc119", - "reference": "9e5dddb637b13db82e35695a8603fe6e118cc119", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/66d301ce3458b522e3b1f2a76ecfccd1834dcf90", + "reference": "66d301ce3458b522e3b1f2a76ecfccd1834dcf90", "shasum": "" }, "require": { @@ -7983,41 +8837,104 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v4.3.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "32203e7cc318dcfd1d5fb12ab35e595fc6016206" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/32203e7cc318dcfd1d5fb12ab35e595fc6016206", + "reference": "32203e7cc318dcfd1d5fb12ab35e595fc6016206", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T12:24:57+00:00" }, { - "name": "symfony/class-loader", - "version": "v3.4.31", + "name": "symfony/debug-bundle", + "version": "v4.3.11", "source": { "type": "git", - "url": "https://github.com/symfony/class-loader.git", - "reference": "e212b06996819a2bce026a63da03b7182d05a690" + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "37f558ddd74933f0254bb5e3b6b758e1ee7ff699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/e212b06996819a2bce026a63da03b7182d05a690", - "reference": "e212b06996819a2bce026a63da03b7182d05a690", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/37f558ddd74933f0254bb5e3b6b758e1ee7ff699", + "reference": "37f558ddd74933f0254bb5e3b6b758e1ee7ff699", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "ext-xml": "*", + "php": "^7.1.3", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/twig-bridge": "~3.4|~4.0", + "symfony/var-dumper": "^4.1.1" + }, + "conflict": { + "symfony/config": "<4.2", + "symfony/dependency-injection": "<3.4" }, "require-dev": { - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/polyfill-apcu": "~1.1" + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/web-profiler-bundle": "~3.4|~4.0" }, "suggest": { - "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + "symfony/config": "For service container configuration", + "symfony/dependency-injection": "For using as a service from the container" }, - "type": "library", + "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\ClassLoader\\": "" + "Symfony\\Bundle\\DebugBundle\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8037,22 +8954,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ClassLoader Component", + "description": "Symfony DebugBundle", "homepage": "https://symfony.com", - "time": "2019-08-20T13:31:17+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "cc686552948d627528c0e2e759186dff67c2610e" + "reference": "ccf895f6f3ed9430f53ae1ce34566e9bb6c58446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e", - "reference": "cc686552948d627528c0e2e759186dff67c2610e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ccf895f6f3ed9430f53ae1ce34566e9bb6c58446", + "reference": "ccf895f6f3ed9430f53ae1ce34566e9bb6c58446", "shasum": "" }, "require": { @@ -8100,44 +9017,45 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.13.0", + "version": "v1.21.1", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "c4388410e2fb6321e77c5dd6e3cb2dba821f9fe6" + "reference": "da629093c7bf9abd9a6a0f232a43bbb1b88de68d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c4388410e2fb6321e77c5dd6e3cb2dba821f9fe6", - "reference": "c4388410e2fb6321e77c5dd6e3cb2dba821f9fe6", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/da629093c7bf9abd9a6a0f232a43bbb1b88de68d", + "reference": "da629093c7bf9abd9a6a0f232a43bbb1b88de68d", "shasum": "" }, "require": { "doctrine/inflector": "^1.2", "nikic/php-parser": "^4.0", - "php": "^7.0.8", - "symfony/config": "^3.4|^4.0", - "symfony/console": "^3.4|^4.0", - "symfony/dependency-injection": "^3.4|^4.0", - "symfony/filesystem": "^3.4|^4.0", - "symfony/finder": "^3.4|^4.0", - "symfony/framework-bundle": "^3.4|^4.0", - "symfony/http-kernel": "^3.4|^4.0" + "php": "^7.1.3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/framework-bundle": "^3.4|^4.0|^5.0", + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "require-dev": { - "doctrine/doctrine-bundle": "^1.8", + "composer/semver": "^3.0@dev", + "doctrine/doctrine-bundle": "^1.8|^2.0", "doctrine/orm": "^2.3", "friendsofphp/php-cs-fixer": "^2.8", "friendsoftwig/twigcs": "^3.1.2", - "symfony/http-client": "^4.3", - "symfony/phpunit-bridge": "^3.4.19|^4.0", - "symfony/process": "^3.4|^4.0", - "symfony/security-core": "^3.4|^4.0", - "symfony/yaml": "^3.4|^4.0" + "symfony/http-client": "^4.3|^5.0", + "symfony/phpunit-bridge": "^4.3|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/security-core": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "type": "symfony-bundle", "extra": { @@ -8168,20 +9086,34 @@ "scaffold", "scaffolding" ], - "time": "2019-08-18T17:34:03+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-08-29T18:05:46+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "8a491edacd54e0214f5e7fb254710d30682e7bc1" + "reference": "8c6a51c55add464d9e6ef7000f1877e4c75f9fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/8a491edacd54e0214f5e7fb254710d30682e7bc1", - "reference": "8a491edacd54e0214f5e7fb254710d30682e7bc1", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/8c6a51c55add464d9e6ef7000f1877e4c75f9fbf", + "reference": "8c6a51c55add464d9e6ef7000f1877e4c75f9fbf", "shasum": "" }, "require": { @@ -8234,34 +9166,34 @@ ], "description": "Symfony Monolog Bridge", "homepage": "https://symfony.com", - "time": "2019-08-07T11:52:19+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/monolog-bundle", - "version": "v3.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "7fbecb371c1c614642c93c6b2cbcdf723ae8809d" + "reference": "e495f5c7e4e672ffef4357d4a4d85f010802f940" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/7fbecb371c1c614642c93c6b2cbcdf723ae8809d", - "reference": "7fbecb371c1c614642c93c6b2cbcdf723ae8809d", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/e495f5c7e4e672ffef4357d4a4d85f010802f940", + "reference": "e495f5c7e4e672ffef4357d4a4d85f010802f940", "shasum": "" }, "require": { - "monolog/monolog": "~1.22", + "monolog/monolog": "~1.22 || ~2.0", "php": ">=5.6", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4.10|^4.0.10", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/monolog-bridge": "~3.4|~4.0" + "symfony/config": "~3.4 || ~4.0 || ^5.0", + "symfony/dependency-injection": "~3.4.10 || ^4.0.10 || ^5.0", + "symfony/http-kernel": "~3.4 || ~4.0 || ^5.0", + "symfony/monolog-bridge": "~3.4 || ~4.0 || ^5.0" }, "require-dev": { - "symfony/console": "~3.4|~4.0", - "symfony/phpunit-bridge": "^3.4.19|^4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/console": "~3.4 || ~4.0 || ^5.0", + "symfony/phpunit-bridge": "^4.4 || ^5.0", + "symfony/yaml": "~3.4 || ~4.0 || ^5.0" }, "type": "symfony-bundle", "extra": { @@ -8282,13 +9214,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" } ], "description": "Symfony MonologBundle", @@ -8297,20 +9229,34 @@ "log", "logging" ], - "time": "2019-06-20T12:18:19+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-06T15:12:11+00:00" }, { "name": "symfony/process", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a" + "reference": "61ab103012c3072fb340447a34598714ba74ba6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e89969c00d762349f078db1128506f7f3dcc0d4a", - "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a", + "url": "https://api.github.com/repos/symfony/process/zipball/61ab103012c3072fb340447a34598714ba74ba6f", + "reference": "61ab103012c3072fb340447a34598714ba74ba6f", "shasum": "" }, "require": { @@ -8346,20 +9292,111 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-09T09:39:05+00:00" + }, + { + "name": "symfony/profiler-pack", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/profiler-pack.git", + "reference": "29ec66471082b4eb068db11eb4f0a48c277653f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/profiler-pack/zipball/29ec66471082b4eb068db11eb4f0a48c277653f7", + "reference": "29ec66471082b4eb068db11eb4f0a48c277653f7", + "shasum": "" + }, + "require": { + "symfony/stopwatch": "*", + "symfony/twig-bundle": "*", + "symfony/web-profiler-bundle": "*" + }, + "type": "symfony-pack", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A pack for the Symfony web profiler", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-08-12T06:50:46+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.3.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "4aff3715c98706ee25bdb4aced6591a9dffc3d9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4aff3715c98706ee25bdb4aced6591a9dffc3d9b", + "reference": "4aff3715c98706ee25bdb4aced6591a9dffc3d9b", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/service-contracts": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/translation", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "28498169dd334095fa981827992f3a24d50fed0f" + "reference": "46e462be71935ae15eab531e4d491d801857f24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/28498169dd334095fa981827992f3a24d50fed0f", - "reference": "28498169dd334095fa981827992f3a24d50fed0f", + "url": "https://api.github.com/repos/symfony/translation/zipball/46e462be71935ae15eab531e4d491d801857f24c", + "reference": "46e462be71935ae15eab531e4d491d801857f24c", "shasum": "" }, "require": { @@ -8422,20 +9459,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6" + "reference": "75669a7e681cbd4a291a08ed31d2d3998fe86e48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/641043e0f3e615990a0f29479f9c117e8a6698c6", - "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/75669a7e681cbd4a291a08ed31d2d3998fe86e48", + "reference": "75669a7e681cbd4a291a08ed31d2d3998fe86e48", "shasum": "" }, "require": { @@ -8498,20 +9535,88 @@ "debug", "dump" ], - "time": "2019-08-26T08:26:39+00:00" + "time": "2020-01-25T12:32:28+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v4.3.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8", + "reference": "ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/config": "^4.2", + "symfony/http-kernel": "^4.3", + "symfony/routing": "^4.3", + "symfony/twig-bundle": "~4.2", + "symfony/var-dumper": "^4.3", + "twig/twig": "^1.41|^2.10" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/form": "<4.3", + "symfony/messenger": "<4.2", + "symfony/var-dumper": "<3.4" + }, + "require-dev": { + "symfony/browser-kit": "^4.3", + "symfony/console": "^4.3", + "symfony/css-selector": "~3.4|~4.0", + "symfony/framework-bundle": "^4.3", + "symfony/stopwatch": "~3.4|~4.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\WebProfilerBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony WebProfilerBundle", + "homepage": "https://symfony.com", + "time": "2020-01-09T12:29:25+00:00" }, { "name": "symfony/web-server-bundle", - "version": "v4.3.4", + "version": "v4.3.11", "source": { "type": "git", "url": "https://github.com/symfony/web-server-bundle.git", - "reference": "dc26b980900ddf3e9feade14e5b21c029e8ca92f" + "reference": "2338445b78f1fb212a96f4286abdc77ee1e92607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-server-bundle/zipball/dc26b980900ddf3e9feade14e5b21c029e8ca92f", - "reference": "dc26b980900ddf3e9feade14e5b21c029e8ca92f", + "url": "https://api.github.com/repos/symfony/web-server-bundle/zipball/2338445b78f1fb212a96f4286abdc77ee1e92607", + "reference": "2338445b78f1fb212a96f4286abdc77ee1e92607", "shasum": "" }, "require": { @@ -8557,27 +9662,27 @@ ], "description": "Symfony WebServerBundle", "homepage": "https://symfony.com", - "time": "2019-08-20T14:27:59+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.3", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + "reference": "75a63c33a8577608444246075ea0af0d052e452a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -8597,20 +9702,26 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" }, { "name": "zircote/swagger-php", - "version": "2.0.14", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "f2a00f26796e5cd08fd812275ba2db3d1e807663" + "reference": "a25c1bfe508e5f27d5f618648449593a79cbe406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/f2a00f26796e5cd08fd812275ba2db3d1e807663", - "reference": "f2a00f26796e5cd08fd812275ba2db3d1e807663", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/a25c1bfe508e5f27d5f618648449593a79cbe406", + "reference": "a25c1bfe508e5f27d5f618648449593a79cbe406", "shasum": "" }, "require": { @@ -8659,7 +9770,7 @@ "rest", "service discovery" ], - "time": "2019-05-17T10:10:34+00:00" + "time": "2020-05-10T13:42:24+00:00" } ], "aliases": [], @@ -8680,5 +9791,6 @@ "ext-sqlite3": "*", "ext-xml": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/config/bundles.php b/config/bundles.php index 8bdb00d57cd1fb0bddff702deba4d101ca42e352..5eadcbeb4fb9a4a17c8012ddda7ef7afd572ce25 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,6 +15,8 @@ return [ Liip\FunctionalTestBundle\LiipFunctionalTestBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true], - MsgPhp\UserBundle\MsgPhpUserBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], + Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], ]; diff --git a/config/jwt/private.pem.dist b/config/jwt/private.pem.dist new file mode 100644 index 0000000000000000000000000000000000000000..cdd4231f5b73184986911c579e9dfc4dc8278fcb --- /dev/null +++ b/config/jwt/private.pem.dist @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI6XJ/TxaNdSwCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDDn5ntkLCgDgeMGr7N9F+WBIIJ +UNo8Q2/+Bs13Ulms3rO7lJr9hRW0pFAD0GgCxIgiQ6PURQjQSPEbxcIEsZBTQUcJ +fpUHHtou+uYSbdbQFQ3FCdX4X3TvOqGixr6eEsZnokByPeOBG0MFYhyQ1HtMWSQT +5h+v3+5E1uvvB6oXMtFJlciwzQ00lVZA5ieRoCL74pZMeY1HVXKi4MN+iAmnHyGN +5+Ky20mOxxyLduPjPZSUH5nzZ2y7gl1azypiyiPa2xEQLe+4gTpFt8DAikJ2e09j +IYccWgvvaR+w60LrZDx8xfHTeNiWay9+1c2zFJ/GAaQ0zUO+/aY1jAxXgdyjxo7J +e6Pd9Trle4JmlN5ciRv1jUUdSi6qXPObIDtppCz1o6gRfMKm+2c/gRoFj0iU9G2O +fDE5u9yOVthwNl4Lrl4E0uEBxe9CXp1uNlzz0c/zb2vsrvRE8k/FHB2LxAQ4+Hth +BusehgZDkY1smrOCXFuGjoUT55pX+gHULyRgSSzK4KzMid+kFW/uQ4H4iAm+362b +VURiinx2K8r5+71WTRG460+hq0MX+Cf8kr0JeOz/RtDo/ySJPPhHIK6ITSeqqMCd +9aNKh/ZT35JzstHYLT4/asIHrGysLFa+Y7TZUlPN6oL9Npg3h4q2U2BpYhBiUan4 +SiwJuhBA+TlbDcwtnXyCVpBjeAd0/5uiohMOShZ0X6GJNMdpoh0P07tgZwmzHiWE +MRvbMyKU54gmaggtoPyMmSaSD6m8X4pt5qOZJPeNgv4HNR4uoSGv2r6891q03oc0 +wsG2feWpjEIx7OoXqtCroZ+qav+mOh5xn++HTFgxrgk2101F+bbcfU2lRBch4f5j +T8Pc5DkuwaHN4R3dOMBXfjxAhH7JxCJasGPfNTMXzd6nQUqiUWLrrsDV05uEC+1E +8DRzECtebpVP5ARpZPOLNlx8CFw3ottXGyOcATPIRU8PBoagMlpsKIrjSZdaN8cu +ZzQ8YN0m9LIuKQVesF6P3bxLi9itK4oWUElp/tej6RFwVuj5oDzqtCsfvdc15J0U +5IvV32GPyQ9n+6yKN+ydlaNwNihPLPe5lKFPiBwptoK1Sa5B0eEligqI8YG8id4t +kNjjME/CIBhvtPwvZbmnfVq77AtgQCAqfVnfbFZNgbMFiWrmg3VZH+VFSJLHTd9j +XMMlb0rkmP2s4MxRVWCxb4fUnO3jT3SXSc2Y7tllVbdjbJaPwqMechMznMMc5Jsl +7SQznWhxput5hYY7w8401gZKEQhkk7KQ51mCB02Z7rUnpNUfRZ+4wcPF6gIhS9it +R0TxkhZpMhwXUcHI5n75mut8vxbwkisk0it4UB76flsFqgqo5v0zE2YxiaBWDo+V +0ziTUi5nixc1tDtnzDcigHpp+RMPPIBvsVc2Gmgje6SbUQzfn3O/OhURcalRi+4x +cXyTYQzD9ZkI/GDfu3dG2JRJWJJKZn2GCRz90j0OW9mGOdQ1rsMFRRS8+AFZjHeu +tUpTea12bQmTzCa4qvauUMEaGtWFrgfcjBp4bqcw4gDxHSTA3Yc/8hvx4Go7rD17 +IWo9rNmu6VROFc5C0RjKmdrioQ3ZfEBSukOek/15+cBRGxKaudy8zrSl0UIKZVuQ +s7PAyr+efE6YTP/gLcUrilR3kJ9E5fgOFo7aqDxZ1Du68ugWxz87sORh4+6MgKre +FlLoztvPWJ8BR4GWXpXddZHtpvabeSX5G2xx1WMADipuu3dzjE9CedL11+UqHHjk +PvrgfDzh95DotpM8CM6MBgpqgTzhHRsvI3rdnDj7w+JWq0NbPcXwBJVz0zXrrjuZ +JNa04RPDUDhOvaAX0y0k7Nipi0Qfl2tAtmpPmF53n55kMrDDlzaYQ2t+Nxdo6292 +T87vgp2zq/bB4WC1i0/hXDzOU/ZFPSwAfMcNK0aohItXSs3LphWpwRl2mi4k+rI2 +MaMmsRYxvxWkRVQY6UA3FIrXKBLS5sEsME3CL/FYsioJYIplfFFVU2baYqhra+if +F9Z2hVVxEsdCxJmvVqeemhiQ4/eaXp8vHKj+VmwNkR7iV21AubF8uSZ20S03o0Lo +UnwYoQ7EeK7PWqx+qQ8dFig5eUvvajdA0x12wz4263I2H0L038RHQGstqf1kOfam +aBPHwdJurG54NMzDj7ztJi1ja0kPN6LO9Rr9V5itCxmK2MzNFvpVyQb2EcmciMKY +4wE/N2YSnm/4KSqClPrMnVd2d0bnehYI+g5B7EVUER4CBmGN1vJlU9Tjm4qh/GYy +he9jqwiXF59lXx7eSffzQsH5JyYT7+E141fUnfQexTA84l5GXwKF/XjsFLSmKHsV +UljbZeS9zdXiLtLDjCraLtp6RUXXYi12zhFF1Mgs6owRi7z5OMFNGqSvXZGxhf3S +nLMDZN3zXH+Vz6AdJzw7Eu6OB3bsXHxd/9/+8rN0uBQnPU8vGNp/eKv5sI6Qv+m9 +Oz4CpZMvhpfSyR+yEgBsA9/CGuR9PzDBv+RJEXVb4U/Xun3Hnh9m2kCBwpqww3ft +58pCY88I5s1DNgqkHCb0cAHEXBCR0X8sZhUyjZ6K1xTkmfHGhH0eo99u/lRi5roL +FWzz5Fc4MDj+hfpwKqMFepUEg0ehgdGzJn/ZUTNRlSAXaZzTAjYzASxcCCInUIsC +Cdx2Q2yktuAEQIQpktDlbQsnYzQ/ng+LhCJ8WFziYBDWrqBJhzL52Av7pM96INrF +75PPmUwaXScXRzYQVWbQ5DiOSSnivU/C+IBLwwinjAV5UPGXaagha/RAn9Edf4/6 +Xwaplbd0C6gTP68jLVo9gd0xDnZwqj5CXM+rMwrBtwLI3EZ6kEYU3cQPuR8+AlUa +5zeHHNojUfT4zCIqN1zl7C+m6u98w29LzCcYVxr8omV/upkaq4yZNr3KCatNSw3A +e2xXL2vAqsMdbU7bFH/QsoldTrTr8h8M2JYWTL0xw/D4vDz6mJe6ygm6fMf/g+PM +Fx7zUvHyCybjLMPjjnViGLvSAZOPTm3uu3n0ECJ4vTYrV0SQmsSWaCV6jtNXkyV2 +GfmetgcXm20Fzm9XvwMbZnVROcfs7mfDDY91zDM/ZRzan5gPmaTLuYWT3+WslzRd +ZXJnnT0OWDb5uO0w4+l8CzVw7frk1gnEuO9nkfN0nTwUhCCUq0SeUQayLKU1pMuM +1IDn2UzN5/fa86isWBOTe2Zd1SsdT6gABlqym8/gXuw1 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/config/jwt/public.pem.dist b/config/jwt/public.pem.dist new file mode 100644 index 0000000000000000000000000000000000000000..abbe4d6c972388bc1c60ab8ec4734ff0b4f75560 --- /dev/null +++ b/config/jwt/public.pem.dist @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9GJWcboEGZSYsQZ5jsrG +jlDz5SfaOJJFD3uJGjD83vFvsqdIoNak7mQvgMBF1cYf3ayIpwdcE1go5P3sX1M2 +/SVnaiB9AE+qj+REeQ+LvRLal2bUctBa743+gVaEi3PJwWac6o5JawKGuerMH4nz +M2XFa/nH3+YYBqR7oSTGphPiTsoJOXDHq5pKR26T6CevJvdpN8LqIoUJSduXnjq8 +Ypi0KAztrGx4P7MhYWwOTojNY/iaEHcr5j8ePQmozrX5sQIUk8AR+9sMp4JSP9Jp ++vZ7tSIer4JdoaVHUlwGYTnbYXUIHufz6xJjNQPcBYSED73OgaUv7ZjjrSjLsezA +Fu0GeK3CkoYU6xEdbhP2P/XClGr6z8FyOF5y0BMv94/JxgHA1nX3nCuiyNCQ6eie +e3ymf1tOpNLtMSRf38x1PNfzQq8VpQvsrware+DVK9NEkfMwL2VK9CfGm6NZ1Wce +zlaiJCUxBtSItccCwYRe/JaXafOrS7Ev2GjeRzXfhMCwL6I0Kcs+73pz5raw5XCY +ov1mtl1TCEKBtKkfy8FQW7yoZrotoX8ik0EPyl4x2mQYUAV3CCmO8OLtYZefX1qG +ctt/l/zu6OFt5BH5vLY31kBAJ5VHJ9rBFDAsZyf/vtwO2EcREHFwhHdPtR9B3/d3 +zc4I0aUFfYKO4fK8DFzP5ZUCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 2175c98692cf91d40b34e3ad1294f65c633cd646..1581e6bf890494344e7f05c0abfec88dc426058f 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -21,3 +21,11 @@ api_platform: # The shenanigans mapping: paths: ['%kernel.project_dir%/src/Entity'] + + enable_profiler: true + + swagger: + api_keys: + apiKey: + name: Authorization + type: header \ No newline at end of file diff --git a/config/packages/dev/debug.yaml b/config/packages/dev/debug.yaml new file mode 100644 index 0000000000000000000000000000000000000000..26d4e53d2cd88a8d49c80fefe1b818da8e033f2e --- /dev/null +++ b/config/packages/dev/debug.yaml @@ -0,0 +1,4 @@ +debug: + # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. + # See the "server:dump" command to start a new server. + dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" diff --git a/config/packages/dev/web_profiler.yaml b/config/packages/dev/web_profiler.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e92166a7fdaecfc6420ac429ba185e48b3199926 --- /dev/null +++ b/config/packages/dev/web_profiler.yaml @@ -0,0 +1,6 @@ +web_profiler: + toolbar: true + intercept_redirects: false + +framework: + profiler: { only_exceptions: false } diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7fa4bc61494804afcb5b08ef5d4f223ab3158cc7..91cc3157f78ae085835f36d827ed7b1ab6d4749b 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -19,3 +19,6 @@ framework: # https://api-platform.com/docs/core/serialization/#configuration serializer: enable_annotations: true + + validation: + enable_annotations: true \ No newline at end of file diff --git a/config/packages/lexik_jwt_authentication.yaml b/config/packages/lexik_jwt_authentication.yaml new file mode 100644 index 0000000000000000000000000000000000000000..afbc83ab0d3a8663f8a5016330304ab7b77b9d38 --- /dev/null +++ b/config/packages/lexik_jwt_authentication.yaml @@ -0,0 +1,5 @@ +lexik_jwt_authentication: + secret_key: '%env(resolve:JWT_SECRET_KEY)%' + public_key: '%env(resolve:JWT_PUBLIC_KEY)%' + pass_phrase: '%env(JWT_PASSPHRASE)%' + user_identity_field: uuid diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 339c25a7a6e63ea980da7cf718caf07e160865f5..cbd8f254b10ba6d3213ddd668bb95c71aeae764b 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -20,6 +20,3 @@ framework: event_bus: default_middleware: allow_no_handlers -services: - msgphp.messenger.command_bus: '@command_bus' - msgphp.messenger.event_bus: '@event_bus' diff --git a/config/packages/msgphp_user.make.php b/config/packages/msgphp_user.make.php deleted file mode 100644 index cbd6984460fc84b7905dd57c4d2646be05239a24..0000000000000000000000000000000000000000 --- a/config/packages/msgphp_user.make.php +++ /dev/null @@ -1,36 +0,0 @@ -extension('msgphp_user', array ( - 'class_mapping' => - array ( - 'MsgPhp\\User\\Credential\\Credential' => 'MsgPhp\\User\\Credential\\EmailPassword', - 'MsgPhp\\User\\Role' => 'App\\Entity\\Role', - 'MsgPhp\\User\\UserRole' => 'App\\Entity\\UserRole', - ), - 'role_providers' => - array ( - 'default' => - array ( - 0 => 'ROLE_USER', - ), - 0 => 'MsgPhp\\User\\Role\\UserRoleProvider', - ), -)); - - $container->services() - ->defaults() - ->private() - ->autoconfigure() - ->autowire() - - ->set(App\Console\ClassContextElementFactory::class) - ->alias(MsgPhp\Domain\Infrastructure\Console\Context\ClassContextElementFactory::class, App\Console\ClassContextElementFactory::class) - ; -}; diff --git a/config/packages/msgphp_user.php b/config/packages/msgphp_user.php deleted file mode 100644 index 447709195db27cf52c97ccdc18e6ef31b44290d3..0000000000000000000000000000000000000000 --- a/config/packages/msgphp_user.php +++ /dev/null @@ -1,12 +0,0 @@ -extension('msgphp_user', [ - 'class_mapping' => [ - User::class => \App\Entity\User::class, - ], - ]); -}; diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 075a7e651f1e4499245121fe6c9445e2b24107a8..c9d543521259bb9521df575d7a2cff6710eddd29 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,13 +1,18 @@ security: # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: - in_memory: { memory: null } - msgphp_user: - id: MsgPhp\User\Infrastructure\Security\UserIdentityProvider + #in_memory: { memory: null } + app_user_provider: + entity: + class: App\Entity\User encoders: - App\Entity\User: auto - MsgPhp\User\Infrastructure\Security\UserIdentity: auto + App\Entity\User: + algorithm: 'argon2i' + # maximum memory (in KiB) that may be used to compute the Argon2 hash + memory_cost: 1024 + # number of times the Argon2 hash algorithm will be run + time_cost: 3 role_hierarchy: ROLE_ADMIN: ROLE_USER @@ -22,20 +27,31 @@ security: pattern: ^/api/docs?(?:[.](?:html|xml|json|ya?ml))?$ security: false - api_registration: - pattern: ^/api/users$ - methods: [POST] - security: false - - main: -# anonymous: true - pattern: ^/api - provider: msgphp_user + api: + pattern: ^/api/ stateless: true - security: true + anonymous: true + provider: app_user_provider + http_basic: + realm: "Authentication Desk" + guard: + authenticators: + - lexik_jwt_authentication.jwt_token_authenticator + json_login: + check_path: /api/_jwt + username_path: usernameOrEmail + password_path: password + success_handler: lexik_jwt_authentication.handler.authentication_success + failure_handler: lexik_jwt_authentication.handler.authentication_failure + main: + anonymous: true http_basic: - realm: "Limaju's Authentication Desk" + realm: "Authentication Desk" + + + + # activate different ways to authenticate # https://symfony.com/doc/current/security.html#firewalls-authentication diff --git a/config/packages/test/web_profiler.yaml b/config/packages/test/web_profiler.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a505b8589ce1f9b8fdd2a0aa2f4d651cc87c34c2 --- /dev/null +++ b/config/packages/test/web_profiler.yaml @@ -0,0 +1,8 @@ +web_profiler: + toolbar: false + intercept_redirects: false + +framework: + profiler: + dsn: 'file:%kernel.project_dir%/var/cache/dev/profiler' + collect: true diff --git a/config/routes/dev/web_profiler.yaml b/config/routes/dev/web_profiler.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c82beff2f61ecba4bc00aab90fcca4740e8078e1 --- /dev/null +++ b/config/routes/dev/web_profiler.yaml @@ -0,0 +1,7 @@ +web_profiler_wdt: + resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + prefix: /_wdt + +web_profiler_profiler: + resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + prefix: /_profiler diff --git a/config/routes/jwt.yaml b/config/routes/jwt.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7b67115b58570fdb0a4fd71d85f8eef7e4c648ec --- /dev/null +++ b/config/routes/jwt.yaml @@ -0,0 +1,3 @@ +authentication_token: + path: /api/_jwt + methods: ['POST'] \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 82cf303054b343e3d4ce264dff5d9dfbd10ae4ab..da4db5a5bb93dd4800f285348b06b9b04b97b9ca 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -38,14 +38,6 @@ services: resource: '../src/Controller' tags: ['controller.service_arguments'] - # Override to make public, only because of the test-suite. Remove this if you can. - MsgPhp\User\Infrastructure\Doctrine\Repository\UserRepository: - public: true - arguments: - - "App\\Entity\\User" - - "@doctrine.orm.default_entity_manager" - - "credential.email" - # Who doesn't need Twig in translations strings? App\Translator\TwiggyTranslator: decorates: translator @@ -60,3 +52,14 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + + App\Swagger\JwtDecorator: + decorates: 'api_platform.swagger.normalizer.documentation' + arguments: ['@App\Swagger\JwtDecorator.inner'] + autoconfigure: false + + app.event.jwt_created_listener: + class: App\EventListener\JWTCreatedListener + arguments: [ '@request_stack' ] + tags: + - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated } \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000000000000000000000000000000000000..a78afffc279b5cd8c83ac0f9dbb5f5bcf3dd83b9 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,22 @@ +version: "3.4" + +services: + php: + build: + context: . + target: symfony_php_dev + environment: + # See https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host + # See https://github.com/docker/for-linux/issues/264 + # The `remote_host` below may optionally be replaced with `remote_connect_back` + XDEBUG_CONFIG: >- + remote_enable=1 + remote_autostart = 1 + remote_host=172.17.0.1 + remote_connect_back = 0 + remote_port=9001 + remote_log="/tmp/xdebug_remote.log + idekey=VSCODE + # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers` + # Then PHPStorm will use the corresponding path mappings + #PHP_IDE_CONFIG: serverName=symfony diff --git a/docker-compose.yml b/docker-compose.yml index 67d215cd1ae52788c215d8bd9519581a6e6103a8..6f9fb9b521b077b45333a116d4c3f715c86daa39 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,8 @@ services: # - /srv/app/var/sessions/ environment: - SYMFONY_VERSION + depends_on: + - db nginx: build: @@ -48,4 +50,19 @@ services: - ./docker/h2-proxy/default.conf:/etc/nginx/conf.d/default.conf:ro ports: - "443:443" + + db: + image: mariadb:10.5.5 + environment: + - MYSQL_DATABASE=limaju + # You should definitely change the password in production + - MYSQL_PASSWORD=limaju + - MYSQL_RANDOM_ROOT_PASSWORD=true + - MYSQL_USER=limaju + volumes: + - ./var/db/data:/var/lib/mysql:rw + ports: + - 3308 + + \ No newline at end of file diff --git a/docs/suite-with-tmpfs.png b/docs/suite-with-tmpfs.png new file mode 100644 index 0000000000000000000000000000000000000000..2412cbe6004b5b4d9b101d67a0aa8986577d8bc3 Binary files /dev/null and b/docs/suite-with-tmpfs.png differ diff --git a/docs/suite-without-tmpfs.png b/docs/suite-without-tmpfs.png new file mode 100644 index 0000000000000000000000000000000000000000..2cc2e44b04e4aa4ce40bbd710a3d69fea1d47ee0 Binary files /dev/null and b/docs/suite-without-tmpfs.png differ diff --git a/features/bootstrap/ApiGraphFeatureContext.php b/features/bootstrap/ApiGraphFeatureContext.php deleted file mode 100644 index 04c52d05f7ba73856bee62e9bdf001754df50148..0000000000000000000000000000000000000000 --- a/features/bootstrap/ApiGraphFeatureContext.php +++ /dev/null @@ -1,211 +0,0 @@ -.+) soumets? l[ea] (?:travail|proposition) suivante? *:$/u - * @When /^(?P.+) submits? the following work *:$/ - */ - public function actorSubmitsTheFollowingWork($actor, $pystring) - { - $work = $this->yaml($pystring); - - $query = <<<'QUERY' -mutation workWithTitleOnly($title: String!) { - createWork(work: {title: $title}) { - title - } -} -QUERY; - - $variables = [ - 'work' => $work, // test - 'title' => $work['title'], - ]; - - $this->actor($actor)->gql($query, $variables); - } - - - /** - * @When /^(?P.+?)(?P *essa[iy]e de)? vot(?:e[szr]?|ons|ent)(?: finalement)? (?:(?PPOUR)|(?PCONTRE)) la proposition titrée "(?P.+)"$/ui - * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? votes? (?:(?P<for>FOR)|(?P<against>AGAINST)) the work titled "(?P<title>.+)"$/ - */ - public function actorVotesOnTheWorkTitled($actor, $try, $for, $against, $title) - { - $query = <<<'QUERY' -mutation voteForOrAgainst($workId: ID!, $vote: NewVote!) { - voteOnWork(workId: $workId, vote: $vote) { - title - createdAt - updatedAt - } -} -QUERY; - - $vote = null; - if ( ! empty($for)) $vote = 'AYE'; - if ( ! empty($against)) $vote = 'NAY'; - $vote = ['decision' => $vote]; - - $work = $this->findOneWorkFromTitle($title); - $workId = $work->getId(); - - $variables = [ - 'workId' => $workId, - 'vote' => $vote, - ]; - - $this->actor($actor)->gql($query, $variables, !empty($try)); - } - - - /** - * @When /^(?P<actor>.+?)(?P<try> *essa[iy]ez? de)? dél[éè]gu(?:e[szr]?|ons|ent)(?: finalement)? ses votes à (?P<delegate>.+)$/ui - * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? delegate? (?:my|her|his|their) votes to (?P<delegate>.+)$/ui - */ - public function actorDelegatesToCitizen($actor, $try, $delegate) - { - $subordinate = $this->user($delegate); - - $query = <<<'QUERY' -mutation delegateToCitizen($subordinateId: String!) { - delegateToCitizen(subordinateId: $subordinateId) { - elector { id } - subordinate { id } - } -} -QUERY; - - $variables = [ 'subordinateId' => $subordinate->getId() ]; - - $this->actor($actor)->gql($query, $variables, !empty($try)); - } - - - /** - * This step makes multiple HTTP queries, one per poll candidate. - * Use: - * Then <actor> prints the last <count> transactions - * - * @When /^(?P<actor>.+?)(?P<try> *essa[iy]ez? de)? vot(?:e[szr]?|ent) sur le scrutin au jugement majoritaire titré "(?P<title>.+)" *:$/ui - * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? votes? on the majority judgment poll titled "(?P<title>.+)" *:$/ui - */ - public function actorVotesOnTheLimajuPollTitled($actor, $try, $title, $pystring) - { - $data = $this->yaml($pystring); - - $poll = $this->findOneLimajuPollFromTitle($title); - $pollId = $poll->getId(); - - $opinions = []; - foreach ($data as $candidateTitle => $localizedMention) { - $pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll); - $mention = $this->unlocalizeLimajuPollMention($localizedMention); - $opinions[(string)$pollCandidate->getId()] = $mention; - - $query = <<<'QUERY' -mutation voteOnLimajuPoll($candidateIri: String!, $mention: String!) { - createLimajuPollCandidateVote(input: { candidate: $candidateIri, mention: $mention }) { - PollCandidateVote { id, author { id } } - } -} -QUERY; - $variables = array( - 'candidateIri' => $this->iri($pollCandidate), - 'mention' => $mention, - ); - $this->actor($actor)->gqlNew($query, $variables, !empty($try)); - } - - } - - - - /** - * This step is way too long. - * - we're doing multiple requests (help wanted with graphql denormalization context) - * - the YAML parsing business could be refactored, since we use it in REST as well. - * - * @When /^(?P<actor>.+?)(?P<try> *essa[iy]ez? de|) cré(?:e[szr]?|ent) (?:le|un) scrutin au jugement majoritaire (?:comme suit|suivant) *:$/u - * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to|) creates? the following majority judgment poll:$/ui - */ - public function actorSubmitsTheLimajuPollLikeSo($actor, $try, $pystring) - { - $data = $this->yaml($pystring); - - $candidates = []; - if (isset($data[$this->t('keys.poll.candidates')])) { - foreach ($data[$this->t('keys.poll.candidates')] as $candidateDatum) { - if (is_string($candidateDatum)) { - $candidateDatum = ['title' => $candidateDatum]; - } - $candidates[] = $candidateDatum; - } - } - - $query = <<<'QUERY' -mutation createLimajuPoll($input: createLimajuPollInput!) { - createLimajuPoll(input: $input) { - Poll { - id - title - } - } -} -QUERY; - - $variables = [ - 'input' => [ - 'title' => $data[$this->t('keys.poll.title')], -// 'candidates' => $candidates, - ], - ]; - $tx = $this->actor($actor)->gqlNew($query, $variables, !empty($try)); - - // Since we can't manage to post candidates as well in one single request, - // even though we configured the graphql denormalization just like REST. - // Maybe we're missing something… Time will tell. - // Anyhow, we're posting the candidates afterwards, and we use the returned poll's IRI. - $response = json_decode($tx->getResponse()->getContent()); - $pollIri = $response->data->createLimajuPoll->LimajuPoll->id; - - foreach ($candidates as $candidate) { - $query = <<<'QUERY' -mutation createLimajuPollCandidate($input: createLimajuPollCandidateInput!) { - createLimajuPollCandidate(input: $input) { - PollCandidate { - id - title - } - } -} -QUERY; - $variables = [ - 'input' => [ - 'title' => $candidate['title'], - 'poll' => $pollIri, - ], - ]; - $this->actor($actor)->gqlNew($query, $variables, !empty($try)); - } - - } - -} diff --git a/features/bootstrap/ApiRegistrationFeatureContext.php b/features/bootstrap/ApiRegistrationFeatureContext.php index b7fa9b48b28ba147922ad89c181f1a1afc741bd5..4324fbf2c6a5fb42a41a6b93c59568f0878a8c3e 100644 --- a/features/bootstrap/ApiRegistrationFeatureContext.php +++ b/features/bootstrap/ApiRegistrationFeatureContext.php @@ -2,6 +2,10 @@ /** + * + * NOT USED + * + * * Steps for registration, using the REST form of the API * because we can't manage to do registration via graphql * @@ -18,8 +22,7 @@ P<actor>.+) [mst]'inscri[st] en tant que citoyen(?:⋅?ne)? avec le pseudonyme " public function iRegisterPseudonimicallyAsNewCitizenWithAat($actor, $name, $aat) { print("🐵 Registering as ".$name."…"); - - $this->actor($actor, true)->apiOld('POST', '/user', ["name" => $name, "aat" => $aat]); + // todo? } @@ -30,8 +33,7 @@ P<actor>.+) [mst]'inscri[st] en tant que citoyen(?:⋅?ne)? avec le pseudonyme " public function iRegisterAnonymouslyAsNewCitizenWithAat($actor, $aat) { print("🤖 Registering anonymously…"); - - $this->actor($actor, true)->apiOld('POST', '/user', ["aat" => $aat]); + // todo? } } diff --git a/features/bootstrap/ApiRestFeatureContext.php b/features/bootstrap/ApiRestFeatureContext.php index 9363fda70abfdce69a6f12baeaf4046f913f3bc7..78f5ffc6a649dfa06575402b1155b889c276933e 100644 --- a/features/bootstrap/ApiRestFeatureContext.php +++ b/features/bootstrap/ApiRestFeatureContext.php @@ -41,24 +41,24 @@ class ApiRestFeatureContext extends BaseFeatureContext /** * This step makes multiple requests, one per given mention. * - * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) vot(?:e[szr]?|ent) sur le scrutin(?: au jugement majoritaire)? (?:titré|intitulé|assujettissant) "(?P<title>.+)" *:$/ui - * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? votes? on the majority judgment poll titled "(?P<title>.+)" *:$/ui + * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) vot(?:e[szr]?|ent) sur le scrutin(?: au jugement majoritaire)? (?:pour|de|titré|intitulé|assujetti(?:ssant)?) "(?P<pollSubject>.+)" *:$/ui + * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? votes? on the majority judgment poll titled "(?P<pollSubject>.+)" *:$/ui */ - public function actorVotesOnTheLimajuPollTitled($actor, $try, $title, $pystring) + public function actorVotesOnThePollOnTheSubjectOf($actor, $try, $pollSubject, $pystring) { - $poll = $this->findOneLimajuPollFromTitle($title); + $poll = $this->findOnePollFromSubject($pollSubject); $data = $this->yaml($pystring); - foreach ($data as $candidateTitle => $localizedMention) { - $pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll); - $mention = $this->unlocalizeLimajuPollMention($localizedMention); + foreach ($data as $proposalTitle => $gradeTitle) { + $proposal = $this->findOnePollProposalFromTitleAndPoll($proposalTitle, $poll); + $pollId = $poll->getUuid(); + $proposalId = $proposal->getUuid(); $this->actor($actor)->api( - 'POST',"/poll_candidate_votes", + 'POST',"/polls/{$pollId}/proposals/{$proposalId}/ballots", [ // the author is inferred from auth // 'author' => $this->iri($this->actor($actor)->getUser()), - 'candidate' => $this->iri($pollCandidate), - 'mention' => $mention, + 'grade' => $gradeTitle, ], [], !empty($try) ); } @@ -66,29 +66,48 @@ class ApiRestFeatureContext extends BaseFeatureContext /** - * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) cré(?:e[szr]?|ent) (?:le|un) scrutin au jugement majoritaire (?:comme suit|suivant) *:$/u + * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) cré(?:e[szr]?|ent) (?:le|un) scrutin(?: au jugement majoritaire)? (?:comme suit|suivant) *:?$/u * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to|) creates? the following majority judgment poll:$/ui */ - public function actorSubmitsTheLimajuPollLikeSo($actor, $try, $pystring) + public function actorCreatesThePollLikeSo($actor, $try, $pystring) { $data = $this->yaml($pystring); - $candidates = []; + $proposals = []; if (isset($data[$this->t('keys.poll.proposals')])) { foreach ($data[$this->t('keys.poll.proposals')] as $proposal) { if (is_string($proposal)) { $proposal = ['title' => $proposal]; } - $candidates[] = $proposal; + $proposals[] = $proposal; } } + $grades = []; + if (isset($data[$this->t('keys.poll.grades')])) { + foreach ($data[$this->t('keys.poll.grades')] as $k => $grade) { + if (is_string($grade)) { + $grade = [ + 'name' => $grade, + 'level' => $k, + ]; + } + $grades[] = $grade; + } + } + + $extraContent = []; + if (isset($data[$this->t('keys.poll.scope')])) { + $extraContent['scope'] = $this->t('values.scopes.' . $data[$this->t('keys.poll.scope')]); + } + $this->actor($actor)->api( 'POST',"/polls", [ 'subject' => $data[$this->t('keys.poll.subject')], - 'proposals' => $candidates, - ], [], !empty($try) + 'proposals' => $proposals, + 'grades' => $grades, + ] + $extraContent, [], !empty($try) ); } @@ -96,15 +115,15 @@ class ApiRestFeatureContext extends BaseFeatureContext public $that_tally; /** - * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) dépouill(?:e[szr]?|ent)(?: (?:de|à) nouveau)? le scrutin titré "(?P<title>.+)"$/ui + * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) dépouill(?:e[szr]?|ent)(?: (?:de|à) nouveau)? le scrutin (?:titré|intitulé|assujettissant) "(?P<title>.+)"$/ui * When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to)? tall(?:y|ies) the majority judgment poll titled "(?P<title>.+)"$/ui */ - public function actorTalliesTheLimajuPollTitled($actor, $try, $title) + public function actorTalliesThePollTitled($actor, $try, $title) { - $poll = $this->findOneLimajuPollFromTitle($title); + $poll = $this->findOnePollFromSubject($title); $tx = $this->actor($actor)->api( - 'GET',"/poll_tally/".$poll->getId(), + 'GET',"/polls/".$poll->getUuid()->toString()."/tally", [], [], !empty($try) ); @@ -117,17 +136,96 @@ class ApiRestFeatureContext extends BaseFeatureContext /** - * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) supprimer?(?: (?:de|à) nouveau)? le scrutin titré "(?P<title>.+)"$/ui + * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) supprimer?(?: (?:de|à) nouveau)? le scrutin (?:titré|intitulé|assujettissant) "(?P<title>.+)"$/ui * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to|) deletes? the majority judgment poll titled "(?P<title>.+)"$/ui */ - public function actorDeletesTheLimajuPollTitled($actor, $try, $title) + public function actorDeletesThePollTitled($actor, $try, $title) { - $poll = $this->findOneLimajuPollFromTitle($title); + $poll = $this->findOnePollFromSubject($title); $this->actor($actor)->api( - 'DELETE',"/polls/".$poll->getId(), + 'DELETE',"/polls/".$poll->getUuid()->toString(), [], [], !empty($try) ); } + + /** + * @When /^(?P<actor>.+?)(?P<try> (?:essa[iy]ez?|tente) de|) g[ée]n[éèe]r(?:e[szr]?|ent) (?P<invitationsAmount>.+?) invitations? pour le scrutin(?: au jugement majoritaire)? (?:pour|de|titré|intitulé|assujetti(?:ssant)?) "(?P<pollSubject>.+)"$/ui + * @When /^(?P<actor>.+?)(?P<try> tr(?:y|ies) to|) generate (?P<invitationsAmount>.+?) invitations? on the poll titled "(?P<pollSubject>.+)"$/ui + * @throws Exception + */ + public function actorGeneratesInvitationsForPollAbout($actor, $try, $invitationsAmount, $pollSubject) + { + $actor = $this->actor($actor); + $invitationsAmount = $this->number($invitationsAmount); + $poll = $this->findOnePollFromSubject($pollSubject); + $pollId = $poll->getUuid()->toString(); + + $actor->api( + 'GET',"/polls/{$pollId}/invitations", + [], [ + 'limit' => $invitationsAmount, + ], !empty($try) + ); + + $content = $actor->getLastTransaction()->getResponseJson(); + //dump($content); + //array:6 [ + // "@context" => "/api/contexts/Invitation" + // "@id" => "/api/invitations" + // "@type" => "hydra:Collection" + // "hydra:member" => array:2 [ + // 0 => array:4 [ + // "@id" => "/api/invitations/14c8e709-47a1-4451-ac2e-6f73f13d21c5" + // "@type" => "Invitation" + // "uuid" => "14c8e709-47a1-4451-ac2e-6f73f13d21c5" + // "poll" => "/api/polls/e1517447-9758-4a79-b723-9c4b993af521" + // ] + // 1 => array:4 [ + // "@id" => "/api/invitations/8442cb74-0337-4232-a5d7-5acf3a5086a9" + // "@type" => "Invitation" + // "uuid" => "8442cb74-0337-4232-a5d7-5acf3a5086a9" + // "poll" => "/api/polls/e1517447-9758-4a79-b723-9c4b993af521" + // ] + // ] + // "hydra:totalItems" => 2 + // "hydra:view" => array:2 [ + // "@id" => "/api/polls/e1517447-9758-4a79-b723-9c4b993af521/invitations?limit=10" + // "@type" => "hydra:PartialCollectionView" + // ] + //] + + foreach ($content['hydra:member'] as $invitation) { + $actor->addInvitation($invitation, $poll); + } + + $actor->printTransaction(); + } + + /** + * @When /^(?P<actor>.+?)(?P<try>| (?:essa[iy]ez?|tente) d['e]) ?accept(?:e[szr]?|ent) l'invitation N°? ?(?P<invitationIndex>.+?) de (?P<otherActor>.+?)$/ui + * When /^(?P<actor>.+?)(?P<try>| tr(?:y|ies) to) generate (?P<invitationsAmount>.+?) invitations? on the poll titled "(?P<pollSubject>.+)"$/ui + * @throws Exception + */ + public function actorAcceptsInvitationOfOtherActor($actor, $try, $invitationIndex, $otherActor) + { + $actor = $this->actor($actor); + $otherActor = $this->actor($otherActor); + $invitationIndex = $this->number($invitationIndex); + + $invitation = $otherActor->getInvitationByNumber($invitationIndex); + if (null === $invitation) { + $this->failTrans("invitation.not_found_by_number"); + } + $invitationId = $invitation['uuid']; + + $actor->api( + 'GET',"/invitations/{$invitationId}", + [], [], !empty($try) + ); + } + + + } diff --git a/features/bootstrap/BaseFeatureContext.php b/features/bootstrap/BaseFeatureContext.php index 9c65a5b3540c50fcef420d39c3aeac2c6bdc3a2e..0757adaa1dd5e256070a7f8662316b059e001ad6 100644 --- a/features/bootstrap/BaseFeatureContext.php +++ b/features/bootstrap/BaseFeatureContext.php @@ -3,27 +3,25 @@ use App\Application; use App\Entity\Poll; -use App\Entity\PollCandidate; +use App\Entity\Poll\Proposal; +use App\Entity\PollProposal; +use App\Entity\User; use App\Features\Actor; use App\Features\Actors; -use App\Repository\PollCandidateRepository; -use App\Repository\PollCandidateVoteRepository; +use App\Repository\PollProposalRepository; +use App\Repository\PollProposalBallotRepository; use App\Repository\PollRepository; +use App\Repository\UserRepository; use App\Tally\Bot\TallyBotInterface; use Behat\Behat\Context\Context; use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use Liip\FunctionalTestBundle\Test\WebTestCase; -use MsgPhp\User\Command\CreateUser; -use MsgPhp\User\Infrastructure\Doctrine\Repository\UserRepository; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Yaml\Yaml; -//use FOS\UserBundle\Model\UserManager; - - /** * This context class contains the definitions of the steps used by the demo * feature file. Learn how to get started with Behat and BDD on Behat's website. @@ -177,25 +175,25 @@ class BaseFeatureContext extends WebTestCase implements Context /** * @return PollRepository */ - protected function getLimajuPollRepository() + protected function getPollRepository() { return $this->get(PollRepository::class); } /** - * @return PollCandidateRepository + * @return PollProposalRepository */ - protected function getLimajuPollCandidateRepository() + protected function getLimajuPollProposalRepository() { - return $this->get(PollCandidateRepository::class); + return $this->get(PollProposalRepository::class); } /** - * @return PollCandidateVoteRepository + * @return PollProposalBallotRepository */ - protected function getLimajuPollCandidateVoteRepository() + protected function getLimajuPollProposalVoteRepository() { - return $this->get(PollCandidateVoteRepository::class); + return $this->get(PollProposalBallotRepository::class); } @@ -218,7 +216,7 @@ class BaseFeatureContext extends WebTestCase implements Context /// Reverse I18N /// - public function unlocalizeLimajuPollMention($localizedMention) + public function unlocalizePollMention($localizedMention) { return $this->t("value.majority_judgment_poll.mention.$localizedMention"); } @@ -375,40 +373,45 @@ class BaseFeatureContext extends WebTestCase implements Context } - protected function findOneLimajuPollFromTitle($title, $lenient = false) : ?Poll + protected function findOnePollFromSubject($subject, $lenient = false) : ?Poll { - $work = $this->getLimajuPollRepository()->findOneByTitle($title); + $work = $this->getPollRepository()->findOneBySubject($subject); if (( ! $lenient) && (null == $work)) { - $this->failTrans("no_majority_judgment_poll_found_for_title", ['title' => $title]); + $this->failTrans("no_majority_judgment_poll_found_for_title", ['title' => $subject]); } return $work; } - protected function findOneLimajuPollCandidateFromId($id, $lenient = false) : ?PollCandidate + protected function findOnePollProposalFromId($uuid, $lenient = false) : ?Proposal { - $pollCandidate = $this->getRepository(PollCandidate::class)->findOneById($id); - if (( ! $lenient) && (null == $pollCandidate)) { - $this->fail("No PollCandidate with Id `$id' could be found."); + $pollProposal = $this->getRepository(Proposal::class)->findOneByUuid($uuid); + if (( ! $lenient) && (null == $pollProposal)) { +// $this->fail("No Proposal with UUID `$uuid' could be found."); + $this->failTrans("no_poll_proposal_found_for_uuid", ['uuid' => $uuid]); } - return $pollCandidate; + return $pollProposal; } - protected function findOneLimajuPollCandidateFromTitleAndPoll($title, $poll, $lenient = false) : ?PollCandidate + protected function findOnePollProposalFromTitleAndPoll($title, $poll, $lenient = false) : ?Proposal { - /** @var PollCandidate $pollCandidate */ - $pollCandidate = $this->getRepository(PollCandidate::class)->findOneBy([ + /** @var Proposal $PollProposal + */ + $PollProposal = $this->getRepository(Proposal::class)->findOneBy([ 'title' => $title, 'poll' => $poll, ]); - if (( ! $lenient) && (null == $pollCandidate)) { - $this->failTrans("no_majority_judgment_poll_candidate_found_for_title", ['title' => $title]); + if (( ! $lenient) && (null == $PollProposal)) { + $this->failTrans("no_majority_judgment_poll_proposal_found_for_title", [ + 'title' => $title, + 'poll' => $poll, + ]); } - return $pollCandidate; + return $PollProposal; } @@ -442,30 +445,30 @@ class BaseFeatureContext extends WebTestCase implements Context //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - protected function createUser($name) : array + protected function createUser($name, $roles = []) : array { $identifier = uniqid("citizen-", true); - $email = "${identifier}@test.assemblee-liquide.fr"; - $pseudonym = $name; - $token = md5(uniqid()); // security is irrelevant, since those are test users - - $this->getMessageBus()->dispatch(new CreateUser([ -// 'id' => new ScalarUserId(), - 'name' => $name, - 'email' => $email, - 'password' => $token, - ])); - - $user = $this->getUserRepository()->findByUsername($email); - $encodedPassword = $this->get('security.password_encoder')->encodePassword($user, $token); - - $user->getCredential()(new \MsgPhp\User\Event\Domain\ChangeCredential([ - 'password' => $encodedPassword, - ])); + $email = "${identifier}@users.mieuxvoter.fr"; + + $password = md5(uniqid()); // security is irrelevant, since those are test users + + $user = new User(); + $user + ->setEmail($email) + ->setPlainPassword($password) + ->setUsername($name); + + if ( ! empty($roles)) { + foreach ($roles as $role) { + $user->addRole($role); + } + } - $this->getEntityManager()->flush(); + // We use App\DataProvider\UserDataProvider::persist() to ensure password encryption + $this->get("App\DataPersister\UserDataPersister")->persist($user); +// $this->getEntityManager()->persist($user); // no, no - return ['user' => $user, 'token' => $token]; + return ['user' => $user, 'token' => $password]; } diff --git a/features/bootstrap/LanguageAwareFeatureTrait.php b/features/bootstrap/LanguageAwareFeatureTrait.php index d289e5375b40e6b6d3af4af3a5a59814b15e8076..bb524168990dd8135eacc93ebeb25e830ae4ac6c 100644 --- a/features/bootstrap/LanguageAwareFeatureTrait.php +++ b/features/bootstrap/LanguageAwareFeatureTrait.php @@ -3,9 +3,8 @@ use App\Translator\TwiggyTranslator; use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\YamlFileLoader; - +use Symfony\Component\Translation\Translator; /** @@ -38,6 +37,7 @@ trait LanguageAwareFeatureTrait $this->language = $scope->getFeature()->getLanguage(); $this->translator = new Translator($this->language); $yamlLoader = new YamlFileLoader(); + $this->translator->addLoader('yml', $yamlLoader); $this->translator->addResource( 'yml', @@ -50,10 +50,6 @@ trait LanguageAwareFeatureTrait 'en' ); $this->translator->setFallbackLocales(['en']); - -// $this->twiggy_translator = new TwiggyTranslator([ -// 'messages', '', null -// ], $this->translator, $this->get('twig')); } /** diff --git a/features/bootstrap/MainFeatureContext.php b/features/bootstrap/MainFeatureContext.php index 4f8495ddd54982d035983c1a1f5c0d262d7d705f..a9a2127d8d9cf7342fdc506c3061fd26c9c3ee6b 100644 --- a/features/bootstrap/MainFeatureContext.php +++ b/features/bootstrap/MainFeatureContext.php @@ -7,9 +7,9 @@ use App\Entity\Poll; -use App\Entity\PollCandidate; +use App\Entity\Poll\Grade; +use App\Entity\Poll\Proposal; use App\Entity\User; -use MsgPhp\User\Command\AddUserRole; /** @@ -19,10 +19,19 @@ use MsgPhp\User\Command\AddUserRole; */ class MainFeatureContext extends BaseFeatureContext { + // _____ _ _______ _ ___ + // / ____(_) / /_ _| | | | \ \ + // | | __ ___ _____ _ __ | | | | _ __ | |_ ___ _ __ _ __ __ _| || | + // | | |_ | \ \ / / _ \ '_ \ | | | | | '_ \| __/ _ \ '__| '_ \ / _` | || | + // | |__| | |\ V / __/ | | | | | _| |_| | | | || __/ | | | | | (_| | || | + // \_____|_| \_/ \___|_| |_| | ||_____|_| |_|\__\___|_| |_| |_|\__,_|_|| | + // \_\ /_/ + /** - * @Given /^a citizen named (?P<name>.+)$/ui * @Given /^un(?:⋅?e)? (?:utilisat(?:eure?|rice)|élect(?:eure?|rice)|citoyen(?:⋅?ne)?)(?: .*)? (?:sur)?nommé(?:⋅?e)? (?P<name>.+)$/ui + * @Given /^a citizen named (?P<name>.+)$/ui + * @throws Exception */ public function givenCitizenNamed($name) { @@ -35,8 +44,9 @@ class MainFeatureContext extends BaseFeatureContext /** - * @Given /^(?P<actor>.+?) (?:am|is|are) (?:a|the) citizen named (?P<name>.+)$/u * @Given /^(?P<actor>.+?) (?:suis|est?) un(?:⋅?e)? citoyen(?:⋅?ne)? nommé(?:⋅?e)? (?P<name>.+)$/u + * @Given /^(?P<actor>.+?) (?:am|is|are) (?:a|the) citizen named (?P<name>.+)$/u + * @throws Exception */ public function givenActorIsCitizenNamed($actor, $name) { @@ -49,66 +59,80 @@ class MainFeatureContext extends BaseFeatureContext /** - * @Given /^a moderator named (?P<name>.+)$/ui * @Given /^un(?:⋅?e)? modérat(?:eur[⋅.]?e?|rice)(?: .*?)? (?:sur)?nommé(?:⋅?e)? (?P<name>.+)$/ui + * @Given /^a moderator named (?P<name>.+)$/ui + * @throws Exception */ public function givenModeratorNamed($name) { - $userAndToken = $this->createUser($name); + $userAndToken = $this->createUser($name, ['ROLE_ADMIN']); $actor = $this->actor($name, true); $actor->setUser($userAndToken['user']); $actor->setPassword($userAndToken['token']); - - $userId = $actor->getUser()->getId(); - $roleName = 'ROLE_ADMIN'; - $context = []; - $this->app()->getMessageBus()->dispatch(new AddUserRole($userId, $roleName, $context)); } /** - * @Given /^un scrutin au jugement majoritaire comme suit:$/u - * @Given /^a majority judgment poll like so:$/u + * @Given /^un scrutin(?: au jugement majoritaire)? comme suit:?$/ui + * @Given /^a(?: majority judgment)? poll like so:?$/ui */ - public function givenLimajuPoll($pystring) + public function givenPollLikeSo($pystring) { - $titleKey = $this->t('keys.poll.title'); - $candidatesKey = $this->t('keys.poll.candidates'); + $subjectKey = $this->t('keys.poll.subject'); + $proposalsKey = $this->t('keys.poll.proposals'); + $gradesKey = $this->t('keys.poll.grades'); $data = $this->yaml($pystring); $poll = new Poll(); - if ( ! isset($data[$titleKey])) { - $this->fail("Set poll title with '${titleKey}:'."); + if ( ! isset($data[$subjectKey])) { + $this->failTrans("poll_has_no_subject"); } - $poll->setTitle($data[$titleKey]); + $poll->setSubject($data[$subjectKey]); $this->persist($poll); - if ( ! isset($data[$candidatesKey])) { - $this->fail("At least one candidate is required in '${candidatesKey}:'."); + if ( ! isset($data[$proposalsKey])) { + $this->failTrans("poll_has_no_proposal", ['proposalsKey' => $proposalsKey]); + } + + foreach ($data[$proposalsKey] as $candidateTitle) { + $proposal = new Proposal(); + $proposal->setTitle($candidateTitle); + $poll->addProposal($proposal); + $this->persist($proposal); + } + + if ( ! isset($data[$gradesKey])) { + $this->failTrans("poll_has_no_grades", ['gradesKey' => $gradesKey]); } - foreach ($data[$candidatesKey] as $candidateTitle) { - $candidate = new PollCandidate(); - $candidate->setTitle($candidateTitle); - $poll->addCandidate($candidate); - $this->persist($candidate); + foreach ($data[$gradesKey] as $k => $gradeName) { + $grade = new Grade(); + $grade->setName($gradeName); + $grade->setLevel($k); + $poll->addGrade($grade); + $this->persist($grade); } $this->flush(); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// ASSERTIONS - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // _ _______ _ ___ + // /\ | | / /_ _| | | | \ \ + // / \ ___ ___ ___ _ __| |_ | | | | _ __ | |_ ___ _ __ _ __ __ _| || | + // / /\ \ / __/ __|/ _ \ '__| __| | | | | | '_ \| __/ _ \ '__| '_ \ / _` | || | + // / ____ \\__ \__ \ __/ | | |_ | | _| |_| | | | || __/ | | | | | (_| | || | + // /_/ \_\___/___/\___|_| \__| | ||_____|_| |_|\__\___|_| |_| |_|\__,_|_|| | + // \_\ /_/ + // /** - * @Then /^there should(?: now)?(?: still)?(?: only)? be (?P<thatMuch>.+) users? in the database$/u * @Then /^(?:qu')?il(?: ne)? d(?:oi|evrai)t(?: maintenant)? y avoir (?P<thatMuch>.+) utilisateur(?:⋅?e)?s? dans la base de données$/ui + * @Then /^there should(?: now)?(?: still)?(?: only)? be (?P<thatMuch>.+) users? in the database$/ui */ public function thereShouldBeSomeUsersInTheDatabase($thatMuch) { @@ -117,29 +141,59 @@ class MainFeatureContext extends BaseFeatureContext /** + * @Then /^(?:qu')?il(?: ne)? d(?:oi|evrai)t(?: maintenant)?(?: encore)?(?: toujours)? (?:n')?y avoir (?P<thatMuch>.+) scrutins?(?: au jugement majoritaire)? dans la base de données$/ui * @Then /^there should(?: now)?(?: still)?(?: only)? be (?P<thatMuch>.+) majority judgment polls? in the database$/ui - * @Then /^(?:qu')?il(?: ne)? d(?:oi|evrai)t(?: maintenant)?(?: encore)? y avoir (?P<thatMuch>.+) scrutins?(?: au jugement majoritaire)? dans la base de données$/ui */ - public function thereShouldBeSomeLimajuPollsInTheDatabase($thatMuch) + public function thereShouldBeSomePollsInTheDatabase($thatMuch) { $this->thereShouldBeExactlyThatMuchEntitiesInTheDatabase($thatMuch, Poll::class); } + /** + * fixme: en step + * @Then /^le scrutin(?: au jugement majoritaire)? intitulé "(?P<pollSubject>.+?)" d(?:oi|evrai)t(?: maintenant)?(?: encore)? avoir (?P<thatMuch>.+) propositions?$/ui + * @throws Exception + */ + public function thereShouldBeSomeProposalsInThePoll($thatMuch, $pollSubject) + { + $thatMuch = $this->number($thatMuch); + $poll = $this->findOnePollFromSubject($pollSubject); + $actual = count($poll->getProposals()); + + $this->assertEquals($thatMuch, $actual); + } + + + /** + * fixme: en step + * @Then /^le scrutin(?: au jugement majoritaire)? (?:intitulé|assujettissant) "(?P<pollSubject>.+?)" d(?:oi|evrai)t(?: maintenant)?(?: encore)? avoir (?P<thatMuch>.+) mentions?$/ui + * @throws Exception + */ + public function thereShouldBeSomeGradesInThePoll($thatMuch, $pollSubject) + { + $thatMuch = $this->number($thatMuch); + $poll = $this->findOnePollFromSubject($pollSubject); + $actual = count($poll->getGrades()); + + $this->assertEquals($thatMuch, $actual); + } + /** * fixme: en step * Then /^there should(?: now)?(?: still)?(?: only)? be (?P<thatMuch>.+) majority judgment polls? in the database$/ui - * @Then /^(?:que?' ?)?(?P<actor>.+?)(?: ne)? d(?:oi|evrai)t(?: maintenant)?(?: encore)? avoir (?P<thatMuch>.+) votes? sur le scrutin(?: au jugement majoritaire)? titré "(?P<title>.+?)"$/ui + * @Then /^(?:que?' ?)?(?P<actor>.+?)(?: ne)? d(?:oi|evrai)t(?: maintenant)?(?: encore)? avoir (?P<thatMuch>.+) votes? sur le scrutin(?: au jugement majoritaire)? (?:titré|intitulé|assujetti(?:ssant)?) "(?P<title>.+?)"$/ui + * @throws Exception */ - public function actorShouldHaveSomeLimajuPollCandidateVotesForPoll($actor, $thatMuch, $title) + public function actorShouldHaveSomePollProposalVotesForPoll($actor, $thatMuch, $title) { $actor = $this->actor($actor); $thatMuch = $this->number($thatMuch); - $poll = $this->findOneLimajuPollFromTitle($title); + $poll = $this->findOnePollFromSubject($title); // fixme: for poll - $votes = $this->getLimajuPollCandidateVoteRepository()->findBy([ + $votes = $this->getLimajuPollProposalVoteRepository()->findBy([ 'elector' => $actor->getUser()->getId(), // 'poll' => $poll, ]); @@ -156,67 +210,78 @@ class MainFeatureContext extends BaseFeatureContext //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // _ ________ _ ___ + // /\ | | / / ____| | | | \ \ + // / \ ___ ___ ___ _ __| |_ | || |__ __ _| |_ ___ _ __ _ __ __ _| || | + // / /\ \ / __/ __|/ _ \ '__| __| | || __| \ \/ / __/ _ \ '__| '_ \ / _` | || | + // / ____ \\__ \__ \ __/ | | |_ | || |____ > <| || __/ | | | | | (_| | || | + // /_/ \_\___/___/\___|_| \__| | ||______/_/\_\\__\___|_| |_| |_|\__,_|_|| | + // \_\ /_/ + // /** + * This step is too long and needs refactoring and simplification. + * * fixme: en step - * @Then /^le dépouillement(?: de)? (?P<tally>standard) du scrutin au jugement majoritaire titré "(?P<title>.*)" devrait être *:?$/u + * @Then /^le dépouillement(?: de)? (?P<tally>standard) du scrutin(?: au jugement majoritaire)? (?:titré|intitulé|assujetti(?:ssant)?) "(?P<pollSubject>.*)" devrait être *:?$/u */ - public function theTallyOfTheLimajuPollTitledShouldBeLikeYaml($tally, $title, $pystring) + public function theTallyOfThePollTitledShouldBeLikeYaml($tally, $pollSubject, $pystring) { $expectedRaw = $this->yaml($pystring); - $poll = $this->findOneLimajuPollFromTitle($title); + $poll = $this->findOnePollFromSubject($pollSubject); - $mentionAtom = 'mention'; - $positionAtom = 'position'; + $gradeAtom = $this->t('keys.proposal.grade'); + $rankAtom = $this->t('keys.proposal.rank'); $expected = []; - $pollcandidates = []; - foreach ($expectedRaw as $candidateTitle => $localizedMentionOrData) { - $pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll); - $pollCandidateId = $pollCandidate->getId()->toString(); - $pollcandidates[$pollCandidateId] = $pollCandidate; - - if ( ! is_array($localizedMentionOrData)) { - $localizedMentionOrData = [ - $mentionAtom => $localizedMentionOrData, + $pollProposals = []; + foreach ($expectedRaw as $proposalTitle => $gradeOrData) { + $pollProposal = $this->findOnePollProposalFromTitleAndPoll($proposalTitle, $poll); + $pollProposalId = $pollProposal->getUuid()->toString(); + $pollProposals[$pollProposalId] = $pollProposal; + + if ( ! is_array($gradeOrData)) { + $gradeOrData = [ + $gradeAtom => $gradeOrData, ]; } - $localizedMentionOrData[$mentionAtom] = $this->unlocalizeLimajuPollMention($localizedMentionOrData[$mentionAtom]); - $expected[$pollCandidateId] = $localizedMentionOrData; +// $gradeOrData[$gradeAtom] = $this->unlocalizePollMention($gradeOrData[$gradeAtom]); + $expected[$pollProposalId] = $gradeOrData; } $tallyBot = $this->getTallyBot($tally); - $actual = $tallyBot->tallyVotesOnLimajuPoll($poll); + $actual = $tallyBot->tallyVotesOnPoll($poll); $assertedSomething = false; $expectationsLeftToProcess = array_keys($expected); - foreach ($actual->candidates as $candidateTally) { + foreach ($actual->proposals as $proposalTally) { - $candidateId = $candidateTally->poll_candidate_id->toString(); + $proposalUuid = $proposalTally->poll_proposal_id->toString(); - if (isset($expected[$candidateId])) { + if (isset($expected[$proposalUuid])) { $assertedSomething = true; - $expectationsLeftToProcess = array_diff($expectationsLeftToProcess, [$candidateId]); - - $pollCandidate = $this->findOneLimajuPollCandidateFromId($candidateId); - if ($expected[$candidateId][$mentionAtom] !== $candidateTally->mention) { - dump("Actual candidate tally", $candidateTally); - $this->failTrans("candidate_tallies_dont_match", [ - 'expected_mention' => $this->t('majority_judgment_poll.mention.'.$expected[$candidateId][$mentionAtom]), - 'actual_mention' => $this->t('majority_judgment_poll.mention.'.$candidateTally->mention), - 'candidate' => $pollCandidate, + $expectationsLeftToProcess = array_diff($expectationsLeftToProcess, [$proposalUuid]); + + $pollProposal = $this->findOnePollProposalFromId($proposalUuid); + + if ($expected[$proposalUuid][$gradeAtom] !== $proposalTally->median_grade) { + //dump("Actual proposal tally", $proposalTally); + $this->failTrans("proposal_tallies_dont_match", [ + 'expected_grade' => $expected[$proposalUuid][$gradeAtom], + 'actual_grade' => $proposalTally->median_grade, + 'proposal' => $pollProposal, ]); } - if (isset($expected[$candidateId][$positionAtom])) { - if ($expected[$candidateId][$positionAtom] !== $candidateTally->position) { + if (isset($expected[$proposalUuid][$rankAtom])) { + if ($expected[$proposalUuid][$rankAtom] !== $proposalTally->rank) { dump("Actual poll tally", $actual); - $this->failTrans('candidate_position_mismatch', [ - 'expected_position' => $expected[$candidateId][$positionAtom], - 'actual_position' => $candidateTally->position, - 'candidate' => $pollCandidate, + $this->failTrans('proposal_rank_mismatch', [ + 'expected_rank' => $expected[$proposalUuid][$rankAtom], + 'actual_rank' => $proposalTally->rank, + 'proposal' => $pollProposal, ]); } } @@ -225,8 +290,8 @@ class MainFeatureContext extends BaseFeatureContext } if (0 < count($expectationsLeftToProcess)) { - $candidatesLeft = array_map(function($e) use ($pollcandidates) { - return $pollcandidates[$e]; + $candidatesLeft = array_map(function($e) use ($pollProposals) { + return $pollProposals[$e]; }, $expectationsLeftToProcess); $this->failTrans("candidates_left_unprocessed", [ 'expected' => $expected, @@ -239,21 +304,41 @@ class MainFeatureContext extends BaseFeatureContext } } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + * @Then /^(?P<actor>.+?)(?: ne)? devr(?:ai[st]|aient|ions)(?: encore| aussi)? avoir(?: qu[e'])? ?(?P<amount>.+?) invitations?$/iu + * @Then /^(?P<actor>.+?) should(?: now)? have (?P<amount>.+?) invitations?$/iu + * @throws Exception + */ + public function actorShouldHaveInvitations($actor, $amount) + { + $actor = $this->actor($actor); + $amount = $this->number($amount); + + $this->assertEquals($amount, $actor->countInvitations()); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** - * @Then /^(?P<actor>.+?) should (?:(?P<ok>succeed)|(?P<ko>fail))$/ * @Then /^(?P<actor>.+?) devr(?:ai[st]|aient|ions)(?: encore| aussi)? (?:(?P<ok>réussir)|(?P<ko>échouer))$/u + * @Then /^(?P<actor>.+?) should (?:(?P<ok>succeed)|(?P<ko>fail))$/ + * @throws Exception */ public function actorShouldSucceedOrFail($actor, $ok=null, $ko=null) { - $tx = $this->actor($actor)->getLastTransaction(); + $actor = $this->actor($actor); + $tx = $actor->getLastTransaction(); if (empty($ko)) { - $this->actor($actor)->assertTransactionSuccess($tx); + $actor->assertTransactionSuccess($tx); } else if (empty($ok)) { - $this->actor($actor)->assertTransactionFailure($tx); + $actor->assertTransactionFailure($tx); } else { $this->fail("Bad Regex?"); } diff --git a/features/bootstrap/bootstrap.php b/features/bootstrap/bootstrap.php index 9477c78ebb7b43fb99c2aa8fcaff66c7708533b0..019011c5cf7ef270bfb9a43ee8fce08c6649070a 100644 --- a/features/bootstrap/bootstrap.php +++ b/features/bootstrap/bootstrap.php @@ -1,6 +1,6 @@ <?php -print("Validating the algorithmic constitution of Limaju…\n"); +print("Validating the Algorithmic Constitution of a Liquid Majority Judgment Application…\n"); require 'functions.php'; diff --git a/features/gherkin/fr b/features/gherkin/fr index bf4bec96e9199f00db6528e582662a9a037008a9..4e62c51dcd141cfff51261e1166741ceb0cc82d9 160000 --- a/features/gherkin/fr +++ b/features/gherkin/fr @@ -1 +1 @@ -Subproject commit bf4bec96e9199f00db6528e582662a9a037008a9 +Subproject commit 4e62c51dcd141cfff51261e1166741ceb0cc82d9 diff --git a/src/Application.php b/src/Application.php index 780e65919c46c464dd4b21c65fa36a11b0d309da..a99adf6309e0c3f0e04dfa2ff2fb7d4604967aae 100644 --- a/src/Application.php +++ b/src/Application.php @@ -7,8 +7,6 @@ namespace App; use ApiPlatform\Core\Api\IriConverterInterface; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; -use MsgPhp\User\Infrastructure\Security\UserIdentity; -use MsgPhp\User\Repository\UserRepository; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Security\Core\Security; @@ -39,10 +37,10 @@ class Application */ protected $entityManager; - /** - * @var UserRepository - */ - protected $userRepository; +// /** +// * @var UserRepository +// */ + // protected $userRepository; /** * @var IriConverterInterface @@ -66,7 +64,7 @@ class Application public function __construct( Security $security, EntityManagerInterface $entityManager, - UserRepository $userRepository, + // UserRepository $userRepository, IriConverterInterface $iriConverter, MessageBusInterface $messageBus ) @@ -74,7 +72,7 @@ class Application $this->security = $security; $this->entityManager = $entityManager; $this->iriConverter = $iriConverter; - $this->userRepository = $userRepository; + // $this->userRepository = $userRepository; $this->messageBus = $messageBus; } @@ -86,13 +84,11 @@ class Application */ public function getAuthenticatedUser(): ?User { - /** @var UserIdentity $userIdentity */ + /** @var User|null $userIdentity */ $userIdentity = $this->security->getUser(); - - /** @var User|null $user */ - $user = $this->userRepository->findByUsername($userIdentity->getOriginUsername()); - - return $user; + return $userIdentity; +// $user = $this->userRepository->findByUsername($userIdentity->getOriginUsername()); +// return $user; } diff --git a/src/Console/ClassContextElementFactory.php b/src/Console/ClassContextElementFactory.php deleted file mode 100644 index 4435f27313bc3fb7030d9c29980d102dcba60080..0000000000000000000000000000000000000000 --- a/src/Console/ClassContextElementFactory.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Console; - -use App\Entity\User; -use MsgPhp\User\Credential\EmailPassword; -use MsgPhp\Domain\Infrastructure\Console\Context\ClassContextElementFactory as BaseClassContextElementFactory; -use MsgPhp\Domain\Infrastructure\Console\Context\ContextElement; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; - -final class ClassContextElementFactory implements BaseClassContextElementFactory -{ - private $passwordHashing; - - public function __construct(PasswordEncoderInterface $passwordHashing) - { - $this->passwordHashing = $passwordHashing; - } - - public function getElement(string $class, string $method, string $argument): ContextElement - { - $element = new ContextElement(ucfirst((string) preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1 \\2', '\\1 \\2'], $argument))); - - switch ($argument) { - case 'password': - if (User::class === $class || EmailPassword::class === $class) { - $element - ->hide() - ->generator(function (): string { - return bin2hex(random_bytes(8)); - }) - ->normalizer(function (string $value): string { - return $this->passwordHashing->encodePassword($value, null); - }) - ; - } - break; - case 'email': - $element->label = 'E-mail'; - break; - } - - return $element; - } -} diff --git a/src/Controller/AcceptInvitationController.php b/src/Controller/AcceptInvitationController.php new file mode 100644 index 0000000000000000000000000000000000000000..fe8b17a535a1a72e412c35264bc4dc427c47d829 --- /dev/null +++ b/src/Controller/AcceptInvitationController.php @@ -0,0 +1,54 @@ +<?php + + +namespace App\Controller; + + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Security\Core\Security; + + +/** + * See App\Entity\Poll\Invitation where this controller is declared and configured. + * + * Class AcceptInvitationController + * @package App\Controller + */ +class AcceptInvitationController +{ + use Is\EntityAware; + use Is\UserAware; + + public function __construct( + EntityManagerInterface $entityManager, + Security $security + ) { + $this->setEm($entityManager); + $this->setSecurity($security); + } + + /** + * @param Request $request + */ + public function __invoke(Request $request) + { + $invitationId = $request->get('id'); + $invitationsRepo = $this->getInvitationRepository(); + $invitation = $invitationsRepo->findOneByUuid($invitationId); + + if (null === $invitation) { + // TODO: Resilience + // Use TrustWorth service or something akin + // ->addSuspicion(new InvitationDiscoveryAbuse()) + throw new NotFoundHttpException(); + } + + $user = $this->getUser(); + $invitation->setParticipant($user); + $this->flush(); + + // we don't need to return anything? wait, what? + } +} diff --git a/src/Controller/CreateBallotController.php b/src/Controller/CreateBallotController.php new file mode 100644 index 0000000000000000000000000000000000000000..a2fb3c17d0353b274006382e0ad7c8cbedcf22d3 --- /dev/null +++ b/src/Controller/CreateBallotController.php @@ -0,0 +1,75 @@ +<?php + +namespace App\Controller; + +use App\Entity\Poll; +use App\Entity\Poll\Proposal; +use App\Entity\Poll\Proposal\Ballot; +use App\Handler\BallotHandler; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Security\Core\Security; + + +/** + * Exists to patch https://github.com/api-platform/docs/issues/504 + * Subresources should handle POST. Since they don't yet, we do this "by hand". + * + * See App\Entity\Poll\Proposal\Ballot where this controller is declared and configured. + * + * Class CreateBallotController + * @package App\Controller + */ +class CreateBallotController +{ + protected $ballotHandler; + + use Is\EntityAware; + use Is\UserAware; + + public function __construct( + BallotHandler $ballotandler, + EntityManagerInterface $entityManager, + Security $security + ) { + $this->ballotHandler = $ballotandler; + $this->setEm($entityManager); + $this->setSecurity($security); + } + + /** + * @param Ballot $data + * @param Request $request + * @return Ballot + */ + public function __invoke(Ballot $data, Request $request): Ballot + { + $pollId = $request->get("pollId"); + $proposalId = $request->get("proposalId"); + $poll = $this->getPollRepository()->findOneByUuid($pollId); + $proposal = $this->getProposalRepository()->findOneByUuid($proposalId); + $judge = $this->security->getUser(); + + // Handles setting poll and proposal since apiplatform does not + $ballot = $this->ballotHandler->handleVote($data, $judge, $proposal, $poll); + + // WiP – Another handler? This time for scope access checks? + if ($poll->getScope() === Poll::SCOPE_PRIVATE) { + // fixme: check invitations + // ideas: use a BallotVoter and access rules + // + $invitationsRepo = $this->getInvitationRepository(); + $invitation = $invitationsRepo->findInvitationForUserOnPoll($judge, $poll); + if (null == $invitation) { + throw new HttpException(Response::HTTP_FORBIDDEN); + } + + //throw new NotFoundHttpException("Nope"); + } + + return $ballot; + } +} diff --git a/src/Controller/GetOrCreateInvitationsController.php b/src/Controller/GetOrCreateInvitationsController.php new file mode 100644 index 0000000000000000000000000000000000000000..ab433e90a31037550b7b5cb254a79cc07dd527ce --- /dev/null +++ b/src/Controller/GetOrCreateInvitationsController.php @@ -0,0 +1,92 @@ +<?php + +namespace App\Controller; + +use App\Entity\Poll\Invitation; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Security; + + +/** + * Trying the "eager getter" pattern. Create invitations in the database, on-demand. + * We should (at least sometimes) not let clients POST invitations, + * since we also want mass-email-based invitations + * where even the organizer cannot access invitation tokens. (for added trustworthiness) + * + * See App\Entity\Poll\Invitation where this controller is declared and configured. + * + * Class GetOrCreateInvitationsController + * @package App\Controller + */ +class GetOrCreateInvitationsController +{ + use Is\EntityAware; + use Is\UserAware; + + public function __construct( + EntityManagerInterface $entityManager, + Security $security + ) { + $this->setEm($entityManager); + $this->setSecurity($security); + } + + /** + * @param Request $request + * @return array + */ + public function __invoke(Request $request): array + { + $pollId = $request->get("pollId"); + $invitationsRepo = $this->getInvitationRepository(); + $poll = $this->getPollRepository()->findOneByUuid($pollId); + + // I. Configure + $maximumLimit = 500; // ENV config? + $defaultLimit = 10; // idem + $limit = $request->get("limit", $defaultLimit); + $limit = clamp(0, $maximumLimit, $limit); + $defaultOffset = 0; + $offset = 0; // todo later, once there's a scenario for it + + $invitations = []; + + // II. Read existing Invitations + // We could order them by creation date instead of numerical id + $existingInvitations = $invitationsRepo->findBy(['poll'=>$poll], ['id' => 'ASC'], $limit, $offset); + $invitations = $invitations + $existingInvitations; + + $missingInvitationsAmount = $limit - count($invitations); + // III. Generate missing Invitations + if ($missingInvitationsAmount > 0) { + // Database Concern(s) + // ------------------- + // Let's limit the amount of invitations one can generate. + // Admins may ignore that limit. + // We might do things like allowing more invitations + // once a quota of existing invitations has been "consumed". + // Or allow overriding the quota from a user property or role. + // Game design, etc. + // Anyways, this need benchmarking. + // + // Security Concern(s) + // ------------------- + // We're generating invitations in fast sequence, + // and perhaps UUIDv4 pseudo-random could be reproduced to guess invitations. + // UUIDv4 relies on random_bytes(), if I'm reading this right. + // https://www.php.net/manual/en/function.random-bytes.php + // … "cryptographically secure". Perhaps not enough for us (sequence!). TBD + for ($i = 0; $i < $missingInvitationsAmount; $i++) { + $invitation = new Invitation(); + $invitation->setPoll($poll); + $this->em->persist($invitation); + $invitations[] = $invitation; + } + + $this->em->flush(); + } + + return $invitations; + } +} diff --git a/src/Controller/GetTallyController.php b/src/Controller/GetTallyController.php index 277a11245be682438e86f62627472fc7a68216e4..2df6cfcf7d01341cb0d707ef153c74de4f92b384 100644 --- a/src/Controller/GetTallyController.php +++ b/src/Controller/GetTallyController.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace App\Controller; -use App\Entity\PollTally; +use App\Entity\Poll\Tally; use App\Repository\PollRepository; use App\Tally\Bot\TallyBotInterface; use Doctrine\ORM\EntityManagerInterface; @@ -16,7 +16,12 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/api/poll_tally/{id}", name="api_poll_tally_get", methods={"GET"}) + * Old thing. + * How can we re-do this more in line with apiplatform? + * + * → DataProvider + * + * @Route("/api/polls/{id}/tally", name="api_poll_tally_get", methods={"GET"}) */ final class GetTallyController { @@ -43,13 +48,13 @@ final class GetTallyController EntityManagerInterface $em ): Response { - $poll = $pollRepository->find($id); + $poll = $pollRepository->findOneByUuid($id); - $tally = new PollTally(); + $tally = new Tally(); $tallyType = "standard"; $tallyBot = $this->getTallyBot($tallyType); - $tallyOutput = $tallyBot->tallyVotesOnLimajuPoll($poll); + $tallyOutput = $tallyBot->tallyVotesOnPoll($poll); $votesCount = $tallyOutput->countVotes(); diff --git a/src/Controller/Is/EntityAware.php b/src/Controller/Is/EntityAware.php new file mode 100644 index 0000000000000000000000000000000000000000..937231fa0b8052ee772e7acc5df78cb0283731de --- /dev/null +++ b/src/Controller/Is/EntityAware.php @@ -0,0 +1,48 @@ +<?php + + +namespace App\Controller\Is; + + +use App\Entity\Poll; +use App\Entity\Poll\Grade; +use App\Entity\Poll\Invitation; +use App\Entity\Poll\Proposal; +use App\Entity\Poll\Proposal\Ballot; +use App\Repository\PollGradeRepository; +use App\Repository\PollInvitationRepository; +use App\Repository\PollProposalBallotRepository; +use App\Repository\PollProposalRepository; +use App\Repository\PollRepository; + + +trait EntityAware { + + use EntityManagerAware; + + + public function getPollRepository() : PollRepository + { + return $this->getEm()->getRepository(Poll::class); + } + + public function getProposalRepository() : PollProposalRepository + { + return $this->getEm()->getRepository(Proposal::class); + } + + public function getGradeRepository() : PollGradeRepository + { + return $this->getEm()->getRepository(Grade::class); + } + + public function getBallotRepository() : PollProposalBallotRepository + { + return $this->getEm()->getRepository(Ballot::class); + } + + public function getInvitationRepository() : PollInvitationRepository + { + return $this->getEm()->getRepository(Invitation::class); + } +} \ No newline at end of file diff --git a/src/Controller/Is/EntityManagerAware.php b/src/Controller/Is/EntityManagerAware.php new file mode 100644 index 0000000000000000000000000000000000000000..25e6f321492961514a37efbc17541410434a1360 --- /dev/null +++ b/src/Controller/Is/EntityManagerAware.php @@ -0,0 +1,41 @@ +<?php + + +namespace App\Controller\Is; + + +use Doctrine\ORM\EntityManagerInterface; + + +trait EntityManagerAware { + + /** + * @var EntityManagerInterface + */ + protected $em; + + /** + * @return EntityManagerInterface + */ + public function getEm(): EntityManagerInterface + { + return $this->em; + } + + /** + * @param EntityManagerInterface $em + */ + public function setEm(EntityManagerInterface $em): void + { + $this->em = $em; + } + + /** + * Shorthand to flush the Entity Manager + */ + public function flush() + { + $this->em->flush(); + } + +} \ No newline at end of file diff --git a/src/Controller/Is/UserAware.php b/src/Controller/Is/UserAware.php new file mode 100644 index 0000000000000000000000000000000000000000..b0205fa4861f0da708dfe28790607807992bed3d --- /dev/null +++ b/src/Controller/Is/UserAware.php @@ -0,0 +1,45 @@ +<?php + + +namespace App\Controller\Is; + + +use App\Entity\User; +use Symfony\Component\Security\Core\Security; + + +trait UserAware { + /** + * @var Security + */ + protected $security; + + /** + * @param Security $security + */ + public function setSecurity(Security $security): void + { + $this->security = $security; + } + + /** + * @return Security + */ + public function getSecurity(): Security + { + return $this->security; + } + + /** + * ??? + * + * @return User + */ + public function getUser(): User + { + /** @var User $user */ + $user = $this->getSecurity()->getUser(); + + return $user; + } +} \ No newline at end of file diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php deleted file mode 100644 index 3bd9522bbb7f2c38e1a2eb77b60c249f4753f78b..0000000000000000000000000000000000000000 --- a/src/Controller/RegistrationController.php +++ /dev/null @@ -1,90 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Controller; - -use App\Form\RegistrationType; -use Doctrine\ORM\EntityManagerInterface; -use MsgPhp\User\Command\CreateUser; -use Symfony\Component\Form\FormFactoryInterface; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Routing\Annotation\Route; - -/** - * Register a new User. - * This documentation never appears anywhere, does it? - * See App\Entity\UserDocumentation - * - * @Route("/api/users", name="api_register", methods={"POST"}) - */ -final class RegistrationController -{ - public function __invoke( - Request $request, - FormFactoryInterface $formFactory, - MessageBusInterface $bus, - EntityManagerInterface $em - ): Response { - $data = []; - - $this->adaptRequest($request); - - $form = $formFactory->createNamed('', RegistrationType::class, $data, ['csrf_protection' => false]); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $data = $form->getData(); - $bus->dispatch(new CreateUser($data)); - - return new JsonResponse([ - 'message' => "api.registration.success", - 'data' => $data, // maybe instead output the user, with id? - ], JsonResponse::HTTP_CREATED); - // also provide ['Location' => $locationUrl] - } - - $error = "api.registration.failure.message"; - - if ( ! $form->isSubmitted()) { - $error .= "\n- "."api.registration.failure.missing_post_vars"; - } else { - foreach ($form->getErrors() as $formError) { - $error .= "\n"."- ".$formError->getCause(); // FIXME: I'm just as insecure about this as it is - } - } - - return new JsonResponse(['error' => $error], Response::HTTP_BAD_REQUEST); - } - - /** - * Since ApiPlatform generated doc recommends usage of the Content instead of POST vars, - * let's convert what's in the content to POST vars before feeding it to the form. - * Whe also handle here the password['plain'] array quirk. - * - * This mutates the $request. - * - * @param Request $request - */ - protected function adaptRequest(Request $request) - { - $content = $request->getContent(); - if ( ! empty($content)) { - try { - $content = json_decode($content, true, 3, JSON_BIGINT_AS_STRING&JSON_OBJECT_AS_ARRAY); - } - catch (\JsonException $e) { return; } - catch (\Exception $e) { return; } - - if (isset($content["email"])) { - $request->request->set('email', $content["email"]); - } - if (isset($content["password"])) { - $request->request->set('password', ['plain' => $content["password"]]); - } - } - } -} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index fece4758e9ddece2426e82a5b91089ad70b6041e..175fbcd080b531e70aa8686005c18c0940c17731 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -5,6 +5,15 @@ namespace App\DataFixtures; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\Persistence\ObjectManager; +/** + * This application needs no fixtures for the moment. + * This file allows us to leverage the database reset utilities of fixtures in the gherkin suite. + * + * We might add some preset mentions. Might not. Clients ought to do it? + * + * Class AppFixtures + * @package App\DataFixtures + */ class AppFixtures extends Fixture { public function load(ObjectManager $manager) diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php new file mode 100644 index 0000000000000000000000000000000000000000..ab6aeafd56ce077517c0f320e7c548a88e60e313 --- /dev/null +++ b/src/DataPersister/UserDataPersister.php @@ -0,0 +1,75 @@ +<?php +namespace App\DataPersister; + +use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; +use App\Entity\User; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; + +final class UserDataPersister implements ContextAwareDataPersisterInterface +{ + /** + * + * @var EntityManagerInterface + */ + private $em; + + /** + * + * @var UserPasswordEncoderInterface + */ + private $userPasswordEncoder; + + /** + * + * @param EntityManagerInterface $entityManager + * @param UserPasswordEncoderInterface $userPasswordEncoder + */ + public function __construct(EntityManagerInterface $entityManager, UserPasswordEncoderInterface $userPasswordEncoder) + { + $this->em = $entityManager; + $this->userPasswordEncoder = $userPasswordEncoder; + } + + /** + * + * + * @param $data + * @param array $context + * @return boolean + */ + public function supports($data, array $context = []): bool + { + return $data instanceof User; + } + + /** + * + * @param User $data + * @param array $context + * @return User + */ + public function persist($data, array $context = []) + { + if ($data->getPlainPassword()) { + + $data->setPassword( + $this->userPasswordEncoder->encodePassword($data, $data->getPlainPassword()) + ); + $data->eraseCredentials(); + } + $this->em->persist($data); + $this->em->flush(); + } + + /** + * @param User $data + * @param array $context + * @return void + */ + public function remove($data, array $context = []) + { + $this->em->remove($data); + $this->em->flush(); + } +} \ No newline at end of file diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php index 3cb803a98c52b8e510cdc99a482d801448733fc1..2e7bcfeab0fdd71a42fd396e5c13325d3ba517cf 100644 --- a/src/Entity/Poll.php +++ b/src/Entity/Poll.php @@ -3,26 +3,35 @@ namespace App\Entity; - +use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; use ApiPlatform\Core\Annotation\ApiSubresource; +use App\Entity\Poll\Grade; +use App\Entity\Poll\Proposal; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; /** * A Liquid Majority Judgment Poll. - * A poll is not editable after creation. + * A poll is not editable after creation. (or perhaps after the first judgment has been cast) + * The includes the poll's grades. + * New proposals MAY be added during the poll, but the poll's settings should govern this. * A poll cannot be deleted without privileges. * + * Also, how are we going to localize the error messages? + * See https://framagit.org/limaju/limaju-server-symfony/-/issues/8 + * * @ApiResource( - * normalizationContext={"groups"={"read"}}, + * normalizationContext={"groups"={"Poll:read"}}, * itemOperations={ * "get"={ - * "normalization_context"={"groups"={"read"}}, + * "normalization_context"={"groups"={"Poll:read"}}, * }, * "delete"={ * "access_control"="is_granted('can_delete', object)", @@ -30,82 +39,169 @@ use Symfony\Component\Serializer\Annotation\Groups; * }, * collectionOperations={ * "post"={ - * "denormalization_context"={"groups"={"create"}}, + * "denormalization_context"={"groups"={"Poll:create"}}, * }, - * } + * }, + * ) + * @ORM\Entity( + * repositoryClass="App\Repository\PollRepository", * ) - * @ORM\Entity(repositoryClass="App\Repository\PollRepository") */ class Poll { + const SCOPE_PUBLIC = 'public'; + const SCOPE_UNLISTED = 'unlisted'; + const SCOPE_PRIVATE = 'private'; + + /** @deprecated */ const MENTION_EXCELLENT = 'bonega'; + /** @deprecated */ const MENTION_VERY_GOOD = 'trebona'; + /** @deprecated */ const MENTION_GOOD = 'bona'; + /** @deprecated */ const MENTION_PASSABLE = 'trairebla'; + /** @deprecated */ const MENTION_INADEQUATE = 'neadekvata'; + /** @deprecated */ const MENTION_MEDIOCRE = 'malboneta'; + /** @deprecated */ const MENTION_TO_REJECT = 'malakcepti'; - /** - * @var UuidInterface + * @var int|null + * @ApiProperty(identifier=false) * - * @Groups({ "read" }) - * @ORM\Id - * @ORM\Column(type="uuid", unique=true) - * @ORM\GeneratedValue(strategy="CUSTOM") - * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator") + * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** - * @Groups({ "create", "read", "update" }) + * @var UuidInterface|null + * @ApiProperty(identifier=true) + * @ORM\Column(type="uuid", unique=true) + * @Groups({"Poll:read"}) + */ + public $uuid; + + /** + * The scope of the poll + * Defines where and how the poll is accessible. + * @var string One of Poll::SCOPE_* + * @Groups({"Poll:create", "Poll:read", "Poll:update"}) + * @ORM\Column(type="string", length=16) + */ + private $scope = self::SCOPE_UNLISTED; + + /** + * The subject of the poll. Careful consideration should be taken in the writing of this. + * + * @var string + * @Groups({"Poll:create", "Poll:read", "Poll:update"}) * @ORM\Column(type="string", length=142) + * @Assert\NotBlank( + * message = "The poll must have a subject.", + * ) + * @Assert\Length( + * min="1", + * max="142", + * minMessage = "The poll's subject must be at least {{ limit }} characters.", + * maxMessage = "The poll's subject can be at most {{ limit }} characters.", + * ) + */ + private $subject; + + /** + * A list of proposals to judge. At least two proposals are required. + * + * @var ArrayCollection + * @Groups({"Poll:create", "Poll:read", "Poll:update"}) + * @ApiSubresource() + * @ORM\OneToMany( + * targetEntity="App\Entity\Poll\Proposal", + * mappedBy="poll", + * cascade={"persist"}, + * orphanRemoval=true, + * ) + * The limits are arbitrary, and open (like the rest) to discussion. + * A poll with less then two proposals make no sense. + * We have to set a maximum limit, against abuse scenarios. It can be higher. + * @Assert\Count( + * min=2, + * max=256, + * minMessage = "You must specify at least two proposals.", + * maxMessage = "You cannot specify more than {{ limit }} proposals.", + * ) + */ + private $proposals; + + /** + * @var ArrayCollection + * @Groups({"Poll:create", "Poll:read", "Poll:update"}) + * @ApiSubresource() + * @ORM\OneToMany( + * targetEntity="App\Entity\Poll\Grade", + * mappedBy="poll", + * cascade={"persist"}, + * orphanRemoval=true, + * ) + * The maximum limit of grades is arbitrary and not backed by anything. Discuss! + * @Assert\Count( + * min=2, + * max=16, + * minMessage = "You must specify at least two grades.", + * maxMessage = "You cannot specify more than {{ limit }} grades.", + * ) */ - private $title; + private $grades; /** - * @Groups({ "create", "read", "update" }) + * @var ArrayCollection + * Groups({}) * @ApiSubresource() * @ORM\OneToMany( - * targetEntity="PollCandidate", + * targetEntity="App\Entity\Poll\Invitation", * mappedBy="poll", * cascade={"persist"}, * orphanRemoval=true, * ) */ - private $candidates; + private $invitations; /** - * @Groups({ "none" }) - * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="limajuPolls") + * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="polls") */ private $author; - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - public function __construct() { - $this->candidates = new ArrayCollection(); + $this->proposals = new ArrayCollection(); + $this->grades = new ArrayCollection(); + $this->uuid = Uuid::uuid4(); } - public function getId(): ?UuidInterface + public function getId(): ?int { return $this->id; } - public function getTitle(): ?string + public function getUuid(): ?UuidInterface { - return $this->title; + return $this->uuid; } - public function setTitle(string $title): self + public function getSubject(): ?string { - $this->title = $title; + return $this->subject; + } + + public function setSubject(string $subject): self + { + $this->subject = $subject; return $this; } @@ -113,30 +209,30 @@ class Poll /** - * @return Collection|PollCandidate[] + * @return Collection|Proposal[] */ - public function getCandidates(): Collection + public function getProposals(): Collection { - return $this->candidates; + return $this->proposals; } - public function addCandidate(PollCandidate $candidate): self + public function addProposal(Proposal $proposal): self { - if (!$this->candidates->contains($candidate)) { - $this->candidates[] = $candidate; - $candidate->setPoll($this); + if (!$this->proposals->contains($proposal)) { + $this->proposals[] = $proposal; + $proposal->setPoll($this); } return $this; } - public function removeCandidate(PollCandidate $candidate): self + public function removeProposal(Proposal $proposal): self { - if ($this->candidates->contains($candidate)) { - $this->candidates->removeElement($candidate); + if ($this->proposals->contains($proposal)) { + $this->proposals->removeElement($proposal); // set the owning side to null (unless already changed) - if ($candidate->getPoll() === $this) { - $candidate->setPoll(null); + if ($proposal->getPoll() === $this) { + $proposal->setPoll(null); } } @@ -155,125 +251,90 @@ class Poll return $this; } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Should we do it like this in the end? - -// /** -// * @ORM\Column(type="string", length=32) -// */ -// private $mention_a; -// -// /** -// * @ORM\Column(type="string", length=32) -// */ -// private $mention_b; -// -// /** -// * @ORM\Column(type="string", length=32, nullable=true) -// */ -// private $mention_c; -// -// /** -// * @ORM\Column(type="string", length=32, nullable=true) -// */ -// private $mention_d; -// -// /** -// * @ORM\Column(type="string", length=32, nullable=true) -// */ -// private $mention_e; -// -// /** -// * @ORM\Column(type="string", length=32, nullable=true) -// */ -// private $mention_f; -// -// /** -// * @ORM\Column(type="string", length=32, nullable=true) -// */ -// private $mention_g; - -// public function getMentionA(): ?string -// { -// return $this->mention_a; -// } -// -// public function setMentionA(string $mention_a): self -// { -// $this->mention_a = $mention_a; -// -// return $this; -// } -// -// public function getMentionB(): ?string -// { -// return $this->mention_b; -// } -// -// public function setMentionB(string $mention_b): self -// { -// $this->mention_b = $mention_b; -// -// return $this; -// } -// -// public function getMentionC(): ?string -// { -// return $this->mention_c; -// } -// -// public function setMentionC(?string $mention_c): self -// { -// $this->mention_c = $mention_c; -// -// return $this; -// } -// -// public function getMentionD(): ?string -// { -// return $this->mention_d; -// } -// -// public function setMentionD(?string $mention_d): self -// { -// $this->mention_d = $mention_d; -// -// return $this; -// } -// -// public function getMentionE(): ?string -// { -// return $this->mention_e; -// } -// -// public function setMentionE(?string $mention_e): self -// { -// $this->mention_e = $mention_e; -// -// return $this; -// } -// -// public function getMentionF(): ?string -// { -// return $this->mention_f; -// } -// -// public function setMentionF(?string $mention_f): self -// { -// $this->mention_f = $mention_f; -// -// return $this; -// } -// -// public function getMentionG(): ?string -// { -// return $this->mention_g; -// } -// -// public function setMentionG(?string $mention_g): self -// { -// $this->mention_g = $mention_g; -// -// return $this; -// } + /** + * @return Collection|Grade[] + */ + public function getGrades(): Collection + { + return $this->grades; + } + + public function addGrade(Grade $grade): self + { + if ( ! $this->grades->contains($grade)) { + $this->grades[] = $grade; + $grade->setPoll($this); + } + + return $this; + } + + public function removeGrade(Grade $grade): self + { + if ($this->grades->contains($grade)) { + $this->grades->removeElement($grade); + // set the owning side to null (unless already changed) + if ($grade->getPoll() === $this) { + $grade->setPoll(null); + } + } + + return $this; + } + + public function getGradesInOrder() : array + { + $grades = $this->getGrades()->toArray(); + usort($grades, function (Grade $a, Grade $b) { + return $a->getLevel() - $b->getLevel(); + }); + return $grades; + } + + public function getLevelsOfGrades() : array + { + $levels = []; + foreach ($this->getGrades() as $grade) { + $levels[$grade->getName()] = $grade->getLevel(); + } + return $levels; + } + + public function getGradesNames() : array + { + $names = []; + foreach ($this->getGradesInOrder() as $grade) { + $names[] = $grade->getName(); + } + + return $names; + } + + public function getDefaultGrade() : Grade + { + $grades = $this->getGradesInOrder(); + assert( ! empty($grades), "Poll should have grades."); + return $grades[0]; + } + + public function getDefaultGradeName() : string + { + return $this->getDefaultGrade()->getName(); + } + + /** + * @return string + */ + public function getScope(): string + { + return $this->scope; + } + + /** + * @param string $scope + */ + public function setScope(string $scope): void + { + $this->scope = $scope; + } } diff --git a/src/Entity/Poll/Grade.php b/src/Entity/Poll/Grade.php new file mode 100644 index 0000000000000000000000000000000000000000..e3c5fd0080b6a07bcf8980d1ee13f0d49ca0942b --- /dev/null +++ b/src/Entity/Poll/Grade.php @@ -0,0 +1,129 @@ +<?php + + +namespace App\Entity\Poll; + + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use App\Entity\Poll; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Annotation\Groups; + + +/** + * Grades are attributed to Proposals by Participants, in Ballots. + * Each Poll has at least two Grades like this. + * This entity embodies an available Grade for a given Poll. + * This entity is not reusable between polls. (we'll use Presets) + * This entity is not a vote. (see Ballot) + * This entity should eventually be localized. + * + * Participant's Ballots will hold references to one Grade and one Proposal. + * + * @ApiResource() + * @ORM\Entity( + * repositoryClass="App\Repository\PollGradeRepository", + * ) + */ +class Grade +{ + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * @ApiProperty(identifier=false) + */ + private $id; + + /** + * Universally Unique IDentifier, something like this: 10e3c5e8-4a7d-4d23-a20a-8c175bf45a92 + * + * @var UuidInterface|null + * @ORM\Column(type="uuid", unique=true) + * @ApiProperty(identifier=true) + * @Groups({"Grade:read"}) + */ + public $uuid; + + /** + * @ORM\Column(type="string", length=32) + * @Groups({"Grade:read", "Poll:create"}) + */ + private $name; + + /** + * Used to compare grades procedurally. + * Usually starts at zero (0) and ends at <MAXIMUM_GRADES>-1. + * Grades of the same poll MUST have unique levels between themselves. + * + * @ORM\Column(type="integer") + * @Groups({"Grade:read", "Poll:create"}) + */ + private $level; + + /** + * The poll this grade is attached to. + * + * Groups({"Proposal:create"}) + * Groups({"Poll:create"}) + * @ORM\ManyToOne( + * targetEntity="App\Entity\Poll", + * inversedBy="grades" + * ) + * @ORM\JoinColumn(nullable=false) + */ + private $poll; + + public function __construct() + { + $this->uuid = Uuid::uuid4(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getLevel(): ?int + { + return $this->level; + } + + public function setLevel(int $level): self + { + $this->level = $level; + + return $this; + } + + /** + * @return mixed + */ + public function getPoll() : Poll + { + return $this->poll; + } + + /** + * @param mixed $poll + */ + public function setPoll($poll): void + { + $this->poll = $poll; + } +} diff --git a/src/Entity/Poll/Invitation.php b/src/Entity/Poll/Invitation.php new file mode 100644 index 0000000000000000000000000000000000000000..a1c2b88359cd69b5a2192d24936e7e6f53f8345e --- /dev/null +++ b/src/Entity/Poll/Invitation.php @@ -0,0 +1,152 @@ +<?php + + +namespace App\Entity\Poll; + + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use App\Entity\Poll; +use App\Entity\User; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Annotation\Groups; +use App\Controller\GetOrCreateInvitationsController; +use App\Controller\AcceptInvitationController; + +// * itemOperations={ +// * "get"={ +// * "normalization_context"={"groups"={"Invitation:read"}}, +// * }, +// * }, +/** + * An invitation to a poll. + * Those are created on-demand. + * + * Only polls with scope Poll::SCOPE_PRIVATE require invitations. + * + * @ApiResource( + * normalizationContext={"groups"={"Invitation:read"}}, + * itemOperations={ + * "get"={ + * "method"="GET", + * "controller"=AcceptInvitationController::class, + * }, + * }, + * collectionOperations={ + * "get", + * "get_for_poll"={ + * "method"="GET", + * "controller"=GetOrCreateInvitationsController::class, + * "path"="/polls/{pollId}/invitations.{_format}", + * }, + * }, + * ) + * @ORM\Entity( + * repositoryClass="App\Repository\PollInvitationRepository", + * ) + */ +class Invitation +{ + /** + * @var int|null + * @ApiProperty(identifier=false) + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + */ + private $id; + + /** + * Universally Unique IDentifier, something like this: 10e3c5e8-4a7d-4d23-a20a-8c175bf45a92 + * + * @var UuidInterface|null + * @ApiProperty(identifier=true) + * @ORM\Column(type="uuid", unique=true) + * @Groups({"Invitation:read"}) + */ + private $uuid; + + /** + * The poll this invitation is for. + * Only polls with scope Poll::SCOPE_PRIVATE require invitations. + * + * @Groups({"Invitation:create", "Invitation:read"}) + * @ORM\ManyToOne( + * targetEntity="App\Entity\Poll", + * inversedBy="invitations" + * ) + * @ORM\JoinColumn(nullable=false) + */ + private $poll; + + // + // + + private $is_consumed; // use method instead + + /** + * As long as this is empty, the invitation is still open. + * Should we make a Participant Entity? + * + * @var User|null + * @Groups({"Invitation:read"}) + * @ORM\ManyToOne( + * targetEntity="App\Entity\User", + * inversedBy="invitations" + * ) + * @ORM\JoinColumn(nullable=true) + */ + private $participant; + // That might work… + + // + // + + public function __construct() + { + $this->uuid = Uuid::uuid4(); + } + + /** + * @return UuidInterface|null + */ + public function getUuid(): ?UuidInterface + { + return $this->uuid; + } + + /** + * @return Poll|null + */ + public function getPoll() : ?Poll + { + return $this->poll; + } + + /** + * @param mixed $poll + */ + public function setPoll(Poll $poll): void + { + $this->poll = $poll; + } + + /** + * @return User|null + */ + public function getParticipant(): ?User + { + return $this->participant; + } + + /** + * @param User|null $participant + */ + public function setParticipant(?User $participant): void + { + $this->participant = $participant; + } + +} diff --git a/src/Entity/Poll/Proposal.php b/src/Entity/Poll/Proposal.php new file mode 100644 index 0000000000000000000000000000000000000000..e3905f8e0f21a15a7e79b0ec39f9d1300b262aa9 --- /dev/null +++ b/src/Entity/Poll/Proposal.php @@ -0,0 +1,174 @@ +<?php + + +namespace App\Entity\Poll; + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Core\Annotation\ApiSubresource; +use App\Entity\Poll; +use App\Entity\Poll\Proposal\Ballot; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Annotation\Groups; + +// * "path"="/polls/{pollId}/proposals/{proposalId}", + +/** + * A Proposal of a Poll whom any Judge can give a Grade (aka. Mention) to. + * + * @ApiResource( + * normalizationContext={"groups"={"Proposal:read"}}, + * itemOperations={ + * "get"={ + * "normalization_context"={"groups"={"Proposal:read"}}, + * }, + * }, + * collectionOperations={ + * "post"={ + * "method"="POST", + * "denormalization_context"={"groups"={"Proposal:create"}}, + * "path"="/polls/{pollId}/proposals", + * }, + * }, + * subresourceOperations={ + * "api_polls_proposals_get_subresource"={ + * "method"="GET", + * "normalization_context"={"groups"={"Proposal:read"}}, + * "path"="/polls/{pollId}/proposals", + * }, + * }, + * + * ) + * @ORM\Entity( + * repositoryClass="App\Repository\PollProposalRepository", + * ) + */ +class Proposal +{ + /** + * Should only be used internally. + * + * @var int|null + * @ApiProperty(identifier=false) + * + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + */ + private $id; + + /** + * Universally Unique IDentifier, something like this: 10e3c5e8-4a7d-4d23-a20a-8c175bf45a92 + * + * @var UuidInterface|null + * @ApiProperty(identifier=true) + * @ORM\Column(type="uuid", unique=true) + * @Groups({"Proposal:read"}) + */ + public $uuid; + + /** + * → name ? + * + * @Groups({"Proposal:create", "Proposal:read", "Poll:create"}) + * @ORM\Column(type="string", length=142) + */ + private $title; + + /** + * The poll this proposal is attached to. + * + * @Groups({"Proposal:create"}) + * @ORM\ManyToOne( + * targetEntity="App\Entity\Poll", + * inversedBy="proposals", + * ) + * @ORM\JoinColumn(nullable=false) + */ + private $poll; + + /** + * @ORM\OneToMany( + * targetEntity="App\Entity\Poll\Proposal\Ballot", + * mappedBy="proposal", + * orphanRemoval=true, + * ) + * @ApiSubresource() + */ + private $ballots; + + public function __construct() + { + $this->ballots = new ArrayCollection(); + $this->uuid = Uuid::uuid4(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUuid(): ?UuidInterface + { + return $this->uuid; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getPoll(): ?Poll + { + return $this->poll; + } + + public function setPoll(?Poll $poll): self + { + $this->poll = $poll; + + return $this; + } + + /** + * @return Collection|Ballot[] + */ + public function getBallots(): Collection + { + return $this->ballots; + } + + public function addBallot(Ballot $vote): self + { + if (!$this->ballots->contains($vote)) { + $this->ballots[] = $vote; + $vote->setProposal($this); + } + + return $this; + } + + public function removeBallot(Ballot $vote): self + { + if ($this->ballots->contains($vote)) { + $this->ballots->removeElement($vote); + // set the owning side to null (unless already changed) + if ($vote->getProposal() === $this) { + $vote->setProposal(null); + } + } + + return $this; + } +} diff --git a/src/Entity/Poll/Proposal/Ballot.php b/src/Entity/Poll/Proposal/Ballot.php new file mode 100644 index 0000000000000000000000000000000000000000..e36a02cd8159c8558add7afeb1e3fc0715a7708b --- /dev/null +++ b/src/Entity/Poll/Proposal/Ballot.php @@ -0,0 +1,179 @@ +<?php + + +namespace App\Entity\Poll\Proposal; + + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use App\Controller\CreateBallotController; +use App\Entity\Poll\Proposal; +use App\Entity\User; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Annotation\Groups; + + +/** + * A Ballot holds a Judgment on a Proposal, by a Participant of a Poll. + * + * The sacred text is titled "Judge! Don't Vote." ;) + * + * Rules TBD: + * + * RULESET A: Immutability + * A Ballot is immutable. + * A Ballot cannot be deleted. + * Multiple Ballots may be recorded, and only the most recent one should matter in the Tally. + * + * RULESET B: Mutability + * A Ballot is mutable. + * Only one Ballot per Proposal and Participant. + * + * Right now we implement neither A nor B. There are no Gherkin features for this yet. Help us! + * + * @ApiResource( + * itemOperations={ + * "get"={ + * "normalization_context"={"groups"={"Ballot:read"}}, + * }, + * "delete"={ + * "access_control"="is_granted('can_delete', object)", + * }, + * }, + * collectionOperations={ + * "post"={ + * "path"="/polls/{pollId}/proposals/{proposalId}/ballots.{_format}", + * "method"="POST", + * "controller"=CreateBallotController::class, + * "denormalization_context"={"groups"={"Ballot:create"}}, + * "normalization_context"={"groups"={"Ballot:read"}}, + * "access_control"="is_granted('ROLE_USER')", + * }, + * }, + * ) + * "route_name"="api_poll_proposal_votes_post_collection", + * @ORM\Entity( + * repositoryClass="App\Repository\PollProposalBallotRepository", + * ) + */ +class Ballot +{ + /** + * @var int|null + * @ApiProperty(identifier=false) + * + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @var UuidInterface|null + * @ApiProperty(identifier=true) + * @ORM\Column(type="uuid", unique=true) + * @Groups({"Ballot:read"}) + */ + public $uuid; + + /** + * The Majority Judgment Poll Proposal the author is giving a grade to. + * + * @Groups({"Ballot:create", "Ballot:read"}) + * @ORM\ManyToOne( + * targetEntity="App\Entity\Poll\Proposal", + * inversedBy="votes", + * ) + * @ORM\JoinColumn(nullable=false) + */ + private $proposal; + + /** + * The name of the author of the vote, if any was specified. + * TBD. May be deprecated soon. + * + * @Groups({"Ballot:create", "Ballot:read"}) + * @ORM\Column(type="string", length=32, nullable=true) + */ + private $author_name; + + /** + * The Grade attributed by the Judge to the Proposal. + * + * @Groups({"Ballot:create", "Ballot:read"}) + * @ORM\Column(type="string", length=32) + */ + private $grade; + + /** + * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="votes") + */ + private $elector; // TODO $judge / $author / $voter / $owner / $participant + + + public function __construct() + { + $this->uuid = Uuid::uuid4(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUuid(): ?UuidInterface + { + return $this->uuid; + } + + public function getProposal(): ?Proposal + { + return $this->proposal; + } + + public function setProposal(?Proposal $proposal): self + { + $this->proposal = $proposal; + + return $this; + } + + public function getGrade(): ?string + { + return $this->grade; + } + + public function setGrade(string $grade): self + { + $this->grade = $grade; + + return $this; + } + + public function getAuthorName(): ?string + { + return $this->author_name; + } + + public function setAuthorName(?string $author_name): self + { + $this->author_name = $author_name; + + return $this; + } + + public function getElector(): ?User + { + return $this->elector; + } + + public function setElector(?User $elector): self + { + $this->elector = $elector; + + return $this; + } + +} diff --git a/src/Entity/PollTally.php b/src/Entity/Poll/Tally.php similarity index 86% rename from src/Entity/PollTally.php rename to src/Entity/Poll/Tally.php index d3457eadb1514ed476b661381a0885c224d5cdbb..2bc421710bb3bcb17d1d7d5061bcc19fb083aac9 100644 --- a/src/Entity/PollTally.php +++ b/src/Entity/Poll/Tally.php @@ -1,20 +1,21 @@ <?php -namespace App\Entity; +namespace App\Entity\Poll; use ApiPlatform\Core\Annotation\ApiResource; use App\Tally\Output\PollTally as TallyOutput; use Symfony\Component\Serializer\Annotation\Groups; + /** * A tally of a liquid majority judgment poll. * There may (will!) be additional tallies in the future, * and we'd love to make adding new tally algorithms easy. * * @ApiResource( - * shortName="PollTally", + * shortName="Tally", * itemOperations={ * "get"={ * "controller"="App\Controller\GetTallyController", @@ -23,11 +24,11 @@ use Symfony\Component\Serializer\Annotation\Groups; * collectionOperations={}, * ) */ -class PollTally +class Tally { /** * @var TallyOutput Standard tally output. - * @Groups({ "read" }) + * @Groups({"Tally:read"}) */ public $standard; } \ No newline at end of file diff --git a/src/Entity/PollCandidate.php b/src/Entity/PollCandidate.php deleted file mode 100644 index c6e5dc574a93f6af378a1070124a44253496e710..0000000000000000000000000000000000000000 --- a/src/Entity/PollCandidate.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php - - -namespace App\Entity; - - -use ApiPlatform\Core\Annotation\ApiResource; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\ORM\Mapping as ORM; -use Ramsey\Uuid\UuidInterface; -use Symfony\Component\Serializer\Annotation\Groups; - - -/** - * A Candidate of a Liquid Majority Judgment Poll whom any Elector can give a Mention to. - * - * @ApiResource( - * normalizationContext={"groups"={"read"}}, - * itemOperations={ - * "get"={ - * "normalization_context"={"groups"={"read"}}, - * }, - * }, - * collectionOperations={ - * "post"={ - * "denormalization_context"={"groups"={"create"}}, - * }, - * } - * ) - * @ORM\Entity( - * repositoryClass="App\Repository\PollCandidateRepository", - * ) - */ -class PollCandidate -{ - /** - * @var UuidInterface - * - * @Groups({ "read" }) - * @ORM\Id - * @ORM\Column(type="uuid", unique=true) - * @ORM\GeneratedValue(strategy="CUSTOM") - * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator") - */ - private $id; - - /** - * @Groups({ "create", "read" }) - * @ORM\Column(type="string", length=142) - */ - private $title; - - /** - * The poll this candidate is attached to. - * - * @Groups({ "create" }) - * @ORM\ManyToOne(targetEntity="Poll", inversedBy="candidates") - * @ORM\JoinColumn(nullable=false) - */ - private $poll; - -// /** -// * @ORM\OneToMany(targetEntity="App\Entity\LimajuCandidateVote", mappedBy="candidate", orphanRemoval=true) -// */ -// private $votes; - - public function __construct() - { - $this->votes = new ArrayCollection(); - } - - public function getId() - { - return $this->id; - } - - public function getTitle(): ?string - { - return $this->title; - } - - public function setTitle(string $title): self - { - $this->title = $title; - - return $this; - } - - public function getPoll(): ?Poll - { - return $this->poll; - } - - public function setPoll(?Poll $poll): self - { - $this->poll = $poll; - - return $this; - } - - /** - * @return Collection|PollCandidateVote[] - */ - public function getVotes(): Collection - { - return $this->votes; - } - - public function addVote(PollCandidateVote $vote): self - { - if (!$this->votes->contains($vote)) { - $this->votes[] = $vote; - $vote->setCandidate($this); - } - - return $this; - } - - public function removeVote(PollCandidateVote $vote): self - { - if ($this->votes->contains($vote)) { - $this->votes->removeElement($vote); - // set the owning side to null (unless already changed) - if ($vote->getCandidate() === $this) { - $vote->setCandidate(null); - } - } - - return $this; - } -} diff --git a/src/Entity/PollCandidateVote.php b/src/Entity/PollCandidateVote.php deleted file mode 100644 index 88c8655212317123d8bc451925913794e87e363e..0000000000000000000000000000000000000000 --- a/src/Entity/PollCandidateVote.php +++ /dev/null @@ -1,146 +0,0 @@ -<?php - - -namespace App\Entity; - - -use ApiPlatform\Core\Annotation\ApiResource; -use Doctrine\ORM\Mapping as ORM; -use Ramsey\Uuid\UuidInterface; -use Symfony\Component\Serializer\Annotation\Groups; - - -/** - * A Vote on a Candidate of a Majority Judgment Poll. - * A Vote is immutable. - * A Vote cannot be deleted. - * - * @ApiResource( - * itemOperations={ - * "get"={ - * "normalization_context"={"groups"={"vote:read"}}, - * }, - * "delete"={ - * "access_control"="is_granted('can_delete', object)", - * }, - * }, - * collectionOperations = { - * "post"={ - * "denormalization_context"={"groups"={"vote:create"}}, - * "normalization_context"={"groups"={"vote:read"}}, - * }, - * } - * ) - * @ORM\Entity(repositoryClass="PollCandidateVoteRepository") - */ -class PollCandidateVote -{ - /** - * @var UuidInterface - * - * @Groups({ "read", "vote:read" }) - * @ORM\Id - * @ORM\Column(type="uuid", unique=true) - * @ORM\GeneratedValue(strategy="CUSTOM") - * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator") - */ - private $id; - - - /** - * The Majority Judgment Poll Candidate the author is giving a mention to. - * - * @Groups({ "vote:create", "vote:read" }) - * @ORM\ManyToOne(targetEntity="PollCandidate", inversedBy="votes") - * @ORM\JoinColumn(nullable=false) - */ - private $candidate; - - - /** - * The elector of the vote, the one that chooses the given mention. - * (if there is such a thing as choice) - * This is optional, and some polls may require all participants to be logged in. - * For anonymous votes, this may be null. - * - * @Groups({ "none" }) - * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="limajuPollCandidateVotes") - * @ORM\JoinColumn(nullable=true) - */ - private $elector; - - - /** - * The name of the author of the vote, if any was specified. - * - * @Groups({ "vote:create", "vote:read" }) - * @ORM\Column(type="string", length=32, nullable=true) - */ - private $author_name; - - - /** - * The mention attributed by the author to the candidate. - * - * @Groups({ "vote:create", "vote:read" }) - * @ORM\Column(type="string", length=16) - */ - private $mention; - - - - - public function getId(): ?UuidInterface - { - return $this->id; - } - - public function getCandidate(): ?PollCandidate - { - return $this->candidate; - } - - public function setCandidate(?PollCandidate $candidate): self - { - $this->candidate = $candidate; - - return $this; - } - - public function getElector(): ?User - { - return $this->elector; - } - - public function setElector(?User $elector): self - { - $this->elector = $elector; - - return $this; - } - - public function getMention(): ?string - { - return $this->mention; - } - - public function setMention(string $mention): self - { - $this->mention = $mention; - - return $this; - } - - public function getAuthorName(): ?string - { - return $this->author_name; - } - - public function setAuthorName(?string $author_name): self - { - $this->author_name = $author_name; - - return $this; - } - -} diff --git a/src/Entity/Role.php b/src/Entity/Role.php deleted file mode 100644 index 5920d41a912da393dd54b9115bdd85bc14c0182d..0000000000000000000000000000000000000000 --- a/src/Entity/Role.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Entity; - -use Doctrine\ORM\Mapping as ORM; -use MsgPhp\User\Role as BaseRole; - -/** - * @ORM\Entity() - * - * @final - */ -class Role extends BaseRole -{ - /** @ORM\Id() @ORM\Column(length=191) */ - private $name; - - public function __construct(string $name) - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } -} diff --git a/src/Entity/User.php b/src/Entity/User.php index 218a11d910a5b4091e753bd5f1fc202a5654cf09..b029cfdc1ac464c57856f2e3a06258e480cc5314 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -2,120 +2,294 @@ namespace App\Entity; +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use App\Entity\Poll\Proposal\Ballot; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use MsgPhp\Domain\Event\DomainEventHandler; -use MsgPhp\Domain\Event\DomainEventHandlerTrait; -use MsgPhp\User\Credential\EmailPassword; -use MsgPhp\User\Model\EmailPasswordCredential; -use MsgPhp\User\Model\ResettablePassword; -use MsgPhp\User\Model\RolesField; -use MsgPhp\User\User as BaseUser; -use MsgPhp\User\UserId; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Annotation\Groups; - +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Serializer\Annotation\SerializedName; /** - * We ditched FosUserBundle for the new MsgPhp\User - * - * @ORM\Entity() - * @UniqueEntity(fields={"name"}, message="There is already an account with this name") + * @ORM\Entity(repositoryClass="App\Repository\UserRepository") + * @UniqueEntity(fields={"email"}, groups={"register", "edit"}, message="Email already in use") + * @ApiResource( + * attributes={ + * "normalization_context"={"groups"={"User:read"}}, + * "denormalization_context"={"groups"={"User:edit"}}, + * "validation_groups"={"register", "edit"} + * }, + * collectionOperations={ + * "get"={ + * "method"="GET", + * "access_control"="is_granted('ROLE_ADMIN')" + * }, + * "post"={ + * "method"="POST", + * "access_control"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY') or is_granted('ROLE_ADMIN')", + * "denormalization_context"={"groups"={"User:create"}}, + * "validation_groups"={"register"} + * }, + * }, + * itemOperations={ + * "get"={ + * "method"="GET", + * "access_control"="is_granted('ROLE_USER') and object == user or is_granted('ROLE_ADMIN')", + * "normalization_context"={"groups"={"User:read"}}, + * }, + * "put"={ + * "method"="PUT", + * "access_control"="is_granted('ROLE_USER') and object == user or is_granted('ROLE_ADMIN')", + * "normalization_context"={"groups"={"User:read"}}, + * "denormalization_context"={"groups"={"User:edit"}} + * }, + * "delete"={ + * "method"="DELETE", + * "access_control"="is_granted('ROLE_ADMIN')" + * }, + * } + * ) */ -class User extends BaseUser implements DomainEventHandler, UserInterface -{ - use DomainEventHandlerTrait; - use EmailPasswordCredential; - use ResettablePassword; - use RolesField; - +class User implements UserInterface +{ /** + * @var int|null + * @ApiProperty(identifier=false) * @ORM\Id() * @ORM\GeneratedValue() - * @ORM\Column(type="msgphp_user_id", length=191) + * @ORM\Column(type="integer") */ - private $id; + private $id; /** - * @Groups({ "create", "read", "update" }) - * @ORM\Column(type="string", length=32, nullable=true) + * @var UuidInterface|null + * @ApiProperty(identifier=true) + * @ORM\Column(type="uuid", unique=true) + * @Groups({"User:read"}) */ - public $name; + public $uuid; /** - * Authored Limaju Polls. - * - * @Groups({ "never" }) - * @ORM\OneToMany(targetEntity="Poll", mappedBy="author") + * @ORM\Column(type="string", length=180, unique=true) + * @Groups({"User:create", "User:read", "User:edit"}) + * @Assert\Email(groups={"register", "edit"}) + * @Assert\NotBlank(groups={"register", "edit"}) + */ + private $email; + + /** + * @ORM\Column(type="string", length=255, nullable=true) + * @Groups({"User:create", "User:read", "User:edit"}) + */ + private $username; + + /** + * @ORM\Column(type="json") + */ + private $roles = []; + + /** + * @var string The hashed password + * @ORM\Column(type="string") + * */ - private $limajuPolls; + private $password; + /** + * @Groups({"User:create", "User:edit"}) + * @Assert\NotBlank(groups={"register, login"}) + * @Assert\Length(max=1024, groups={"register", "edit", "login"}) + * @SerializedName("password") + */ + private $plainPassword; + + /** + * @ORM\OneToMany(targetEntity="App\Entity\Poll", mappedBy="author") + * @Groups({"User:read"}) + */ + private $polls; + + /** + * @ORM\OneToMany(targetEntity="App\Entity\Poll\Proposal\Ballot", mappedBy="elector") + * @Groups({"User:read"}) + */ + private $ballots; - public function __construct(UserId $id, string $email, string $password) + public function __construct() { - $this->id = $id; -// $this->name = $name; - $this->credential = new EmailPassword($email, $password); - $this->limajuPolls = new ArrayCollection(); + $this->polls = new ArrayCollection(); + $this->ballots = new ArrayCollection(); + $this->uuid = Uuid::uuid4(); } - public function getId(): UserId + public function getId(): ?int { return $this->id; } - public function getUsername() + public function getUuid(): ?UuidInterface + { + return $this->uuid; + } + + public function getEmail(): ?string { - return $this->getCredential()->getUsername(); + return $this->email; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; } /** - * Returns the salt that was originally used to encode the password. * - * This can return null if the password was not encoded using a salt. - * - * @return string|null The salt + * @see UserInterface + */ + public function getUsername(): string + { + return (string)$this->username; + } + + public function setUsername(?string $username): self + { + $this->username = $username; + + return $this; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + public function addRole(string $role): self + { + if ( ! in_array($role, $this->roles)) { + $this->roles[] = $role; + } + + return $this; + } + + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + public function getPlainPassword(): string + { + return $this->plainPassword; + } + + public function setPlainPassword(string $plainPassword): self + { + $this->plainPassword = $plainPassword; + + return $this; + } + + /** + * @see UserInterface */ public function getSalt() { - return null; + // not needed when using the "bcrypt" algorithm in security.yaml } /** - * Removes sensitive data from the user. - * - * This is important if, at any given point, sensitive information like - * the plain-text password is stored on this object. + * @see UserInterface */ - public function eraseCredentials() {} + public function eraseCredentials() + { + $this->plainPassword = null; + } /** * @return Collection|Poll[] */ - public function getLimajuPolls(): Collection + public function getPolls(): Collection + { + return $this->polls; + } + + public function addPoll(Poll $poll): self + { + if (!$this->polls->contains($poll)) { + $this->polls[] = $poll; + $poll->setAuthor($this); + } + + return $this; + } + + public function removePoll(Poll $poll): self + { + if ($this->polls->contains($poll)) { + $this->polls->removeElement($poll); + // set the owning side to null (unless already changed) + if ($poll->getAuthor() === $this) { + $poll->setAuthor(null); + } + } + + return $this; + } + + /** + * @return Collection|Ballot[] + */ + public function getBallots(): Collection { - return $this->limajuPolls; + return $this->ballots; } - public function addLimajuPoll(Poll $limajuPoll): self + public function addBallot(Ballot $vote): self { - if (!$this->limajuPolls->contains($limajuPoll)) { - $this->limajuPolls[] = $limajuPoll; - $limajuPoll->setAuthor($this); + if (!$this->ballots->contains($vote)) { + $this->ballots[] = $vote; + $vote->setElector($this); } return $this; } - public function removeLimajuPoll(Poll $limajuPoll): self + public function removeBallot(Ballot $vote): self { - if ($this->limajuPolls->contains($limajuPoll)) { - $this->limajuPolls->removeElement($limajuPoll); + if ($this->ballots->contains($vote)) { + $this->ballots->removeElement($vote); // set the owning side to null (unless already changed) - if ($limajuPoll->getAuthor() === $this) { - $limajuPoll->setAuthor(null); + if ($vote->getElector() === $this) { + $vote->setElector(null); } } diff --git a/src/Entity/UserDocumentation.php b/src/Entity/UserDocumentation.php deleted file mode 100644 index 84bd1bc2c1d5b3fee67720df910da20c008b40f3..0000000000000000000000000000000000000000 --- a/src/Entity/UserDocumentation.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - - -namespace App\Entity; - - -use ApiPlatform\Core\Annotation\ApiResource; -use Symfony\Component\Serializer\Annotation\Groups; - - -// This class exists as a (hacky) way to provide documentation. - - -/** - * You. - * - * @ApiResource( - * shortName="User", - * order=10, - * itemOperations={}, - * collectionOperations={ - * "post"={ - * "controller"="App\Controller\RegistrationController", - * }, - * }, - * ) - */ -class UserDocumentation -{ - /** - * @var string Primary e-mail address - * @Groups({"user:read", "user:write"}) - */ - public $email; - - /** - * @var string|null Plain password (Required in "write") - * @Groups({"user:write"}) - */ - public $password; -} \ No newline at end of file diff --git a/src/Entity/UserRole.php b/src/Entity/UserRole.php deleted file mode 100644 index b2ecc7b1de03ace362bb91c2d7698c11ebab7809..0000000000000000000000000000000000000000 --- a/src/Entity/UserRole.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Entity; - -use Doctrine\ORM\Mapping as ORM; -use MsgPhp\User\UserRole as BaseUserRole; - -/** - * @ORM\Entity() - * @ORM\AssociationOverrides({ - * @ORM\AssociationOverride(name="user", inversedBy="roles") - * }) - * - * @final - */ -class UserRole extends BaseUserRole -{ -} diff --git a/src/EventListener/JWTCreatedListener.php b/src/EventListener/JWTCreatedListener.php new file mode 100644 index 0000000000000000000000000000000000000000..ba09001afcc68f274e99787d10f192241e98bb0a --- /dev/null +++ b/src/EventListener/JWTCreatedListener.php @@ -0,0 +1,40 @@ +<?php +namespace App\EventListener; + +use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent; +use Symfony\Component\HttpFoundation\RequestStack; + +class JWTCreatedListener +{ + /** + * @var RequestStack + */ + private $requestStack; + + /** + * @param RequestStack $requestStack + */ + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + /** + * @param JWTCreatedEvent $event + * + * @return void + */ + public function onJWTCreated(JWTCreatedEvent $event) + { + $request = $this->requestStack->getCurrentRequest(); + + $payload = $event->getData(); + /** @var User $user */ + $user = $event->getUser(); + + $payload['uuid'] = $user->getUuid(); + unset($payload['username']); + + $event->setData($payload); + } +} \ No newline at end of file diff --git a/src/EventSubscriber/SetAuthorOfEntityIfNeeded.php b/src/EventSubscriber/SetAuthorOfEntityIfNeeded.php index d300608d6ba5b628d4565915142f072f879f996c..8cb11e1d7b63af52fe4d16868f71c2598997b67b 100644 --- a/src/EventSubscriber/SetAuthorOfEntityIfNeeded.php +++ b/src/EventSubscriber/SetAuthorOfEntityIfNeeded.php @@ -7,7 +7,7 @@ namespace App\EventSubscriber; use ApiPlatform\Core\EventListener\EventPriorities; use App\Application; use App\Entity\Poll; -use App\Entity\PollCandidateVote; +use App\Entity\Poll\Proposal\Ballot; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -16,7 +16,7 @@ use Symfony\Component\HttpKernel\KernelEvents; /** * This is how we hook in API Platform to set the author, during creation, of * - Poll - * - PollCandidateVote + * - Ballot * * This only works for REST * https://github.com/api-platform/api-platform/issues/734 @@ -75,7 +75,7 @@ final class SetAuthorOfEntityIfNeeded implements EventSubscriberInterface $entity->setAuthor($this->application->getAuthenticatedUser()); } - if ($entity instanceof PollCandidateVote) { + if ($entity instanceof Ballot) { $entity->setElector($this->application->getAuthenticatedUser()); } } diff --git a/src/Features/Actor.php b/src/Features/Actor.php index 4ce1ef17bd9e25dfd80640d68453aa66f6ffcbf4..35b50adbfc6cc2cd61bf01105cf911cc5b9f71aa 100644 --- a/src/Features/Actor.php +++ b/src/Features/Actor.php @@ -4,511 +4,60 @@ namespace App\Features; -use App\Entity\Citizen; -//use FOS\UserBundle\Model\User; -use App\Entity\User; -use GuzzleHttp\Exception\TransferException; -use phpDocumentor\Reflection\Types\Object_; -use PHPUnit\Framework\AssertionFailedError; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Bundle\FrameworkBundle\Client as RestClient; -use function DeepCopy\deep_copy; +use App\Entity\Poll; /** - * Feature: Do things and remember them - * In order to do things and remember them - * As an Actor in the Gherkin scenarios - * I need an API client and a transaction log - * - * This usually represents "I" in the steps. + * Actor things that are specific to that application. * * Class Actor * @package App\Features */ -class Actor +class Actor extends ApiActor { - /** - * A HTTP Client for the REST API. - * Perhaps this dependency should be asked for in the constructor? - * Right now it is injected by the Actor factory code in BaseFeatureContext#actor() - * - * @var RestClient - */ - protected $client; - - - /** - * A log of transactions in the order they were accomplished, ie. most recent last. - * - * @var Transaction[] - */ - protected $transactions = []; - - - protected $api_prefix = '/api'; - - - /** - * @var User - */ - protected $user; - - - /** - * Since we can't recover the password from User#getPlainPassword when we need to, - * we store the password "on a post-it on the side of the screen", in this variable. - * - * @var string - */ - protected $password; - - - /** - * @return RestClient - */ - public function getClient(): RestClient - { - return $this->client; - } - - - /** - * @param RestClient $client - */ - public function setClient(RestClient $client): void - { - $this->client = $client; - } - - - /** - * @return Transaction[] - */ - public function getTransactions(): array - { - return $this->transactions; - } - - - /** - * @param Transaction $transaction - * @return Actor - */ - public function addTransaction(Transaction $transaction): self - { - $this->transactions[] = $transaction; - - return $this; - } - - - /** - * @return Transaction - * @throws ActorConfusion - */ - public function getLastTransaction(): Transaction - { - $transactionsCount = count($this->getTransactions()); - if (1 > $transactionsCount) { - throw new ActorConfusion( - "Trying to read the latest transaction but none happened.\n". - "Try to request something first?" - ); - } - - return $this->transactions[$transactionsCount-1]; - } - - - /** - * @return Transaction[] - * @throws ActorConfusion - */ - public function getLastTransactions($count): array - { - $transactionsCount = count($this->getTransactions()); - if ($count > $transactionsCount) { - throw new ActorConfusion( - "Trying to read the latest $count transactions but only $transactionsCount happened.\n" - ); - } -// return $this->getTransactions(); - return array_slice($this->transactions, -1 * $count); - } - - - /** - * @return User - */ - public function getUser(): User - { - return $this->user; - } - - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } - - - /** - * @return string - */ - public function getPassword(): string - { - return $this->password; - } - - - /** - * @param string $password - */ - public function setPassword(string $password): void - { - $this->password = $password; - } - - - /** - * @return string - */ - public function getApiPrefix(): string - { - return $this->api_prefix; - } - - - /** - * @param string $api_prefix - */ - public function setApiPrefix(string $api_prefix): void - { - $this->api_prefix = $api_prefix; - } - - - /** - * @param $route - * @return string - */ - protected function prefixRoute($route) - { - return sprintf('%s/%s', $this->getApiPrefix(), ltrim(trim($route), '/')); - } - - - /** - * @param string $query - * @param array|null $variables - * @param bool $only_trying - * @return Transaction - */ - public function gql(string $query, array $variables = null, bool $only_trying = false): Transaction - { - $parameters = [ - 'query' => $query, - ]; - if ( ! is_null($variables)) { - $parameters['variables'] = $variables; - } - - try { - $response = $this->request('POST', '/gql/', $parameters); - } catch (TransferException $e) { - throw new \RuntimeException('Network Error.' . $e->getMessage(), 0, $e); - } - -// $responseBuilder = new ResponseBuilder(); -// $response = $responseBuilder->build($response); - - $transaction = $this->getLastTransaction(); - - if (! $only_trying) { - $this->assertTransactionSuccess($transaction); - } - - return $transaction; - } - - - /** - * @param string $query - * @param array|null $variables - * @param bool $only_trying - * @return Transaction - */ - public function gqlNew(string $query, array $variables = null, bool $only_trying = false): Transaction - { - $parameters = [ - 'query' => $query, - ]; - if ( ! is_null($variables)) { - $parameters['variables'] = $variables; - } - //$parameters['operationName'] = "graphiql sets this, why?"; - - $server = []; - $server['HTTP_ACCEPT'] = "application/json"; - $server['CONTENT_TYPE'] = "application/json"; - - try { - $response = $this->request('POST', '/api/graphql', [], [], $server, json_encode($parameters)); - } catch (TransferException $e) { - throw new \RuntimeException('Network Error.' . $e->getMessage(), 0, $e); - } - -// $responseBuilder = new ResponseBuilder(); -// $response = $responseBuilder->build($response); - - $transaction = $this->getLastTransaction(); - - if (! $only_trying) { - $this->assertTransactionSuccess($transaction); - } - - return $transaction; - } - - - /** - * Send a request to our API. - * @deprecated - * - * @param string $method Where are the constants for this? - * @param string $route - * @param array $parameters - * @param bool $only_trying - * @return Transaction - */ - public function apiOld(string $method, string $route, array $parameters=[], bool $only_trying = false): Transaction - { - $this->request($method, $route, $parameters); - - $transaction = $this->getLastTransaction(); - - if (! $only_trying) { - $this->assertTransactionSuccess($transaction); - } - - return $transaction; - } - + /** @var array PollStringUuid => Array of invitation-like associative array */ + public $invitations = []; - /** - * Send a request to our API. - * Tailored for compatibility with API Platform. - * - * @param string $method Where are the constants for this? - * @param string $route - * @param null $content - * @param array $parameters - * @param bool $only_trying - * @return Transaction - */ - public function api(string $method, string $route, $content=null, array $parameters=[], bool $only_trying = false): Transaction + public function addInvitation($invitation, Poll $poll) { - $server = []; -// $server['HTTP_ACCEPT'] = "application/*json*;q=0.9,text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.7"; - $server['HTTP_ACCEPT'] = "application/json"; - $server['CONTENT_TYPE'] = "application/json"; + $pollId = $poll->getUuid()->toString(); - $route = $this->prefixRoute($route); - - $content = json_encode($content, JSON_PRETTY_PRINT); - - $this->request($method, $route, $parameters, [], $server, $content); - - $transaction = $this->getLastTransaction(); - - if ( ! $only_trying) { - $this->assertTransactionSuccess($transaction); - } - - return $transaction; - } - - - /** - * Actually request something, and log the transaction. - * This does not care about the status of the response, it will log it just like a successful one. - * Internal tool. That's why it's not public. Not adamant on this, make it public if you want to. - * - * @param string $method - * @param string $uri You may provide only the route portion of the URI, when testing the the local kernel. - * @param array $parameters - * @param array $files - * @param array $server - * @param null $content - * @param bool $changeHistory - * @return Transaction - */ - protected function request($method, string $uri, array $parameters = [], - array $files = [], array $server = [], - $content = null, $changeHistory = true) - { - if ( ! empty($this->user)) { - $server['PHP_AUTH_USER'] = $this->getUser()->getUsername(); - $server['PHP_AUTH_PW'] = $this->getPassword(); + if ( ! isset($this->invitations[$pollId])) { + $this->invitations[$pollId] = []; } -// $server['HTTP_ACCEPT'] = "application/*json*;q=0.9,text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.7"; -// $server['CONTENT_TYPE'] = "application/json"; - -// $parameters['_format'] = 'txt'; -// $parameters['error_format'] = 'txt'; // for readable error responses - - $crawler = $this->getClient()->request( - $method, $uri, $parameters, $files, - $server, $content, $changeHistory - ); - - $transaction = new Transaction( - $this->getClient()->getRequest(), - $this->getClient()->getResponse() - ); - $this->addTransaction($transaction); - - return $transaction; + $this->invitations[$pollId][] = $invitation; } - - public function printRequest($request = null) + public function countInvitations(?Poll $poll = null) : int { - if (null == $request) { - $request = $this->getLastTransaction()->getRequest(); - } - - CliPrinter::printRequest($request); - } + assert(null === $poll); // todo: filter per poll - - public function printResponse($response = null) - { - if (null == $response) { - $response = $this->getLastTransaction()->getResponse(); + $amount = 0; + foreach ($this->invitations as $pollId => $invitations) { + $amount += count($invitations); } - CliPrinter::printResponse($response); + return $amount; } - public function printTransaction($transaction = null) + public function getInvitationByNumber($index) { - //dump($transaction); - if (null == $transaction) { - $transaction = $this->getLastTransaction(); - } - - $this->printRequest($transaction->getRequest()); - $this->printResponse($transaction->getResponse()); - } - - - public function printLastTransactions($count) - { - $transactions = $this->getLastTransactions($count); - -// dump($transactions[0]->getRequest()->get('variables')); -// dump($transactions[1]->getRequest()->get('variables')); - - foreach ($transactions as $transaction) { - $this->printTransaction($transaction); - print("\n\n"); - } - } - - - /** - * Assert that the provided transaction was a success. - * - * @param Transaction $transaction - */ - public function assertTransactionSuccess(Transaction $transaction) - { - $failure = $this->getPossibleFailureFromTransaction($transaction); - - if (null != $failure) { - $this->printTransaction($transaction); - throw new AssertionFailedError($failure); - } - } - - - /** - * Assert that the provided transaction was a failure. - * - * @param Transaction $transaction - */ - public function assertTransactionFailure(Transaction $transaction) - { - $failure = $this->getPossibleFailureFromTransaction($transaction); - - if (null == $failure) { - $this->printTransaction($transaction); - throw new AssertionFailedError("Expected a failure, but it appears to be a success."); - } - } - - - protected function getPossibleFailureFromTransaction(Transaction $transaction) : ?string - { - $failure = null; - $response = $transaction->getResponse(); - - if ( ! $response->isSuccessful()) { - $statusCode = $response->getStatusCode(); - $failure = sprintf("Response is unsuccessful, with '%d' HTTP status code.".PHP_EOL, $statusCode); - } else { - // Our GraphQL bundle does not respect the HTTP status codes. - // It sends back a 200 even if the query is plain wrong and a 400 is the correct response. - // Not sure if relevant to GraphQL or the bundle implementation. - // If it's the bundle, patch it somehow and then we can remove this. - // https://github.com/overblog/GraphQLBundle/issues/86 …sigh. - $failure = $this->getPossibleFailureFromPossibleGqlResponse($response); - } - - return $failure; - } - - - protected function getPossibleFailureFromPossibleGqlResponse(Response $response) - { - $failure = null; - - try { - $content = $response->getContent(); - - //$content = json_decode($content, true, 512, JSON_THROW_ON_ERROR); - $content = json_decode($content, true, 512); - if (null === $content) { - throw new JsonException("Cannot decode response content as JSON."); - } - - if (isset($content['errors'])) { - $failure = "The transaction dumped above was a failure, "; - $failure .= "even if the HTTP status code seems OK. (GraphQL >.<)".PHP_EOL; - $failure .= "Errors:".PHP_EOL; - foreach ($content['errors'] as $error) { - $failure .= "- ".$error['message']; - if (isset($error['debugMessage'])) { - $failure .= " : ".$error['debugMessage']; - } - $failure .= PHP_EOL; + assert($index > 0); + $current = 1; + foreach ($this->invitations as $invitations) { + // inefficient, can be optimized + foreach ($invitations as $invitation) { + if ($current == $index) { + return $invitation; } + $current++; } + } - } catch (JsonException $e) {} - - return $failure; + return null; } -} - -// Oddly enough this is not defined on my PHP setup -class JsonException extends \Exception {} +} \ No newline at end of file diff --git a/src/Features/ApiActor.php b/src/Features/ApiActor.php new file mode 100644 index 0000000000000000000000000000000000000000..a08ac2e142d19466e2e7b6c2cb8126221f33f0b5 --- /dev/null +++ b/src/Features/ApiActor.php @@ -0,0 +1,437 @@ +<?php + + +namespace App\Features; + + +//use App\Entity\Citizen; +//use FOS\UserBundle\Model\User; +use App\Entity\User; +use GuzzleHttp\Exception\TransferException; +use phpDocumentor\Reflection\Types\Object_; +use PHPUnit\Framework\AssertionFailedError; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Bundle\FrameworkBundle\Client as RestClient; +use function DeepCopy\deep_copy; + + +/** + * Feature: Do things and remember them + * In order to do things and remember them + * As an Actor in the Gherkin scenarios + * I need an API client and a transaction log + * + * This usually represents "I" in the steps. + * + * Class ApiActor + * @package App\Features + */ +class ApiActor +{ + + /** + * A HTTP Client for the REST API. + * Perhaps this dependency should be asked for in the constructor? + * Right now it is injected by the Actor factory code in BaseFeatureContext#actor() + * + * @var RestClient + */ + protected $client; + + + /** + * A log of transactions in the order they were accomplished, ie. most recent last. + * + * @var Transaction[] + */ + protected $transactions = []; + + + protected $api_prefix = '/api'; + + + /** + * @var User + */ + protected $user; + + + /** + * Since we can't recover the password from User#getPlainPassword when we need to, + * we store the password "on a post-it on the side of the screen", in this variable. + * + * @var string + */ + protected $password; + + + /** + * @return RestClient + */ + public function getClient(): RestClient + { + return $this->client; + } + + + /** + * @param RestClient $client + */ + public function setClient(RestClient $client): void + { + $this->client = $client; + } + + + /** + * @return Transaction[] + */ + public function getTransactions(): array + { + return $this->transactions; + } + + + /** + * @param Transaction $transaction + * @return Actor + */ + public function addTransaction(Transaction $transaction): self + { + $this->transactions[] = $transaction; + + return $this; + } + + + /** + * @return Transaction + * @throws ActorConfusion + */ + public function getLastTransaction(): Transaction + { + $transactionsCount = count($this->getTransactions()); + if (1 > $transactionsCount) { + throw new ActorConfusion( + "Trying to read the latest transaction but none happened.\n". + "Try to request something first?" + ); + } + + return $this->transactions[$transactionsCount-1]; + } + + + /** + * @return Transaction[] + * @throws ActorConfusion + */ + public function getLastTransactions($count): array + { + $transactionsCount = count($this->getTransactions()); + if ($count > $transactionsCount) { + throw new ActorConfusion( + "Trying to read the latest $count transactions but only $transactionsCount happened.\n" + ); + } +// return $this->getTransactions(); + return array_slice($this->transactions, -1 * $count); + } + + + /** + * @return User + */ + public function getUser(): User + { + return $this->user; + } + + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + + /** + * @param string $password + */ + public function setPassword(string $password): void + { + $this->password = $password; + } + + + /** + * @return string + */ + public function getApiPrefix(): string + { + return $this->api_prefix; + } + + + /** + * @param string $api_prefix + */ + public function setApiPrefix(string $api_prefix): void + { + $this->api_prefix = $api_prefix; + } + + + /** + * @param $route + * @return string + */ + protected function prefixRoute($route) + { + return sprintf('%s/%s', $this->getApiPrefix(), ltrim(trim($route), '/')); + } + + + /** + * Send a request to our API. + * Tailored for compatibility with API Platform. + * + * @param string $method Where are the constants for this? + * @param string $route + * @param null $content + * @param array $parameters + * @param bool $only_trying + * @return Transaction + */ + public function api(string $method, string $route, $content=null, array $parameters=[], bool $only_trying = false): Transaction + { + $server = []; +// $server['HTTP_ACCEPT'] = "application/*json*;q=0.9,text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.7"; + $server['HTTP_ACCEPT'] = "application/ld+json"; + $server['CONTENT_TYPE'] = "application/ld+json"; + + $route = $this->prefixRoute($route); + + $content = json_encode($content, JSON_PRETTY_PRINT); + + $this->request($method, $route, $parameters, [], $server, $content); + + $transaction = $this->getLastTransaction(); + + if ( ! $only_trying) { + $this->assertTransactionSuccess($transaction); + } + + $this->assertTransactionNotServerFailure($transaction); + + return $transaction; + } + + + /** + * Actually request something, and log the transaction. + * This does not care about the status of the response, it will log it just like a successful one. + * Internal tool. That's why it's not public. Not adamant on this, make it public if you want to. + * + * @param string $method + * @param string $uri You may provide only the route portion of the URI, when testing the the local kernel. + * @param array $parameters + * @param array $files + * @param array $server + * @param null $content + * @param bool $changeHistory + * @return Transaction + */ + protected function request($method, string $uri, array $parameters = [], + array $files = [], array $server = [], + $content = null, $changeHistory = true) + { + if ( ! empty($this->user)) { + $server['PHP_AUTH_USER'] = $this->getUser()->getUsername(); + $server['PHP_AUTH_PW'] = $this->getPassword(); + } + +// $server['HTTP_ACCEPT'] = "application/*json*;q=0.9,text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.7"; +// $server['CONTENT_TYPE'] = "application/json"; + +// $parameters['_format'] = 'txt'; +// $parameters['error_format'] = 'txt'; // for readable error responses + + $crawler = $this->getClient()->request( + $method, $uri, $parameters, $files, + $server, $content, $changeHistory + ); + + $transaction = new Transaction( + $this->getClient()->getRequest(), + $this->getClient()->getResponse() + ); + $this->addTransaction($transaction); + + return $transaction; + } + + + public function printRequest($request = null) + { + if (null == $request) { + $request = $this->getLastTransaction()->getRequest(); + } + + CliPrinter::printRequest($request); + } + + + public function printResponse($response = null) + { + if (null == $response) { + $response = $this->getLastTransaction()->getResponse(); + } + + CliPrinter::printResponse($response); + } + + + public function printTransaction($transaction = null) + { + //dump($transaction); + if (null == $transaction) { + $transaction = $this->getLastTransaction(); + } + + $this->printRequest($transaction->getRequest()); + $this->printResponse($transaction->getResponse()); + } + + + public function printLastTransactions($count) + { + $transactions = $this->getLastTransactions($count); + +// dump($transactions[0]->getRequest()->get('variables')); +// dump($transactions[1]->getRequest()->get('variables')); + + foreach ($transactions as $transaction) { + $this->printTransaction($transaction); + print("\n\n"); + } + } + + + /** + * Assert that the provided transaction was a success. + * + * @param Transaction $transaction + */ + public function assertTransactionSuccess(Transaction $transaction) + { + $failure = $this->getPossibleFailureFromTransaction($transaction); + + if (null != $failure) { + $this->printTransaction($transaction); + throw new AssertionFailedError($failure); + } + } + + + /** + * Assert that the provided transaction was a failure. + * + * @param Transaction $transaction + */ + public function assertTransactionFailure(Transaction $transaction) + { + $failure = $this->getPossibleFailureFromTransaction($transaction); + + if (null == $failure) { + $this->printTransaction($transaction); + throw new AssertionFailedError("Expected a failure, but it appears to be a success."); + } + } + + + /** + * Assert that the provided transaction was not a server failure. + * + * @param Transaction $transaction + */ + public function assertTransactionNotServerFailure(Transaction $transaction) + { + $statusCode = $transaction->getResponse()->getStatusCode(); + + if ($statusCode >= 500 && $statusCode < 600) { + $this->printTransaction($transaction); + throw new AssertionFailedError(sprintf( + "Server failed with '%d' HTTP status code.".PHP_EOL, $statusCode + )); + } + } + + + protected function getPossibleFailureFromTransaction(Transaction $transaction) : ?string + { + $failure = null; + $response = $transaction->getResponse(); + + if ( ! $response->isSuccessful()) { + $statusCode = $response->getStatusCode(); + $failure = sprintf("Response is unsuccessful, with '%d' HTTP status code.".PHP_EOL, $statusCode); + } else { + // Our GraphQL bundle does not respect the HTTP status codes. + // It sends back a 200 even if the query is plain wrong and a 400 is the correct response. + // Not sure if relevant to GraphQL or the bundle implementation. + // If it's the bundle, patch it somehow and then we can remove this. + // https://github.com/overblog/GraphQLBundle/issues/86 …sigh. + $failure = $this->getPossibleFailureFromPossibleGqlResponse($response); + } + + return $failure; + } + + + protected function getPossibleFailureFromPossibleGqlResponse(Response $response) + { + $failure = null; + + try { + $content = $response->getContent(); + + //$content = json_decode($content, true, 512, JSON_THROW_ON_ERROR); + $content = json_decode($content, true, 512); + if (null === $content) { + throw new JsonException("Cannot decode response content as JSON."); + } + + if (isset($content['errors'])) { + $failure = "The transaction dumped above was a failure, "; + $failure .= "even if the HTTP status code seems OK. (GraphQL >.<)".PHP_EOL; + $failure .= "Errors:".PHP_EOL; + foreach ($content['errors'] as $error) { + $failure .= "- ".$error['message']; + if (isset($error['debugMessage'])) { + $failure .= " : ".$error['debugMessage']; + } + $failure .= PHP_EOL; + } + } + + } catch (JsonException $e) {} + + return $failure; + } +} + + +// Oddly enough this is not defined on my PHP setup +class JsonException extends \Exception {} diff --git a/src/Features/Transaction.php b/src/Features/Transaction.php index 53eabc739416dc88eeb370df37da42895aa124d8..ed6482b0c5bdecde43dbb6bdc425d7b74b9ea3ad 100644 --- a/src/Features/Transaction.php +++ b/src/Features/Transaction.php @@ -78,4 +78,12 @@ class Transaction $this->response = $response; } + public function getResponseJson() + { + $content = $this->response->getContent(); + $json = json_decode($content, true, 16, JSON_OBJECT_AS_ARRAY); + + return $json; + } + } \ No newline at end of file diff --git a/src/Form/RegistrationType.php b/src/Form/RegistrationType.php deleted file mode 100644 index 00f1507cbb3a80e52cfde1efdbe9754bb119b1b9..0000000000000000000000000000000000000000 --- a/src/Form/RegistrationType.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Form; - -use MsgPhp\User\Infrastructure\Form\Type\HashedPasswordType; -use MsgPhp\User\Infrastructure\Validator\UniqueUsername; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\PasswordType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Validator\Constraints\Email; -use Symfony\Component\Validator\Constraints\Length; -use Symfony\Component\Validator\Constraints\NotBlank; - -/** - * IMPORTANT NOTE - * HashedPasswordType expects password to ba an associative array like so: ['plain'=>'$3çREt!'} - * - * Class RegistrationType - * @package App\Form - */ -final class RegistrationType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $builder->add('email', EmailType::class, [ - 'constraints' => [ - new NotBlank(), - new Email(), - new UniqueUsername(), - ], - ]); - $builder->add('password', HashedPasswordType::class, [ - 'password_options' => [ - 'constraints' => [ - new NotBlank(), - new Length(['max'=>1024]), - ], - ], - ]); - } -} diff --git a/src/Handler/BallotHandler.php b/src/Handler/BallotHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..9a1f1d6f173162da0f6a1d91bead47b4c0c1909e --- /dev/null +++ b/src/Handler/BallotHandler.php @@ -0,0 +1,32 @@ +<?php + + +namespace App\Handler; + + +use App\Entity\Poll; +use App\Entity\Poll\Proposal; +use App\Entity\Poll\Proposal\Ballot; +use App\Entity\User; + + +/** + * Exists to patch https://github.com/api-platform/docs/issues/504 + * Subresources should handle POST. Since they don't yet, we do this "by hand". + * + * Class PollProposalVoteHandler + * @package App\Handler + */ +class BallotHandler +{ + + public function handleVote(Ballot $vote, User $judge, Proposal $proposal, Poll $poll) + { +// $vote->setProposal($proposal); + // Instead we use the inverse, it sets both + $proposal->addBallot($vote); + $vote->setElector($judge); + return $vote; + } + +} \ No newline at end of file diff --git a/src/Repository/PollGradeRepository.php b/src/Repository/PollGradeRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..9934e741fad1256d782402c14b746f534e9caffd --- /dev/null +++ b/src/Repository/PollGradeRepository.php @@ -0,0 +1,50 @@ +<?php + +namespace App\Repository; + +use App\Entity\Grade; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Common\Persistence\ManagerRegistry; + +/** + * @method Grade|null find($id, $lockMode = null, $lockVersion = null) + * @method Grade|null findOneBy(array $criteria, array $orderBy = null) + * @method Grade[] findAll() + * @method Grade[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PollGradeRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Grade::class); + } + + // /** + // * @return Grade[] Returns an array of Grade objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('p.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Grade + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/PollInvitationRepository.php b/src/Repository/PollInvitationRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..c5481b4d2a8836e9d2932d41792597c8a144a1c1 --- /dev/null +++ b/src/Repository/PollInvitationRepository.php @@ -0,0 +1,65 @@ +<?php + +namespace App\Repository; + +use App\Entity\Poll; +use App\Entity\Poll\Invitation; +use App\Entity\User; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Common\Persistence\ManagerRegistry; + +/** + * @method Invitation|null find($id, $lockMode = null, $lockVersion = null) + * @method Invitation|null findOneBy(array $criteria, array $orderBy = null) + * @method Invitation|null findOneByUuid($invitationId) + * @method Invitation[] findAll() + * @method Invitation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PollInvitationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Invitation::class); + } + + public function findInvitationForUserOnPoll(User $user, Poll $poll) : ?Invitation + { + $invitation = $this->findOneBy( + [ + 'participant' => $user, + 'poll' => $poll, + ] + ); + + return $invitation; + } + + // /** + // * @return Invitation[] Returns an array of Invitation objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Invitation + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/PollCandidateVoteRepository.php b/src/Repository/PollProposalBallotRepository.php similarity index 57% rename from src/Repository/PollCandidateVoteRepository.php rename to src/Repository/PollProposalBallotRepository.php index 78de0e17916bd1b9d40f21b5ec47cb51b7827055..107f8c42322f67d735d40ef25124de18416b5975 100644 --- a/src/Repository/PollCandidateVoteRepository.php +++ b/src/Repository/PollProposalBallotRepository.php @@ -3,37 +3,37 @@ namespace App\Repository; use App\Entity\Poll; -use App\Entity\PollCandidateVote; +use App\Entity\Poll\Proposal\Ballot; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Common\Persistence\ManagerRegistry; /** - * @method PollCandidateVote|null find($id, $lockMode = null, $lockVersion = null) - * @method PollCandidateVote|null findOneBy(array $criteria, array $orderBy = null) - * @method PollCandidateVote[] findAll() - * @method PollCandidateVote[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @method Ballot|null find($id, $lockMode = null, $lockVersion = null) + * @method Ballot|null findOneBy(array $criteria, array $orderBy = null) + * @method Ballot[] findAll() + * @method Ballot[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class PollCandidateVoteRepository extends ServiceEntityRepository +class PollProposalBallotRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, PollCandidateVote::class); + parent::__construct($registry, Ballot::class); } public function countVotesOnPoll(Poll $poll) { $count = 0; - foreach ($poll->getCandidates() as $candidate) { + foreach ($poll->getProposals() as $proposal) { $count += $this->count([ - 'candidate' => $candidate->getId(), + 'proposal' => $proposal->getId(), ]); } return $count; } // /** - // * @return PollCandidateVote[] Returns an array of PollCandidateVote objects + // * @return Ballot[] Returns an array of Ballot objects // */ /* public function findByExampleField($value) @@ -50,7 +50,7 @@ class PollCandidateVoteRepository extends ServiceEntityRepository */ /* - public function findOneBySomeField($value): ?PollCandidateVote + public function findOneBySomeField($value): ?Ballot { return $this->createQueryBuilder('l') ->andWhere('l.exampleField = :val') diff --git a/src/Repository/PollCandidateRepository.php b/src/Repository/PollProposalRepository.php similarity index 57% rename from src/Repository/PollCandidateRepository.php rename to src/Repository/PollProposalRepository.php index 14245b889c7d6d377386a0d383fe0baeed7bca42..7ff56b7d7e546b1afd28f775085b04be3b840061 100644 --- a/src/Repository/PollCandidateRepository.php +++ b/src/Repository/PollProposalRepository.php @@ -2,25 +2,26 @@ namespace App\Repository; -use App\Entity\PollCandidate; +use App\Entity\Poll\Proposal; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Common\Persistence\ManagerRegistry; /** - * @method PollCandidate|null find($id, $lockMode = null, $lockVersion = null) - * @method PollCandidate|null findOneBy(array $criteria, array $orderBy = null) - * @method PollCandidate[] findAll() - * @method PollCandidate[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @method Proposal|null find($id, $lockMode = null, $lockVersion = null) + * @method Proposal|null findOneBy(array $criteria, array $orderBy = null) + * @method Proposal|null findOneByUuid($proposalId) + * @method Proposal[] findAll() + * @method Proposal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class PollCandidateRepository extends ServiceEntityRepository +class PollProposalRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, PollCandidate::class); + parent::__construct($registry, Proposal::class); } // /** - // * @return PollCandidate[] Returns an array of PollCandidate objects + // * @return Proposal[] Returns an array of Proposal objects // */ /* public function findByExampleField($value) @@ -37,7 +38,7 @@ class PollCandidateRepository extends ServiceEntityRepository */ /* - public function findOneBySomeField($value): ?PollCandidate + public function findOneBySomeField($value): ?Proposal { return $this->createQueryBuilder('l') ->andWhere('l.exampleField = :val') diff --git a/src/Repository/PollRepository.php b/src/Repository/PollRepository.php index 4ab71ad14147662fe7ed12958b52ae7285d7920b..b3a4908104e4320b499218b325f34579f1d25672 100644 --- a/src/Repository/PollRepository.php +++ b/src/Repository/PollRepository.php @@ -9,6 +9,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; /** * @method Poll|null find($id, $lockMode = null, $lockVersion = null) * @method Poll|null findOneBy(array $criteria, array $orderBy = null) + * @method Poll|null findOneByUuid($pollId) * @method Poll[] findAll() * @method Poll[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..e7ec2f8a98a315d85bb0890af4572b522d2d67fa --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,63 @@ +<?php + +namespace App\Repository; + +use App\Entity\User; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Common\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; + +/** + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository implements UserLoaderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + /** + * @inheritdoc + */ + public function loadUserByUsername($usernameOrEmail) + { + return $this->createQueryBuilder('u') + ->where('u.username = :query or u.email = :query or u.uuid = :query') + ->setParameter('query', $usernameOrEmail) + ->getQuery() + ->getOneOrNullResult(); + } + + // /** + // * @return User[] Returns an array of User objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('u') + ->andWhere('u.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('u.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?User + { + return $this->createQueryBuilder('u') + ->andWhere('u.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Security/PollBouncer.php b/src/Security/Authorization/PollVoter.php similarity index 66% rename from src/Security/PollBouncer.php rename to src/Security/Authorization/PollVoter.php index fd3f1775c539d1c0a52c2c391ec37b0779db979f..5ee0db779254eed4a6458bd4407e1b311c47c798 100644 --- a/src/Security/PollBouncer.php +++ b/src/Security/Authorization/PollVoter.php @@ -1,24 +1,24 @@ <?php - - -namespace App\Security; - +namespace App\Security\Authorization; use App\Application; use App\Entity\Poll; -use App\Repository\PollCandidateVoteRepository; +use App\Entity\User; +use App\Repository\PollProposalBallotRepository; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; - /** - * This bouncer's job is to tell who can get in and who can't. + * Note: this is not a Voter in the political sense. + * The word Voter comes from the Symfony ecosystem, and means … well… bouncer, basically. + * + * This bouncer's job is to tell which Users may edit which Polls. * - * Class Bouncer - * @package App\Security + * Class PollVoter + * @package App\Security\Authorization */ -class PollBouncer extends Voter +class PollVoter extends Voter { const CAN_DELETE = "can_delete"; @@ -33,7 +33,7 @@ class PollBouncer extends Voter private $app; /** - * @var PollCandidateVoteRepository + * @var PollProposalBallotRepository */ private $voteRepository; @@ -41,35 +41,17 @@ class PollBouncer extends Voter /** * Bouncer constructor. * @param Application $app + * @param PollProposalBallotRepository $voteRepository * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct( Application $app, - PollCandidateVoteRepository $voteRepository, + PollProposalBallotRepository $voteRepository, AuthorizationCheckerInterface $authorizationChecker ) { - $this->authorizationChecker = $authorizationChecker; $this->app = $app; $this->voteRepository = $voteRepository; - } - - public function canCurrentUserEditPoll(Poll $poll, string $password='') : bool - { - if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) { - return true; - } - - if ('' == $password) { - if ( ! $this->authorizationChecker->isGranted('ROLE_USER')) { - return false; - } else { - - } - } else { - // TODO - } - - return false; + $this->authorizationChecker = $authorizationChecker; } /** @@ -82,7 +64,7 @@ class PollBouncer extends Voter */ protected function supports($attribute, $subject) { - if ( ! $subject instanceof Poll) { + if ( ! ($subject instanceof Poll)) { return false; } @@ -98,29 +80,32 @@ class PollBouncer extends Voter * It is safe to assume that $attribute and $subject already passed the "supports()" method check. * * @param string $attribute - * @param mixed $subject + * @param Poll $subject * * @return bool */ protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { - $roles = $token->getRoleNames(); - if (self::CAN_DELETE == $attribute) { - if ($subject instanceof Poll) { - if (in_array('ROLE_ADMIN', $roles)) { + $user = $token->getUser(); + if ( ! ($user instanceof User)) { + return false; + } + + switch ($attribute) { + case self::CAN_DELETE: + if (in_array('ROLE_ADMIN', $user->getRoles())) { return true; } if (0 < $this->voteRepository->countVotesOnPoll($subject)) { return false; } - - if ($subject->getAuthor() === $this->app->getAuthenticatedUser()) { + if ($subject->getAuthor() === $user) { return true; } - } + break; } return false; } -} \ No newline at end of file +} diff --git a/src/Swagger/JwtDecorator.php b/src/Swagger/JwtDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..5a57e49015a7102f2318c1684bb60e73aee08f39 --- /dev/null +++ b/src/Swagger/JwtDecorator.php @@ -0,0 +1,83 @@ +<?php + +declare(strict_types=1); + +namespace App\Swagger; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +final class JwtDecorator implements NormalizerInterface +{ + private NormalizerInterface $decorated; + + public function __construct(NormalizerInterface $decorated) + { + $this->decorated = $decorated; + } + + public function supportsNormalization($data, $format = null) + { + return $this->decorated->supportsNormalization($data, $format); + } + + public function normalize($object, $format = null, array $context = []) + { + $docs = $this->decorated->normalize($object, $format, $context); + + $docs['components']['schemas']['Token'] = [ + 'type' => 'object', + 'properties' => [ + 'token' => [ + 'type' => 'string', + 'readOnly' => true, + ], + ], + ]; + + $docs['components']['schemas']['Credentials'] = [ + 'type' => 'object', + 'properties' => [ + 'usernameOrEmail' => [ + 'type' => 'string', + 'example' => 'api', + ], + 'password' => [ + 'type' => 'string', + 'example' => 'api', + ], + ], + ]; + + $tokenDocumentation = [ + 'paths' => [ + '/api/_jwt' => [ + 'post' => [ + 'tags' => ['Token'], + 'operationId' => 'postCredentialsItem', + 'summary' => 'Gets a JWT token to login', + 'parameters' => [ + [ + 'name' => 'Credentials', + 'in' => 'body', + 'description' => 'Create new JWT Token', + 'schema' => [ + '$ref' => '#/components/schemas/Credentials', + ], + ], + ], + 'responses' => [ + Response::HTTP_OK => [ + 'description' => 'Get JWT token', + 'schema' => [ + '$ref' => '#/components/schemas/Token', + ], + ], + ], + ], + ], + ], + ]; + return array_merge_recursive($docs, $tokenDocumentation); + } +} \ No newline at end of file diff --git a/src/Tally/Bot/StandardTallyBot.php b/src/Tally/Bot/StandardTallyBot.php index f57929f63179c1177c9400f0159f2c8a0154cd6c..8bce57ffceecf295452ee003d2cbe977b74cfa7f 100644 --- a/src/Tally/Bot/StandardTallyBot.php +++ b/src/Tally/Bot/StandardTallyBot.php @@ -6,18 +6,18 @@ namespace App\Tally\Bot; use App\Application; use App\Entity\Poll; -use App\Entity\PollCandidateVote; -use App\Repository\PollCandidateRepository; -use App\Repository\PollCandidateVoteRepository; -use App\Tally\Output\PollCandidateTally; +use App\Entity\Poll\Proposal\Ballot; +use App\Repository\PollProposalRepository; +use App\Repository\PollProposalBallotRepository; +use App\Tally\Output\PollProposalTally; use App\Tally\Output\PollTally; /** * Not sure about the name "standard". + * Pretty inefficient algorithm. * - * - Sum direct votes - * - Sum delegated votes without entropy + * Sums direct votes. * * Class StandardTallyBot * @package App\Tally\Bot @@ -31,98 +31,100 @@ class StandardTallyBot implements TallyBotInterface protected $application; /** - * @var PollCandidateRepository + * @var PollProposalRepository */ - protected $limajuPollCandidateRepository; + protected $pollProposalRepository; /** - * @var PollCandidateVoteRepository + * @var PollProposalBallotRepository */ - protected $limajuPollCandidateVoteRepository; + protected $pollProposalVoteRepository; /** * StandardTallyBot constructor. - * @param PollCandidateRepository $limajuPollCandidateRepository - * @param PollCandidateVoteRepository $limajuPollCandidateVoteRepository + * @param PollProposalRepository $pollProposalRepository + * @param PollProposalBallotRepository $pollProposalVoteRepository * @param Application $application */ public function __construct( - PollCandidateRepository $limajuPollCandidateRepository, - PollCandidateVoteRepository $limajuPollCandidateVoteRepository, - Application $application) + PollProposalRepository $pollProposalRepository, + PollProposalBallotRepository $pollProposalVoteRepository, + Application $application + ) { $this->application = $application; - $this->limajuPollCandidateRepository = $limajuPollCandidateRepository; - $this->limajuPollCandidateVoteRepository = $limajuPollCandidateVoteRepository; + $this->pollProposalRepository = $pollProposalRepository; + $this->pollProposalVoteRepository = $pollProposalVoteRepository; } /** * @inheritDoc */ - public function tallyVotesOnLimajuPoll(Poll $poll): PollTally + public function tallyVotesOnPoll(Poll $poll): PollTally { - /** @var PollCandidateTally[] $candidatesTallies */ - $candidatesTallies = array(); - - $positions = (new PollCandidateTally())->getMentionsPositions(); + /** @var PollProposalTally[] $proposalsTallies */ + $proposalsTallies = array(); + $defaultGrade = $poll->getDefaultGradeName(); + $levelOfGrade = $poll->getLevelsOfGrades(); $maxVotesCount = 0; // First loop: collect data - foreach ($poll->getCandidates() as $candidate) { + foreach ($poll->getProposals() as $proposal) { - $votes = $this->limajuPollCandidateVoteRepository->findBy([ - 'candidate' => $candidate->getId(), + $votes = $this->pollProposalVoteRepository->findBy([ + 'proposal' => $proposal->getId(), ]); $votesCount = count($votes); $maxVotesCount = max($maxVotesCount, $votesCount); - $mentionsTally = array(); // MENTION_XXX => integer - - if ($votesCount) { + $gradesTally = array(); // grade_name => integer - usort($votes, function(PollCandidateVote $a, PollCandidateVote $b) use ($positions) { - return $positions[$a->getMention()] - $positions[$b->getMention()]; - }); +// if ($votesCount) { - foreach ($positions as $mentionToTally => $whoCares) { - $votesForMention = array_filter($votes, function(PollCandidateVote $v) use ($mentionToTally) { - return $v->getMention() === $mentionToTally; - }); - $mentionsTally[$mentionToTally] = count($votesForMention); - } + usort($votes, function (Ballot $a, Ballot $b) use ($levelOfGrade) { + return $levelOfGrade[$a->getGrade()] - $levelOfGrade[$b->getGrade()]; + }); + foreach ($levelOfGrade as $gradeToTally => $whoCares) { + $votesForMention = array_filter($votes, function (Ballot $v) use ($gradeToTally) { + return $v->getGrade() === $gradeToTally; + }); + $gradesTally[$gradeToTally] = count($votesForMention); } - $candidateTally = new PollCandidateTally(); - $candidateTally->setPollCandidateId($candidate->getId()); - $candidateTally->setMentionsTally($mentionsTally); +// } + + $proposalTally = new PollProposalTally(); + $proposalTally->setPollProposalId($proposal->getUuid()); + $proposalTally->setGradesNames($poll->getGradesNames()); + $proposalTally->setGradesTally($gradesTally); // Setting these later once we have all the tallies - //$candidateTally->setMention(?); - //$candidateTally->setPosition(?); + //$proposalTally->setMention(?); + //$proposalTally->setPosition(?); - $candidatesTallies[] = $candidateTally; + $proposalsTallies[] = $proposalTally; } // Second loop: equalize the votes count and compute the mention - foreach ($candidatesTallies as $candidateTally) { - // Fill up candidate tallies that have less votes, with TO_REJECT mentions + foreach ($proposalsTallies as $proposalTally) { + // Fill up proposal tallies that have less votes, with TO_REJECT mentions // so that all tallies have the same number of mentions in the end. // The goal here is to enforce the Rule about TO_REJECT being the default mention. - $candidateTally->addVotesForMention($maxVotesCount - $candidateTally->countVotes(), Poll::MENTION_TO_REJECT); + $proposalTally->addVotesForGrade($maxVotesCount - $proposalTally->countVotes(), $defaultGrade); // Once this is done, we can now compute the final mention from the median - $candidateTally->setMention($candidateTally->getMedian()); + $proposalTally->setMedianGrade($proposalTally->getMedian()); } - // Sort the candidates using majority judgment on the median - usort($candidatesTallies, function(PollCandidateTally $a, PollCandidateTally $b) use ($positions) { + // Sort the proposals using majority judgment on the median + usort($proposalsTallies, function (PollProposalTally $a, PollProposalTally $b) use ($levelOfGrade) { // From https://en.wikipedia.org/wiki/Majority_judgment - // If more than one candidate has the same highest median-grade, + // If more than one proposal has the same highest median-grade, // the MJ winner is discovered by removing (one-by-one) any grades equal - // in value to the shared median grade from each tied candidate’s total. - // This is repeated until only one of the previously tied candidates + // in value to the shared median grade from each tied proposal’s total. + // This is repeated until only one of the previously tied proposals // is currently found to have the highest median-grade. if ($a->getMedian() === $b->getMedian()) { // We're going to work on copies we can remove votes from. @@ -137,7 +139,7 @@ class StandardTallyBot implements TallyBotInterface // While one may prefer recursive functions for their simplicity, // we're approaching this with a flat loop that should scale RAM usage better. // Of course, it may still blow into infinite loops. Those are the best. - while ( ! ($wipTallyA->isEmpty() && $wipTallyB->isEmpty())) { + while (!($wipTallyA->isEmpty() && $wipTallyB->isEmpty())) { $mentionA = $wipTallyA->getMedian(); $mentionB = $wipTallyB->getMedian(); @@ -145,24 +147,24 @@ class StandardTallyBot implements TallyBotInterface $wipTallyA->removeOneVoteForMention($mentionA); $wipTallyB->removeOneVoteForMention($mentionB); } else { - return $positions[$mentionB] - $positions[$mentionA]; + return $levelOfGrade[$mentionB] - $levelOfGrade[$mentionA]; } } // All the votes were the exact same. Banana condition. - // Right now we're sorting in the order of the candidates, I think. To test. + // Right now we're sorting in the order of the proposals, I think. To test. return 0; } - return $positions[$b->getMedian()] - $positions[$a->getMedian()]; + return $levelOfGrade[$b->getMedian()] - $levelOfGrade[$a->getMedian()]; }); - foreach ($candidatesTallies as $k => $candidateTally) { - // In the future, two candidates may have the same position ; this code will perhaps change. - $candidateTally->setPosition($k+1); + foreach ($proposalsTallies as $k => $proposalTally) { + // In the future, two proposals may have the same position ; this code will perhaps change. + $proposalTally->setRank($k + 1); } - $tally = new PollTally($candidatesTallies); + $tally = new PollTally($proposalsTallies); return $tally; } diff --git a/src/Tally/Bot/TallyBotInterface.php b/src/Tally/Bot/TallyBotInterface.php index 24409fb1fdcb0116aed9f96a9bab8901de01d5ed..1d678ef29045a651a64aaf65dc4366ed5d39408c 100644 --- a/src/Tally/Bot/TallyBotInterface.php +++ b/src/Tally/Bot/TallyBotInterface.php @@ -19,5 +19,5 @@ use App\Tally\Output\PollTally; */ interface TallyBotInterface { - public function tallyVotesOnLimajuPoll(Poll $poll) : PollTally; + public function tallyVotesOnPoll(Poll $poll) : PollTally; } \ No newline at end of file diff --git a/src/Tally/Output/PollCandidateTally.php b/src/Tally/Output/PollProposalTally.php similarity index 52% rename from src/Tally/Output/PollCandidateTally.php rename to src/Tally/Output/PollProposalTally.php index 44830beb5ac5165d8641feab59c35295d92963ce..963c9b868a4c28cdfa9d00c1dde1fc9c4b05db56 100644 --- a/src/Tally/Output/PollCandidateTally.php +++ b/src/Tally/Output/PollProposalTally.php @@ -4,47 +4,48 @@ namespace App\Tally\Output; -use App\Entity\Poll; use Ramsey\Uuid\UuidInterface; -class PollCandidateTally +class PollProposalTally { /** + * Why not pas the poll ? (careful, we `clone` this class) + * * @var UuidInterface - * UUID of the PollCandidate that this tally belongs to. + * UUID of the Proposal that this tally belongs to. */ - public $poll_candidate_id; + public $poll_proposal_id; /** * @var string * Final mention tallied, for example the median mention in the standard tally. * One of Poll::MENTION_XXX */ - public $mention; + public $median_grade; /** * @var integer - * The position of this candidate in its poll, after tallying. - * The position starts at 1 and ends at <NUMBER_OF_CANDIDATES>. - * Two candidates MAY have the same position, in extreme cases. + * The rank of this proposal in its poll, after tallying. + * The rank starts at 1 and usually ends at <AMOUNT_OF_PROPOSALS>. + * Two or more proposals MAY have the same rank, in extreme cases. */ - public $position; + public $rank; /** * @var array - * Poll::MENTION_XXX => integer - * Count of votes for each mention. + * grade_name => integer + * Count of votes for each grade. */ - public $mentions_tally; + public $grades_tally; /** * @var array|string[] - * The list of Poll::MENTION_XXX this tally uses. - * The order matters, and must be from worse to best. + * The list of the names of grades this tally & poll use. + * The order matters, and MUST be from "worse" to "best". */ - protected $mentions_list; + protected $grades_names; // protected $mentions_tree; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -52,65 +53,65 @@ class PollCandidateTally /** * @return UuidInterface */ - public function getPollCandidateId(): UuidInterface + public function getPollProposalId(): UuidInterface { - return $this->poll_candidate_id; + return $this->poll_proposal_id; } /** - * @param UuidInterface $poll_candidate_id + * @param UuidInterface $poll_proposal_id */ - public function setPollCandidateId(UuidInterface $poll_candidate_id): void + public function setPollProposalId(UuidInterface $poll_proposal_id): void { - $this->poll_candidate_id = $poll_candidate_id; + $this->poll_proposal_id = $poll_proposal_id; } /** * @return string */ - public function getMention(): string + public function getMedianGrade(): string { - return $this->mention; + return $this->median_grade; } /** - * @param string $mention + * @param string $median_grade */ - public function setMention(string $mention): void + public function setMedianGrade(string $median_grade): void { - $this->mention = $mention; + $this->median_grade = $median_grade; } /** * @return int */ - public function getPosition(): int + public function getRank(): int { - return $this->position; + return $this->rank; } /** - * @param int $position + * @param int $rank */ - public function setPosition(int $position): void + public function setRank(int $rank): void { - $this->position = $position; + $this->rank = $rank; } /** * @return array */ - public function getMentionsTally(): array + public function getGradesTally(): array { - return $this->mentions_tally; + return $this->grades_tally; } /** - * @param array $mentions_tally + * @param array $grades_tally */ - public function setMentionsTally(array $mentions_tally): void + public function setGradesTally(array $grades_tally): void { - $this->mentions_tally = $mentions_tally; + $this->grades_tally = $grades_tally; } @@ -139,9 +140,9 @@ class PollCandidateTally */ public function getMedian($low=true): string { - $mentions = $this->getMentionsList(); - $order = $this->getMentionsPositions(); - $tally = $this->getMentionsTally(); + $mentions = $this->getGradesNames(); +// $order = $this->getMentionsPositions(); + $tally = $this->getGradesTally(); $count = $this->countVotes(); $median = $mentions[0]; // Worse mention is the default. @@ -170,45 +171,34 @@ class PollCandidateTally /** * @return array|string[] */ - public function getMentionsList() + public function getGradesNames() { - if (null === $this->mentions_list) { - // Let's initialize here the list of mentions. - // What should we do with these? => Inject from Config? - $this->mentions_list = [ - Poll::MENTION_TO_REJECT, - Poll::MENTION_MEDIOCRE, - Poll::MENTION_INADEQUATE, - Poll::MENTION_PASSABLE, - Poll::MENTION_GOOD, - Poll::MENTION_VERY_GOOD, - Poll::MENTION_EXCELLENT, - ]; + if (null === $this->grades_names) { + trigger_error("No grades list."); } - - return $this->mentions_list; + return $this->grades_names; } /** - * @param array|string[] $mentions_list + * @param array|string[] $grades_names */ - public function setMentionsList($mentions_list): void + public function setGradesNames($grades_names): void { - $this->mentions_list = $mentions_list; + $this->grades_names = $grades_names; } /** * Yields the mapping of the mentions to their "worth", an integer between 0 and `mentionsCount-1`. - * Helps when sorting the candidates during tallying. - * + * Helps when sorting the proposals during tallying. + * @deprecated * @return array of MENTION_XXX => N */ public function getMentionsPositions() { - return array_flip($this->getMentionsList()); + return array_flip($this->getGradesNames()); } @@ -220,14 +210,13 @@ class PollCandidateTally public function countVotes(): int { $count = 0; - $tally = $this->getMentionsTally(); + $tally = $this->getGradesTally(); - foreach ($this->getMentionsList() as $mention) { - if (isset($tally[$mention])) { - $count += (int) $tally[$mention]; - } else { + foreach ($this->getGradesNames() as $mention) { + if ( ! isset($tally[$mention])) { trigger_error("Mention `$mention' is not available in the tally.", E_USER_ERROR); } + $count += (int) $tally[$mention]; } return $count; @@ -239,14 +228,14 @@ class PollCandidateTally * Every politician's wet dream that banksters made a reality. * * May be used during tallying, on copies of tallies, - * to help with candidates of similar mentions. + * to help with proposals of similar mentions. * @param $mention - * @return PollCandidateTally + * @return PollProposalTally */ public function removeOneVoteForMention($mention): self { - if ($this->mentions_tally[$mention] > 0) { - $this->mentions_tally[$mention] -= 1; + if ($this->grades_tally[$mention] > 0) { + $this->grades_tally[$mention] -= 1; } return $this; @@ -258,12 +247,12 @@ class PollCandidateTally * * @param int $count * @param $mention - * @return PollCandidateTally + * @return PollProposalTally */ - public function addVotesForMention(int $count, $mention): self + public function addVotesForGrade(int $count, $mention): self { - if (isset($this->mentions_tally[$mention])) { - $this->mentions_tally[$mention] += $count; + if (isset($this->grades_tally[$mention])) { + $this->grades_tally[$mention] += $count; } return $this; diff --git a/src/Tally/Output/PollTally.php b/src/Tally/Output/PollTally.php index c0f6b186178d5817abf6207008f1fd6e533192ec..99a431f8bf40d02314aa64721a458be923982f3b 100644 --- a/src/Tally/Output/PollTally.php +++ b/src/Tally/Output/PollTally.php @@ -12,24 +12,24 @@ namespace App\Tally\Output; class PollTally { /** - * @var array|PollCandidateTally[] + * @var array|PollProposalTally[] */ - public $candidates; + public $proposals; /** * Tally constructor. - * @param array $candidates + * @param array $proposals */ - public function __construct(array $candidates) + public function __construct(array $proposals) { - $this->candidates = $candidates; + $this->proposals = $proposals; } public function countVotes() { $count = 0; - foreach ($this->candidates as $candidate) { - $count += $candidate->countVotes(); + foreach ($this->proposals as $proposal) { + $count += $proposal->countVotes(); } return $count; diff --git a/src/Tally/Output/Tally.php b/src/Tally/Output/Tally.php index 8f4d82f570d7d9ece4b832c21e60f4e974703a98..265dd8b9e29b842d9ce09f86df86a352fe951920 100644 --- a/src/Tally/Output/Tally.php +++ b/src/Tally/Output/Tally.php @@ -5,6 +5,8 @@ namespace App\Tally\Output; /** + * NOT USED + * * A count of the votes. * The value are floats instead of integers because we may have complex tallybots with transitivity friction|entropy. * The purpose of a TallyBot is to generate and output an instance of this. diff --git a/symfony.lock b/symfony.lock index 50cf93bea668eeee82ebc8450cfb219e33f7cce2..87586a5b9616b91cd15e2679c88cbc2d2e266dec 100644 --- a/symfony.lock +++ b/symfony.lock @@ -22,6 +22,9 @@ "behat/gherkin": { "version": "v4.6.0" }, + "behat/mink": { + "version": "v1.8.1" + }, "behat/symfony2-extension": { "version": "2.1", "recipe": { @@ -41,6 +44,9 @@ "behat/transliterator": { "version": "v1.2.0" }, + "behatch/contexts": { + "version": "3.3.0" + }, "container-interop/container-interop": { "version": "1.2.0" }, @@ -131,6 +137,9 @@ "fig/link-util": { "version": "1.0.0" }, + "friends-of-behat/mink-extension": { + "version": "2.3.1" + }, "gedmo/doctrine-extensions": { "version": "v2.4.37" }, @@ -146,37 +155,42 @@ "jdorn/sql-formatter": { "version": "v1.2.17" }, - "league/oauth2-client": { - "version": "2.4.1" - }, - "liip/functional-test-bundle": { - "version": "2.0.0-alpha17" - }, - "monolog/monolog": { - "version": "1.25.1" + "justinrainbow/json-schema": { + "version": "5.2.10" }, - "msgphp/domain": { - "version": "v0.13.0" + "lcobucci/jwt": { + "version": "3.3.3" }, - "msgphp/user": { - "version": "v0.13.0" + "league/oauth2-client": { + "version": "2.4.1" }, - "msgphp/user-bundle": { - "version": "0.10", + "lexik/jwt-authentication-bundle": { + "version": "2.5", "recipe": { - "repo": "github.com/symfony/recipes-contrib", + "repo": "github.com/symfony/recipes", "branch": "master", - "version": "0.10", - "ref": "4067ee796789a8828ec72f82c76508a76875bcf8" + "version": "2.5", + "ref": "5b2157bcd5778166a5696e42f552ad36529a07a6" }, "files": [ - "config/packages/msgphp_user.php", - "src/Entity/User.php" + "config/packages/lexik_jwt_authentication.yaml" ] }, + "liip/functional-test-bundle": { + "version": "2.0.0-alpha17" + }, + "matthewbaggett/clamp": { + "version": "v1.1" + }, + "monolog/monolog": { + "version": "1.25.1" + }, "myclabs/deep-copy": { "version": "1.9.3" }, + "namshi/jose": { + "version": "7.2.3" + }, "nelmio/api-doc-bundle": { "version": "3.0", "recipe": { @@ -383,9 +397,24 @@ "config/bootstrap.php" ] }, + "symfony/css-selector": { + "version": "v4.3.11" + }, "symfony/debug": { "version": "v4.3.4" }, + "symfony/debug-bundle": { + "version": "4.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.1", + "ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea" + }, + "files": [ + "config/packages/dev/debug.yaml" + ] + }, "symfony/dependency-injection": { "version": "v4.3.4" }, @@ -509,6 +538,9 @@ "symfony/polyfill-intl-idn": { "version": "v1.12.0" }, + "symfony/polyfill-intl-normalizer": { + "version": "v1.18.1" + }, "symfony/polyfill-mbstring": { "version": "v1.12.0" }, @@ -521,6 +553,9 @@ "symfony/process": { "version": "v4.3.4" }, + "symfony/profiler-pack": { + "version": "v1.0.5" + }, "symfony/property-access": { "version": "v4.3.4" }, @@ -572,6 +607,9 @@ "symfony/service-contracts": { "version": "v1.1.6" }, + "symfony/stopwatch": { + "version": "v4.3.11" + }, "symfony/templating": { "version": "v4.3.4" }, @@ -630,6 +668,20 @@ "symfony/web-link": { "version": "v4.3.4" }, + "symfony/web-profiler-bundle": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.3", + "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" + }, + "files": [ + "config/packages/dev/web_profiler.yaml", + "config/packages/test/web_profiler.yaml", + "config/routes/dev/web_profiler.yaml" + ] + }, "symfony/web-server-bundle": { "version": "3.3", "recipe": { diff --git a/translations/features.en.yml b/translations/features.en.yml index bd070b27c571f6c7b403fcb86c52f40e24d815a1..b6cc1ef26fdb78918e63ea7669957d7f635eaaab 100644 --- a/translations/features.en.yml +++ b/translations/features.en.yml @@ -8,9 +8,24 @@ keys: poll: subject: 'subject' title: 'title' - options: 'options' proposals: 'proposals' + grades: 'grades' + scope: 'scope' + proposal: + grade: 'grade' + rank: 'rank' + scopes: + public: 'public' + unlisted: 'unlisted' + private: 'private' +values: + scopes: + 'public': 'public' + 'unlisted': 'unlisted' + 'private': 'private' + +# @deprecated value: majority_judgment_poll: mention: @@ -24,7 +39,7 @@ value: 'mediocre': 'malboneta' 'reject': 'malakcepti' - +# @deprecated majority_judgment_poll: mention: # Keys are Poll::MENTION_XXX @@ -49,20 +64,37 @@ testing: test: failure: - candidate_tallies_dont_match: | - Expected mention `{{ expected_mention }}' but the tally got `{{ actual_mention }}' - on candidate `{{ candidate.title }}' ({{ candidate.id }})." - candidate_position_mismatch: | - Expected position `{{ expected_position }}' but the tally got `{{ actual_position }}' - on candidate `{{ candidate.title }}' ({{ candidate.id }})." - candidates_left_unprocessed: | - Some candidates are in the specifications but were never tallied: - {% for candidate in candidates %} - - {{ candidate.title }} ({{ candidate.id }}) + no_poll_proposal_found_for_uuid: + No Proposal with UUID `{{ uuid }}' could be found. + proposal_tallies_dont_match: | + Expected mention `{{ expected_grade }}' but the tally got `{{ actual_grade }}' + on proposal `{{ proposal.title }}' ({{ proposal.id }})." + proposal_rank_mismatch: | + Expected rank `{{ expected_rank }}' but the tally got `{{ actual_rank }}' + on proposal `{{ proposal.title }}' ({{ proposal.id }})." + proposals_left_unprocessed: | + Some proposals are in the specifications but were never tallied: + {% for proposal in proposals %} + - {{ proposal.title }} ({{ proposal.id }}) {% endfor %} - no_work_found_for_title: | - No work titled `{{ title }}' could be found in the database. + poll_has_no_subject: | + The poll has no subject. + Add one with (for example): + subject: "My awesome poll" + poll_has_no_proposal: | + At least one proposal is required. + Add some like this (for example): + {{ proposalsKey }}: + - Jane Doe + - Ann O'nymous + poll_has_no_grades: | + The poll has no grades. At least two grades are required. + Add some like this (for example): + {{ gradesKey }}: + - Reject + - Accept no_majority_judgment_poll_found_for_title: | No majority judgment poll titled `{{ title }}' could be found in the database. - no_majority_judgment_poll_candidate_found_for_title: | - No majority judgment poll candidate titled `{{ title }}' could be found in the database. \ No newline at end of file + no_majority_judgment_poll_proposal_found_for_title: | + No majority judgment poll proposal titled `{{ title }}' could be found in the database + for poll with subject `{{ poll.subject }}' ({{ poll.uuid }}). \ No newline at end of file diff --git a/translations/features.fr.yml b/translations/features.fr.yml index 314aaa22a50e463b38ec8c5b11c9e33be4d79c77..174b9c7f248c73cb612fa1c8e2eb756c56e4e12e 100644 --- a/translations/features.fr.yml +++ b/translations/features.fr.yml @@ -8,9 +8,25 @@ keys: poll: subject: 'sujet' title: 'titre' - candidates: 'candidats' proposals: 'propositions' + grades: 'mentions' + scope: 'accès' + proposal: + grade: 'mention' + # rang ? + rank: 'position' + scopes: + public: 'public' + unlisted: 'non-listé' + private: 'privé' +values: + scopes: + 'public': 'public' + 'non-listé': 'unlisted' + 'privé': 'private' + +# @deprecated value: majority_judgment_poll: mention: @@ -24,6 +40,7 @@ value: 'insuffisant': 'malboneta' 'à rejeter': 'malakcepti' +# @deprecated majority_judgment_poll: mention: # Keys are Poll::MENTION_XXX @@ -40,19 +57,37 @@ test: failure: not_equal: | La valeur obtenue ({{ actual }}) diffère de la valeur attendue ({{ expected }}). - candidate_tallies_dont_match: | - Sur le candidat `{{ candidate.title }}' ({{ candidate.id }}), - on obtient la mention `{{ actual_mention }}' mais la mention attendue est `{{ expected_mention }}'. - candidate_position_mismatch: | - Sur le candidat `{{ candidate.title }}' ({{ candidate.id }}), - on obtient la position `{{ actual_position }}' mais sa position attendue est `{{ expected_position }}'. - candidates_left_unprocessed: | - Des candidats sont dans les spécifications mais n'ont jamais été comparés au dépouillement: - {% for candidate in candidates %} - - {{ candidate.title }} ({{ candidate.id }}) + no_poll_proposal_found_for_uuid: + Aucun Proposal n'a été trouvé pour l'UUID `{{ uuid }}'. + proposal_tallies_dont_match: | + Sur la proposition `{{ proposal.title }}' ({{ proposal.uuid }}), + on obtient la mention `{{ actual_grade }}' mais la mention attendue est `{{ expected_grade }}'. + proposal_rank_mismatch: | + Sur la proposition `{{ proposal.title }}' ({{ proposal.id }}), + on obtient la position `{{ actual_rank }}' mais sa position attendue est `{{ expected_rank }}'. + proposals_left_unprocessed: | + Des propositions sont dans les spécifications mais n'ont jamais été comparés au dépouillement: + {% for proposal in proposals %} + - {{ proposal.title }} ({{ proposal.id }}) {% endfor %} + poll_has_no_subject: | + Le scrutin n'a pas de sujet. + Ajoutez-lui un sujet comme ceci (par exemple): + sujet: "Mon scrutin à moi" + poll_has_no_proposal: | + Au moins UNE proposition est requise pour un scrutin. + Ajoutez-lui des propositions comme ceci (par exemple): + {{ proposalsKey }}: + - Émile Zola + - Victor Hugo + poll_has_no_grades: | + Au moins deux mentions sont requises pour un scrutin. + Ajoutez-lui des mentions comme ceci (par exemple): + {{ gradesKey }}: + - À Rejeter + - À Accepter no_majority_judgment_poll_found_for_title: | - Aucun scrutin au jugement majoritaure ayant le titre `{{ title }}' n'a été trouvé dans la base de données. - no_majority_judgment_poll_candidate_found_for_title: | - Aucun candidat ayant le titre `{{ title }}' et appartenant à un scrutin au jugement majoritaire - n'a été trouvée dans la base de données. + Aucun scrutin au jugement majoritaire ayant pour sujet `{{ title }}' n'a été trouvé dans la base de données. + no_majority_judgment_poll_proposal_found_for_title: | + Aucune proposition n'ayant le titre `{{ title }}' et appartenant à un scrutin au jugement majoritaire + a été trouvée dans la base de données pour le scrutin assujettissant `{{ poll.subject }}'.