diff --git a/.env b/.env index c2ae58d..d2fffa8 100644 --- a/.env +++ b/.env @@ -56,3 +56,9 @@ SFTP_XML_EXPORT_HOST= SFTP_XML_EXPORT_PORT= SFTP_XML_EXPORT_USER= SFTP_XML_EXPORT_PASSWORD= + +###> league/oauth2-server-bundle ### +OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/secret/private.key +OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/secret/public.key +OAUTH_ENCRYPTION_KEY=580084fd179e67399467f59ee96658ac +###< league/oauth2-server-bundle ### diff --git a/.gitignore b/.gitignore index 4471738..85c982d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ yarn-error.log ###< symfony/webpack-encore-bundle ### /http-client.private.env.json + +###> friendsofphp/php-cs-fixer ### +/.php-cs-fixer.php +/.php-cs-fixer.cache +###< friendsofphp/php-cs-fixer ### diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..3788194 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,13 @@ +in(__DIR__) + ->exclude('var') +; + +return (new PhpCsFixer\Config()) + ->setRules([ + '@Symfony' => true, + ]) + ->setFinder($finder) +; diff --git a/api.http b/api.http new file mode 100644 index 0000000..31a7042 --- /dev/null +++ b/api.http @@ -0,0 +1,40 @@ +### user profile minimum +GET https://myep-next.ddev.site/api/userinfo +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_email")}} + +### user profile full +GET https://myep-next.ddev.site/api/userinfo +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_profile")}} + +### user crm attributes +GET https://myep-next.ddev.site/api/crm-attributes +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_profile")}} + +### API countries +GET https://myep-next.ddev.site/api/countries +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + +### API hotels +GET https://myep-next.ddev.site/api/hotels +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + +### API hotel +GET https://myep-next.ddev.site/api/hotels/198924 +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + +### API pickups +GET https://myep-next.ddev.site/api/pickups +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + +### API pickup +GET https://myep-next.ddev.site/api/pickups/1 +Accept: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + diff --git a/composer.json b/composer.json index a8887d2..22b1e4e 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "knplabs/knp-menu-bundle": "^3.4", "league/flysystem-bundle": "^3.4", "league/flysystem-sftp-v3": "^3.29", + "league/oauth2-server-bundle": "^0.11.0", "nelexa/zip": "^4.0", "nesbot/carbon": "^3.8", "phpdocumentor/reflection-docblock": "^5.6", @@ -109,6 +110,7 @@ }, "require-dev": { "deployer/deployer": "^7.5", + "friendsofphp/php-cs-fixer": "^3.75", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "6.4.*", "symfony/css-selector": "6.4.*", diff --git a/composer.lock b/composer.lock index 80b42a2..51e0d18 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6fcf6eb198ed84c9f95609ce613497e0", + "content-hash": "d80fbf64aabc9bf47d7cfc5a8bb02163", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -75,6 +75,73 @@ ], "time": "2023-12-11T17:09:12+00:00" }, + { + "name": "defuse/php-encryption", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -1498,6 +1565,202 @@ }, "time": "2024-12-25T16:34:19+00:00" }, + { + "name": "lcobucci/clock", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/coding-standard": "^11.1.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.25", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^11.3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.3.1" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2024-09-24T20:45:14+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.5.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "a835af59b030d3f2967725697cf88300f579088e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", + "reference": "a835af59b030d3f2967725697cf88300f579088e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.5.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-01-26T21:29:45+00:00" + }, + { + "name": "league/event", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.3" + }, + "time": "2024-09-04T16:06:53+00:00" + }, { "name": "league/flysystem", "version": "3.29.1", @@ -1805,6 +2068,352 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "league/oauth2-server", + "version": "9.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "00323013403e1a1e0f424affafca56c28b60c22c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/00323013403e1a1e0f424affafca56c28b60c22c", + "reference": "00323013403e1a1e0f424affafca56c28b60c22c", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.4", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/clock": "^2.3 || ^3.0", + "lcobucci/jwt": "^5.0", + "league/event": "^3.0", + "league/uri": "^7.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/http-message": "^2.0", + "psr/http-server-middleware": "^1.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^9.6.21", + "roave/security-advisories": "dev-master", + "slevomat/coding-standard": "^8.14.1", + "squizlabs/php_codesniffer": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/9.2.0" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2025-02-15T00:49:10+00:00" + }, + { + "name": "league/oauth2-server-bundle", + "version": "v0.11", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server-bundle.git", + "reference": "935f92bce72892dcc73df52e95d163428d6f3a03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server-bundle/zipball/935f92bce72892dcc73df52e95d163428d6f3a03", + "reference": "935f92bce72892dcc73df52e95d163428d6f3a03", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.8.0", + "doctrine/orm": "^2.14|^3.0", + "ext-openssl": "*", + "league/oauth2-server": "^9.2", + "nyholm/psr7": "^1.4", + "php": "^8.1", + "psr/http-factory": "^1.0", + "symfony/deprecation-contracts": "^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/polyfill-php81": "^1.22", + "symfony/psr-http-message-bridge": "^6.4|^7", + "symfony/security-bundle": "^6.4|^7.0" + }, + "require-dev": { + "ext-pdo": "*", + "ext-pdo_sqlite": "*", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/phpunit-bridge": "^7.2" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "0.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Bundle\\OAuth2ServerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "All contributors", + "homepage": "https://github.com/thephpleague/oauth2-server-bundle/graphs/contributors" + } + ], + "description": "Symfony bundle .", + "homepage": "https://github.com/thephpleague/oauth2-server-bundle", + "keywords": [ + "auth", + "authorization", + "bundle", + "oauth2", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server-bundle/issues", + "source": "https://github.com/thephpleague/oauth2-server-bundle/tree/v0.11" + }, + "time": "2025-04-06T22:20:36+00:00" + }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, { "name": "masterminds/html5", "version": "2.9.0", @@ -2154,6 +2763,84 @@ ], "time": "2025-03-27T12:57:33+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.0.0", @@ -2803,6 +3490,61 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, { "name": "psr/http-message", "version": "2.0", @@ -2856,6 +3598,119 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, { "name": "psr/link", "version": "2.0.1", @@ -6676,6 +7531,89 @@ ], "time": "2025-01-21T10:52:27+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c9cf83326a1074f83a738fc5320945abf7fb7fec", + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.2" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.2|^7.0", + "symfony/http-kernel": "^6.2|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "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": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v6.4.13" + }, + "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": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/routing", "version": "v6.4.18", @@ -9121,6 +10059,296 @@ } ], "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "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": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "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": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "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": "2024-05-06T16:37:16+00:00" + }, { "name": "deployer/deployer", "version": "v7.5.12", @@ -9175,6 +10403,218 @@ ], "time": "2025-02-19T16:45:27+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.75.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.2", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.4 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", + "symfony/polyfill-mbstring": "^1.31", + "symfony/polyfill-php80": "^1.31", + "symfony/polyfill-php81": "^1.31", + "symfony/process": "^5.4 || ^6.4 || ^7.2", + "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.6", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^5.3 || ^6.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12", + "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3", + "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-03-31T18:40:42+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.0", @@ -9833,6 +11273,532 @@ ], "time": "2024-12-05T13:48:26+00:00" }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-01-01T16:37:48+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.2", diff --git a/config/bundles.php b/config/bundles.php index f452abf..5f23f18 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,4 +15,5 @@ return [ Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true], League\FlysystemBundle\FlysystemBundle::class => ['all' => true], + League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true], ]; diff --git a/config/packages/league_oauth2_server.yaml b/config/packages/league_oauth2_server.yaml new file mode 100644 index 0000000..84d15c6 --- /dev/null +++ b/config/packages/league_oauth2_server.yaml @@ -0,0 +1,22 @@ +league_oauth2_server: + authorization_server: + private_key: '%env(resolve:OAUTH_PRIVATE_KEY)%' + private_key_passphrase: null + encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%' + enable_client_credentials_grant: true + enable_auth_code_grant: true + enable_refresh_token_grant: false + enable_password_grant: false + access_token_ttl: PT10M + resource_server: + public_key: '%env(resolve:OAUTH_PUBLIC_KEY)%' + scopes: + available: ['email','profile','api'] + default: ['email'] + persistence: + doctrine: null + +when@test: + league_oauth2_server: + persistence: + in_memory: null diff --git a/config/packages/nyholm_psr7.yaml b/config/packages/nyholm_psr7.yaml new file mode 100644 index 0000000..ade8312 --- /dev/null +++ b/config/packages/nyholm_psr7.yaml @@ -0,0 +1,11 @@ +services: + # Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories) + Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory' + + nyholm.psr7.psr17_factory: + class: Nyholm\Psr7\Factory\Psr17Factory diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6d41b9e..3b53efa 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -12,12 +12,14 @@ security: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false + api_token: + pattern: ^/token$ + security: false api: - lazy: true - stateless: true pattern: ^/api - custom_authenticator: - - App\Security\ApiKeyAuthenticator + security: true + stateless: true + oauth2: true main: lazy: true provider: app_user_provider @@ -30,7 +32,7 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - - { path: ^/api, roles: ROLE_API } + - { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED } when@test: security: diff --git a/config/routes/league_oauth2_server.yaml b/config/routes/league_oauth2_server.yaml new file mode 100644 index 0000000..38a4d1b --- /dev/null +++ b/config/routes/league_oauth2_server.yaml @@ -0,0 +1,3 @@ +league_oauth2_server: + resource: '@LeagueOAuth2ServerBundle/config/routes.php' + type: php diff --git a/config/services.yaml b/config/services.yaml index 9b65a0a..7836f0e 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -27,10 +27,6 @@ services: - '../src/Entity/' - '../src/Kernel.php' - App\Security\ApiKeyAuthenticator: - arguments: - $apiKeys: '%env(csv:API_KEYS)%' - App\BusProNet\ApiClient: arguments: $logger: '@monolog.logger.bpn' diff --git a/http-client.env.json b/http-client.env.json new file mode 100644 index 0000000..5964d1f --- /dev/null +++ b/http-client.env.json @@ -0,0 +1,37 @@ +{ + "dev": { + "Security": { + "Auth": { + "oauth2_email": { + "Type": "OAuth2", + "Grant Type": "Authorization Code", + "Client ID": "{{oauth2_client_id}}", + "Client Secret": "{{oauth2_client_secret}}", + "Auth URL": "https://myep-next.ddev.site/authorize", + "Token URL": "https://myep-next.ddev.site/token", + "Redirect URL": "https://myep-team.ddev.site/auth/check", + "Scope": "email" + }, + "oauth2_profile": { + "Type": "OAuth2", + "Grant Type": "Authorization Code", + "Client ID": "{{oauth2_client_id}}", + "Client Secret": "{{oauth2_client_secret}}", + "Auth URL": "https://myep-next.ddev.site/authorize", + "Token URL": "https://myep-next.ddev.site/token", + "Redirect URL": "https://myep-team.ddev.site/auth/check", + "Scope": "email profile" + }, + "oauth2_api": { + "Type": "OAuth2", + "Grant Type": "Client Credentials", + "Client ID": "{{oauth2_api_client_id}}", + "Client Secret": "{{oauth2_api_client_secret}}", + "Auth URL": "https://myep-next.ddev.site/authorize", + "Token URL": "https://myep-next.ddev.site/token", + "Scope": "api" + } + } + } + } +} diff --git a/migrations/Version20250424152547.php b/migrations/Version20250424152547.php new file mode 100644 index 0000000..199da78 --- /dev/null +++ b/migrations/Version20250424152547.php @@ -0,0 +1,71 @@ +addSql(<<<'SQL' + CREATE TABLE oauth2_access_token (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', revoked TINYINT(1) NOT NULL, INDEX IDX_454D9673C7440455 (client), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth2_authorization_code (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', revoked TINYINT(1) NOT NULL, INDEX IDX_509FEF5FC7440455 (client), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth2_client (identifier VARCHAR(32) NOT NULL, name VARCHAR(128) NOT NULL, secret VARCHAR(128) DEFAULT NULL, redirect_uris TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_redirect_uri)', grants TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_grant)', scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', active TINYINT(1) NOT NULL, allow_plain_text_pkce TINYINT(1) DEFAULT 0 NOT NULL, PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth2_refresh_token (identifier CHAR(80) NOT NULL, access_token CHAR(80) DEFAULT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', revoked TINYINT(1) NOT NULL, INDEX IDX_4DD90732B6A2DD68 (access_token), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_access_token ADD CONSTRAINT FK_454D9673C7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_authorization_code ADD CONSTRAINT FK_509FEF5FC7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_refresh_token ADD CONSTRAINT FK_4DD90732B6A2DD68 FOREIGN KEY (access_token) REFERENCES oauth2_access_token (identifier) ON DELETE SET NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_access_token DROP FOREIGN KEY FK_454D9673C7440455 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_authorization_code DROP FOREIGN KEY FK_509FEF5FC7440455 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth2_refresh_token DROP FOREIGN KEY FK_4DD90732B6A2DD68 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth2_access_token + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth2_authorization_code + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth2_client + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth2_refresh_token + SQL); + } +} diff --git a/src/BusProNet/ApiClient.php b/src/BusProNet/ApiClient.php index 606ac42..f6e1a1e 100644 --- a/src/BusProNet/ApiClient.php +++ b/src/BusProNet/ApiClient.php @@ -2,7 +2,6 @@ namespace App\BusProNet; -use App\BusProNet\XmlParser\ApiResponseParser; use App\BusProNet\DataProcessor\BookingDataProcessor; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Exception\ResponseParserException; @@ -12,6 +11,8 @@ use App\BusProNet\Model\BookingUpdate; use App\BusProNet\Model\CrmAttributes; use App\BusProNet\Model\Notification; use App\BusProNet\Model\PersonalData; +use App\BusProNet\Traits\ApiClientTrait; +use App\BusProNet\XmlParser\ApiResponseParser; use App\Form\Model\BookingData; use App\Form\Model\RegistrationData; use League\Flysystem\FilesystemException; @@ -39,7 +40,7 @@ class ApiClient private readonly ApiResponseParser $responseParser, private readonly FilesystemOperator $xmlDump, private readonly LoggerInterface $logger, - array $options + array $options, ) { $this->config = $this->resolveOptions($options); } @@ -107,7 +108,7 @@ class ApiClient string $email, string $password, PersonalData $personalData, - bool $debug = false + bool $debug = false, ): Notification|PersonalData { $data = [ 'user' => $this->config['bpn_username'], @@ -333,7 +334,7 @@ class ApiClient private function dumpXmlToFile(string $type, string $requestId, string $body): void { try { - $this->xmlDump->write($requestId . '_' . $type . '.xml', $body); + $this->xmlDump->write($requestId.'_'.$type.'.xml', $body); } catch (FilesystemException $e) { } } diff --git a/src/BusProNet/DataProvider/CountryDataProvider.php b/src/BusProNet/DataProvider/CountryDataProvider.php index 210f02b..e509d87 100644 --- a/src/BusProNet/DataProvider/CountryDataProvider.php +++ b/src/BusProNet/DataProvider/CountryDataProvider.php @@ -3,7 +3,6 @@ namespace App\BusProNet\DataProvider; use App\BusProNet\ApiClient; -use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\Country; use Psr\Cache\InvalidArgumentException; use Symfony\Contracts\Cache\CacheInterface; @@ -12,7 +11,8 @@ use Symfony\Contracts\Cache\ItemInterface; class CountryDataProvider { public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache) - {} + { + } public function getAll(): array { @@ -39,4 +39,4 @@ class CountryDataProvider return $this->getAll()[$token] ?? null; } -} \ No newline at end of file +} diff --git a/src/BusProNet/Exception/ResponseParserException.php b/src/BusProNet/Exception/ResponseParserException.php index b11c043..62b2183 100644 --- a/src/BusProNet/Exception/ResponseParserException.php +++ b/src/BusProNet/Exception/ResponseParserException.php @@ -4,4 +4,4 @@ namespace App\BusProNet\Exception; class ResponseParserException extends \Exception { -} \ No newline at end of file +} diff --git a/src/BusProNet/Form/ChoiceLoader/CountryChoiceLoader.php b/src/BusProNet/Form/ChoiceLoader/CountryChoiceLoader.php index 1fe4c56..3b7296d 100644 --- a/src/BusProNet/Form/ChoiceLoader/CountryChoiceLoader.php +++ b/src/BusProNet/Form/ChoiceLoader/CountryChoiceLoader.php @@ -11,9 +11,10 @@ use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; class CountryChoiceLoader implements ChoiceLoaderInterface { public function __construct(private readonly CountryDataProvider $countries, private readonly string $property) - {} + { + } - public function loadChoiceList(callable $value = null): ChoiceListInterface + public function loadChoiceList(?callable $value = null): ChoiceListInterface { $choices = []; /** @var Country[] $countries */ @@ -25,16 +26,15 @@ class CountryChoiceLoader implements ChoiceLoaderInterface } return new ArrayChoiceList($choices); - } - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { return $values; } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { return $choices; } -} \ No newline at end of file +} diff --git a/src/BusProNet/Form/CountryType.php b/src/BusProNet/Form/CountryType.php index f107261..ffbfdc0 100644 --- a/src/BusProNet/Form/CountryType.php +++ b/src/BusProNet/Form/CountryType.php @@ -13,7 +13,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class CountryType extends AbstractType { public function __construct(private readonly CountryDataProvider $countries) - {} + { + } public function getParent(): string { @@ -35,4 +36,4 @@ class CountryType extends AbstractType }, ]); } -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Availability.php b/src/BusProNet/Model/Availability.php index c221cc9..6e6826a 100644 --- a/src/BusProNet/Model/Availability.php +++ b/src/BusProNet/Model/Availability.php @@ -16,4 +16,4 @@ class Availability implements \JsonSerializable 'available' => $this->available, ]; } -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/BaseData.php b/src/BusProNet/Model/BaseData.php index 9f05600..5fa9aea 100644 --- a/src/BusProNet/Model/BaseData.php +++ b/src/BusProNet/Model/BaseData.php @@ -5,7 +5,8 @@ namespace App\BusProNet\Model; class BaseData { public function __construct(private readonly array $items) - {} + { + } public function getItems(): array { @@ -16,4 +17,4 @@ class BaseData { return $this->items[$key] ?? null; } -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Country.php b/src/BusProNet/Model/Country.php index b36349c..5c5d12b 100644 --- a/src/BusProNet/Model/Country.php +++ b/src/BusProNet/Model/Country.php @@ -8,4 +8,4 @@ class Country public ?string $name = null; public ?string $token = null; public ?string $nationality = null; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/CrmAttributes.php b/src/BusProNet/Model/CrmAttributes.php index 69bfefe..014f1b9 100644 --- a/src/BusProNet/Model/CrmAttributes.php +++ b/src/BusProNet/Model/CrmAttributes.php @@ -12,4 +12,4 @@ class CrmAttributes public bool $teamer = false; public ?string $hotelCode = null; public array $roles = []; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/CrmSelection.php b/src/BusProNet/Model/CrmSelection.php index 4e889ee..1d1b98e 100644 --- a/src/BusProNet/Model/CrmSelection.php +++ b/src/BusProNet/Model/CrmSelection.php @@ -17,4 +17,4 @@ class CrmSelection #[Groups(['api:single'])] public bool $selected = false; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/CrmSelectionGroup.php b/src/BusProNet/Model/CrmSelectionGroup.php index ccca89e..12e8a12 100644 --- a/src/BusProNet/Model/CrmSelectionGroup.php +++ b/src/BusProNet/Model/CrmSelectionGroup.php @@ -8,7 +8,8 @@ class CrmSelectionGroup { /** * Maps BusPro IDs of CRM selection groups representing - * included services + * included services. + * * @var array|string[] */ public static array $includedServicesMapping = [ diff --git a/src/BusProNet/Model/Error.php b/src/BusProNet/Model/Error.php index bd4a1f9..d3a37a8 100644 --- a/src/BusProNet/Model/Error.php +++ b/src/BusProNet/Model/Error.php @@ -100,4 +100,4 @@ class Error 985 => 'Zustieg mit ID #1# nicht fürs Internet buchbar', 999 => 'Systemfehler: #1#', ]; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/File.php b/src/BusProNet/Model/File.php index ed37066..db92a78 100644 --- a/src/BusProNet/Model/File.php +++ b/src/BusProNet/Model/File.php @@ -14,4 +14,4 @@ class File public ?string $filename = null; public ?string $content = null; public ?string $mimeType = null; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Guide.php b/src/BusProNet/Model/Guide.php index 262df2e..8a61c3a 100644 --- a/src/BusProNet/Model/Guide.php +++ b/src/BusProNet/Model/Guide.php @@ -6,4 +6,4 @@ class Guide { public ?string $name = null; public ?string $phone = null; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Hotel.php b/src/BusProNet/Model/Hotel.php index bd0f4fa..55496fb 100644 --- a/src/BusProNet/Model/Hotel.php +++ b/src/BusProNet/Model/Hotel.php @@ -26,4 +26,4 @@ class Hotel #[Groups(['api:list', 'api:single'])] public ?string $country = null; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/MutableData.php b/src/BusProNet/Model/MutableData.php index ecf4bf8..1db09f9 100644 --- a/src/BusProNet/Model/MutableData.php +++ b/src/BusProNet/Model/MutableData.php @@ -21,4 +21,4 @@ class MutableData public ?string $category = null; public bool $mutable = false; public ?\DateTimeImmutable $mutableBefore = null; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/PersonalData.php b/src/BusProNet/Model/PersonalData.php index f3ac39b..d64128d 100644 --- a/src/BusProNet/Model/PersonalData.php +++ b/src/BusProNet/Model/PersonalData.php @@ -64,4 +64,36 @@ class PersonalData 'bemerkung' => $this->remarks, ]; } + + public function getClaims(): array + { + return [ + 'email' => $this->communication->email, + 'profile' => [ + 'first_name' => $this->firstName, + 'last_name' => $this->name, + 'title' => $this->title, + 'salutation' => $this->salutation, + 'gender' => $this->gender, + 'nationality' => $this->nationality, + 'address' => [ + 'street' => $this->address->street, + 'postcode' => $this->address->postCode, + 'city' => $this->address->city, + 'disctrict' => $this->address->district, + 'country' => $this->address->country, + ], + 'communication' => [ + 'email' => $this->communication->email, + 'mobile' => $this->communication->mobile, + 'phone' => $this->communication->phone, + 'newsletter' => $this->communication->newsletter, + ], + 'height' => $this->height, + 'weight' => $this->weight, + 'shoe_size' => $this->shoeSize, + 'remarks' => $this->remarks, + ], + ]; + } } diff --git a/src/BusProNet/Model/Pickup.php b/src/BusProNet/Model/Pickup.php index ee77cd5..5cbb80a 100644 --- a/src/BusProNet/Model/Pickup.php +++ b/src/BusProNet/Model/Pickup.php @@ -32,4 +32,4 @@ class Pickup #[Groups(['api:booking'])] public array $mapping = []; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Room.php b/src/BusProNet/Model/Room.php index 816fc40..33b6fa0 100644 --- a/src/BusProNet/Model/Room.php +++ b/src/BusProNet/Model/Room.php @@ -63,4 +63,4 @@ class Room #[Groups(['booking'])] public array $individualPrice = []; -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Service.php b/src/BusProNet/Model/Service.php index 175be4b..b115dde 100644 --- a/src/BusProNet/Model/Service.php +++ b/src/BusProNet/Model/Service.php @@ -31,7 +31,7 @@ class Service public ?string $category = null; #[Groups(['api:single', 'api:list'])] - public ?string $status = null; + public ?string $status = null; #[Groups(['api:single', 'api:list'])] public bool $mandatory = false; diff --git a/src/BusProNet/Model/Surcharge.php b/src/BusProNet/Model/Surcharge.php index c3d810e..fd88f37 100644 --- a/src/BusProNet/Model/Surcharge.php +++ b/src/BusProNet/Model/Surcharge.php @@ -8,4 +8,4 @@ class Surcharge public array $mapping = []; public ?float $totalPrice = null; public array $individualPrice = []; -} \ No newline at end of file +} diff --git a/src/BusProNet/ApiClientTrait.php b/src/BusProNet/Traits/ApiClientTrait.php similarity index 95% rename from src/BusProNet/ApiClientTrait.php rename to src/BusProNet/Traits/ApiClientTrait.php index 124b140..3ec21fb 100644 --- a/src/BusProNet/ApiClientTrait.php +++ b/src/BusProNet/Traits/ApiClientTrait.php @@ -1,6 +1,6 @@ startOfDay(); + return $date->toDateTimeImmutable(); } catch (InvalidFormatException $e) { return null; @@ -66,6 +67,7 @@ trait TypeConversionTrait try { $dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16))->startOfDay(); + return $dateTime->toDateTimeImmutable(); } catch (InvalidFormatException $e) { return null; diff --git a/src/BusProNet/XmlParserTrait.php b/src/BusProNet/Traits/XmlParserTrait.php similarity index 91% rename from src/BusProNet/XmlParserTrait.php rename to src/BusProNet/Traits/XmlParserTrait.php index a357826..f8e0d6e 100644 --- a/src/BusProNet/XmlParserTrait.php +++ b/src/BusProNet/Traits/XmlParserTrait.php @@ -1,6 +1,6 @@ count() ? (int) $node->text() : null; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlLoader/AbstractLoader.php b/src/BusProNet/XmlLoader/AbstractLoader.php index b06d9af..8b3d5b3 100644 --- a/src/BusProNet/XmlLoader/AbstractLoader.php +++ b/src/BusProNet/XmlLoader/AbstractLoader.php @@ -2,8 +2,8 @@ namespace App\BusProNet\XmlLoader; -use App\BusProNet\TypeConversionTrait; -use App\BusProNet\XmlParserTrait; +use App\BusProNet\Traits\TypeConversionTrait; +use App\BusProNet\Traits\XmlParserTrait; use League\Flysystem\FilesystemOperator; use Symfony\Contracts\Cache\CacheInterface; diff --git a/src/BusProNet/XmlLoader/TravelLoader.php b/src/BusProNet/XmlLoader/TravelLoader.php index d02b43d..6631f17 100644 --- a/src/BusProNet/XmlLoader/TravelLoader.php +++ b/src/BusProNet/XmlLoader/TravelLoader.php @@ -24,7 +24,7 @@ class TravelLoader extends AbstractLoader private readonly HotelLoader $hotelDataLoader, CacheInterface $cache, private readonly string $travelInfoBaseUrl, - FileSystemOperator $xmlExport, + FilesystemOperator $xmlExport, ) { parent::__construct($cache, $xmlExport); } diff --git a/src/BusProNet/XmlParser/AbstractParser.php b/src/BusProNet/XmlParser/AbstractParser.php index 49604f4..661b71a 100644 --- a/src/BusProNet/XmlParser/AbstractParser.php +++ b/src/BusProNet/XmlParser/AbstractParser.php @@ -2,7 +2,7 @@ namespace App\BusProNet\XmlParser; -use App\BusProNet\TypeConversionTrait; +use App\BusProNet\Traits\TypeConversionTrait; use Symfony\Component\DomCrawler\Crawler; abstract class AbstractParser @@ -47,4 +47,4 @@ abstract class AbstractParser return $this->stringToArray($node->text(), $separator); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/ApiResponseParser.php b/src/BusProNet/XmlParser/ApiResponseParser.php index 4c1cd1e..b65385a 100644 --- a/src/BusProNet/XmlParser/ApiResponseParser.php +++ b/src/BusProNet/XmlParser/ApiResponseParser.php @@ -61,4 +61,4 @@ class ApiResponseParser extends AbstractParser throw new ResponseParserException('Unable to parse XML response'); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/AvailabilitiesParser.php b/src/BusProNet/XmlParser/AvailabilitiesParser.php index f25d898..165369e 100644 --- a/src/BusProNet/XmlParser/AvailabilitiesParser.php +++ b/src/BusProNet/XmlParser/AvailabilitiesParser.php @@ -27,4 +27,4 @@ class AvailabilitiesParser extends AbstractParser return new BaseData($availabilities); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/BookingsParser.php b/src/BusProNet/XmlParser/BookingsParser.php index cc0c75d..2b6766d 100644 --- a/src/BusProNet/XmlParser/BookingsParser.php +++ b/src/BusProNet/XmlParser/BookingsParser.php @@ -38,4 +38,4 @@ class BookingsParser extends AbstractParser return new BaseData($bookings); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/CountriesParser.php b/src/BusProNet/XmlParser/CountriesParser.php index 123cc91..4ed007a 100644 --- a/src/BusProNet/XmlParser/CountriesParser.php +++ b/src/BusProNet/XmlParser/CountriesParser.php @@ -27,4 +27,4 @@ class CountriesParser return new BaseData($countries); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/CrmAttributesResponseParser.php b/src/BusProNet/XmlParser/CrmAttributesResponseParser.php index 2d9f887..4b269dd 100644 --- a/src/BusProNet/XmlParser/CrmAttributesResponseParser.php +++ b/src/BusProNet/XmlParser/CrmAttributesResponseParser.php @@ -6,7 +6,7 @@ use App\BusProNet\Model\CrmAction; use App\BusProNet\Model\CrmAttributes; use App\BusProNet\Model\CrmSelection; use App\BusProNet\Model\CrmSelectionGroup; -use App\BusProNet\TypeConversionTrait; +use App\BusProNet\Traits\TypeConversionTrait; use Symfony\Component\DomCrawler\Crawler; class CrmAttributesResponseParser diff --git a/src/BusProNet/XmlParser/DocumentsParser.php b/src/BusProNet/XmlParser/DocumentsParser.php index 455faac..37288ff 100644 --- a/src/BusProNet/XmlParser/DocumentsParser.php +++ b/src/BusProNet/XmlParser/DocumentsParser.php @@ -37,8 +37,7 @@ class DocumentsParser }); return new File('reisedokumente.zip', $zip->outputAsString(), 'application/zip'); - } - catch (ZipException $e) { + } catch (ZipException $e) { return null; } } @@ -57,4 +56,4 @@ class DocumentsParser return [$filename, $pdfData]; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/MutableDataParser.php b/src/BusProNet/XmlParser/MutableDataParser.php index 6d39fe8..4fb9773 100644 --- a/src/BusProNet/XmlParser/MutableDataParser.php +++ b/src/BusProNet/XmlParser/MutableDataParser.php @@ -39,4 +39,4 @@ class MutableDataParser extends AbstractParser return new BaseData($mutableFields); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/NotificationParser.php b/src/BusProNet/XmlParser/NotificationParser.php index c25ff4f..bbf78bd 100644 --- a/src/BusProNet/XmlParser/NotificationParser.php +++ b/src/BusProNet/XmlParser/NotificationParser.php @@ -14,4 +14,4 @@ class NotificationParser extends AbstractParser return new Notification($number, $text); } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/PersonalDataParser.php b/src/BusProNet/XmlParser/PersonalDataParser.php index 1428dea..79e3ea1 100644 --- a/src/BusProNet/XmlParser/PersonalDataParser.php +++ b/src/BusProNet/XmlParser/PersonalDataParser.php @@ -36,8 +36,8 @@ class PersonalDataParser extends AbstractParser $address = new Address(); $address->street = $this->getStringOrNullValue($addressNode->filterXPath('//strasse')); $address->postCode = $this->getStringOrNullValue($addressNode->filterXPath('//plz')); - $address->city = $this->getStringOrNullValue($addressNode->filterXPath('//ort')) ; - $address->country = $this->getStringOrNullValue($addressNode->filterXPath('//land')) ; + $address->city = $this->getStringOrNullValue($addressNode->filterXPath('//ort')); + $address->country = $this->getStringOrNullValue($addressNode->filterXPath('//land')); $personalData->address = $address; } @@ -63,4 +63,4 @@ class PersonalDataParser extends AbstractParser return $personalData; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/PickupsParser.php b/src/BusProNet/XmlParser/PickupsParser.php index a985b80..e70cc0a 100644 --- a/src/BusProNet/XmlParser/PickupsParser.php +++ b/src/BusProNet/XmlParser/PickupsParser.php @@ -33,4 +33,4 @@ class PickupsParser extends AbstractParser return $pickups; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/RoomsParser.php b/src/BusProNet/XmlParser/RoomsParser.php index 477be63..4ff2166 100644 --- a/src/BusProNet/XmlParser/RoomsParser.php +++ b/src/BusProNet/XmlParser/RoomsParser.php @@ -13,7 +13,7 @@ class RoomsParser extends AbstractParser $result->each(function (Crawler $node) use (&$rooms) { $room = new Room(); - $room->id = (int) $node->attr('idzimmer');; + $room->id = (int) $node->attr('idzimmer'); $room->label = $node->attr('zimmer'); $room->category = $node->attr('kategorie'); $room->boardId = (int) $node->attr('idverpflegung'); @@ -38,4 +38,4 @@ class RoomsParser extends AbstractParser return $rooms; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/ServicesParser.php b/src/BusProNet/XmlParser/ServicesParser.php index f2cd87e..7142821 100644 --- a/src/BusProNet/XmlParser/ServicesParser.php +++ b/src/BusProNet/XmlParser/ServicesParser.php @@ -39,4 +39,4 @@ class ServicesParser extends AbstractParser return $services; } -} \ No newline at end of file +} diff --git a/src/BusProNet/XmlParser/SurchargesParser.php b/src/BusProNet/XmlParser/SurchargesParser.php index b903232..222a701 100644 --- a/src/BusProNet/XmlParser/SurchargesParser.php +++ b/src/BusProNet/XmlParser/SurchargesParser.php @@ -30,4 +30,4 @@ class SurchargesParser extends AbstractParser return $surcharges; } -} \ No newline at end of file +} diff --git a/src/Command/CleanupXMLDumpsCommand.php b/src/Command/CleanupXMLDumpsCommand.php index 7f07a46..e083d92 100644 --- a/src/Command/CleanupXMLDumpsCommand.php +++ b/src/Command/CleanupXMLDumpsCommand.php @@ -13,8 +13,8 @@ use Symfony\Component\Console\Output\OutputInterface; #[AsCommand( name: 'app:cleanup:xml-dumps', - description: 'Removes XML dumps of BPN requests/responses older than a week') -] + description: 'Removes XML dumps of BPN requests/responses older than a week' +)] class CleanupXMLDumpsCommand extends Command { public function __construct( @@ -33,11 +33,11 @@ class CleanupXMLDumpsCommand extends Command $files = $this ->xmlDump ->listContents('.') - ->filter(fn(StorageAttributes $attributes) => $attributes->isFile() && $attributes->lastModified() < $maxDate) - ->map(fn(StorageAttributes $attributes) => $attributes->path()) + ->filter(fn (StorageAttributes $attributes) => $attributes->isFile() && $attributes->lastModified() < $maxDate) + ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray(); } catch (FilesystemException $e) { - $output->writeln('' . $e->getMessage() . ''); + $output->writeln(''.$e->getMessage().''); return Command::FAILURE; } diff --git a/src/Controller/Api/CountryController.php b/src/Controller/Api/CountryController.php index 8d202a4..46e4756 100644 --- a/src/Controller/Api/CountryController.php +++ b/src/Controller/Api/CountryController.php @@ -6,8 +6,10 @@ use App\BusProNet\DataProvider\CountryDataProvider; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/api')] +#[IsGranted('ROLE_OAUTH2_API')] class CountryController extends AbstractController { public function __construct(private readonly CountryDataProvider $dataProvider) @@ -21,4 +23,4 @@ class CountryController extends AbstractController return $this->json($countries); } -} \ No newline at end of file +} diff --git a/src/Controller/Api/CrmAttributesController.php b/src/Controller/Api/CrmAttributeController.php similarity index 64% rename from src/Controller/Api/CrmAttributesController.php rename to src/Controller/Api/CrmAttributeController.php index d36ed0f..14a8f80 100644 --- a/src/Controller/Api/CrmAttributesController.php +++ b/src/Controller/Api/CrmAttributeController.php @@ -5,28 +5,29 @@ namespace App\Controller\Api; use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\Notification; +use App\Entity\User; +use App\Security\Crypt; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/api')] -class CrmAttributesController extends AbstractController +#[IsGranted('ROLE_OAUTH2_PROFILE')] +class CrmAttributeController extends AbstractController { - public function __construct(private readonly APiClient $apiClient) + public function __construct(private readonly ApiClient $apiClient, private readonly Crypt $crypt) { } #[Route('/crm-attributes', name: 'api_crm_attributes', methods: ['GET'])] - public function index(Request $request): JsonResponse + public function index(): JsonResponse { - $email = $request->headers->get('x-bpn-email'); - $password = $request->headers->get('x-bpn-password'); - - if (null === $email || null === $password) { - return new JsonResponse(['message' => 'Invalid credentials'], Response::HTTP_BAD_REQUEST); - } + /** @var User $user */ + $user = $this->getUser(); + $email = $user->getEmail(); + $password = $this->crypt->decrypt($user->getPassword()); try { $data = $this->apiClient->getCrmAttributes($email, $password); @@ -40,4 +41,4 @@ class CrmAttributesController extends AbstractController return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_BAD_REQUEST); } } -} \ No newline at end of file +} diff --git a/src/Controller/Api/HotelController.php b/src/Controller/Api/HotelController.php index 2fa3d83..4907c10 100644 --- a/src/Controller/Api/HotelController.php +++ b/src/Controller/Api/HotelController.php @@ -7,15 +7,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/api')] +#[IsGranted('ROLE_OAUTH2_API')] class HotelController extends AbstractController { public function __construct(private readonly HotelLoader $xmlLoader) { } - #[Route('/hotels', name: 'api_hotel_all')] + #[Route('/hotels', name: 'api_hotels_all')] public function index(): JsonResponse { $hotels = $this->xmlLoader->loadAll(); @@ -34,4 +36,4 @@ class HotelController extends AbstractController return $this->json($hotel, Response::HTTP_OK, [], ['groups' => 'api:single']); } -} \ No newline at end of file +} diff --git a/src/Controller/Api/PersonalDataController.php b/src/Controller/Api/PersonalDataController.php deleted file mode 100644 index d2e0fdf..0000000 --- a/src/Controller/Api/PersonalDataController.php +++ /dev/null @@ -1,43 +0,0 @@ -headers->get('x-bpn-email'); - $password = $request->headers->get('x-bpn-password'); - - if (null === $email || null === $password) { - return new JsonResponse(['message' => 'Invalid credentials'], Response::HTTP_BAD_REQUEST); - } - - try { - $data = $this->apiClient->getPersonalData($email, $password); - - if ($data instanceof Notification) { - return new JsonResponse(['message' => $data->message, 'code' => $data->code], Response::HTTP_BAD_REQUEST); - } - - return $this->json($data); - } catch (ApiClientException $e) { - return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_BAD_REQUEST); - } - } -} \ No newline at end of file diff --git a/src/Controller/Api/PickupController.php b/src/Controller/Api/PickupController.php index 3d5374d..43a2c02 100644 --- a/src/Controller/Api/PickupController.php +++ b/src/Controller/Api/PickupController.php @@ -7,15 +7,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/api')] +#[IsGranted('ROLE_OAUTH2_API')] class PickupController extends AbstractController { public function __construct(private readonly PickupLoader $xmlLoader) { } - #[Route('/pickups', name: 'api_pickup_all')] + #[Route('/pickups', name: 'api_pickups_all')] public function index(): JsonResponse { $pickups = $this->xmlLoader->loadAll(); @@ -23,11 +25,11 @@ class PickupController extends AbstractController return $this->json($pickups, Response::HTTP_OK, [], ['groups' => 'api:list']); } - #[Route('/pickups/{id}', name: 'api_pickup_single')] + #[Route('/pickups/{id}', name: 'api_pickups_single')] public function single(int $id): JsonResponse { $pickup = $this->xmlLoader->loadById($id); return $this->json($pickup, Response::HTTP_OK, [], ['groups' => 'api:list']); } -} \ No newline at end of file +} diff --git a/src/Controller/Api/ResetPasswordController.php b/src/Controller/Api/ResetPasswordController.php deleted file mode 100644 index 1f2521b..0000000 --- a/src/Controller/Api/ResetPasswordController.php +++ /dev/null @@ -1,47 +0,0 @@ -getContent(), true); - $email = $payload['email'] ?? null; - - if (null === $email) { - return new JsonResponse(['message' => 'Invalid email'], Response::HTTP_BAD_REQUEST); - } - - $violationList = $this->validator->validate($email, [new Email([ - 'mode' => 'strict', - ])]); - - if (0 < count($violationList)) { - return new JsonResponse(['message' => 'Invalid email'], Response::HTTP_BAD_REQUEST); - } - - try { - $this->apiClient->resetPassword($email); - } catch (ApiClientException $e) { - } - - return $this->json(['message' => 'Password reset invoked']); - } -} \ No newline at end of file diff --git a/src/Controller/Api/TravelController.php b/src/Controller/Api/TravelController.php index 1cd5822..ba58ce2 100644 --- a/src/Controller/Api/TravelController.php +++ b/src/Controller/Api/TravelController.php @@ -11,16 +11,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; #[Route('/api')] +#[IsGranted('ROLE_OAUTH2_API')] class TravelController extends AbstractController { public function __construct( - private readonly TravelLoader $travelXmlLoader, - private readonly HotelLoader $hotelXmlLoader, - private readonly PickupLoader $pickupXmlLoader, + private readonly TravelLoader $travelXmlLoader, + private readonly HotelLoader $hotelXmlLoader, + private readonly PickupLoader $pickupXmlLoader, private readonly CacheInterface $cache, ) { } @@ -90,4 +92,4 @@ class TravelController extends AbstractController return $travel; } -} \ No newline at end of file +} diff --git a/src/Controller/Api/UserinfoController.php b/src/Controller/Api/UserinfoController.php new file mode 100644 index 0000000..54bea4a --- /dev/null +++ b/src/Controller/Api/UserinfoController.php @@ -0,0 +1,74 @@ +isGranted('ROLE_OAUTH2_PROFILE')) { + $scopes[] = 'profile'; + } + + /** @var User $user */ + $user = $this->getUser(); + $email = $user->getEmail(); + $password = $this->crypt->decrypt($user->getPassword()); + + try { + $data = $this->apiClient->getPersonalData($email, $password); + + if ($data instanceof Notification) { + return new JsonResponse(['message' => $data->message, 'code' => $data->code], Response::HTTP_BAD_REQUEST); + } + + // extract userdata for resulting claims + $userData = $this->getClaims($data, $scopes); + + return $this->json($userData); + } catch (ApiClientException $e) { + return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_BAD_REQUEST); + } + } + + private function getClaims(PersonalData $data, array $scopes): array + { + // get all available claims + $allClaims = $data->getClaims(); + $keys = array_keys($allClaims); + $claims = []; + + // filter claims by provided scopes + foreach ($scopes as $scope) { + if (false === in_array($scope, $keys)) { + continue; + } + $claims[$scope] = $allClaims[$scope]; + } + + return $claims; + } +} diff --git a/src/Controller/Booking/DownloadController.php b/src/Controller/Booking/DownloadController.php index aec3037..a26aeda 100644 --- a/src/Controller/Booking/DownloadController.php +++ b/src/Controller/Booking/DownloadController.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Contracts\Cache\CacheInterface; + use function Symfony\Component\String\u; class DownloadController extends AbstractController @@ -42,7 +43,7 @@ class DownloadController extends AbstractController requirements: ['id' => '\d+'], defaults: ['fileType' => 'invoice'] )] - #[IsGranted("ROLE_USER")] + #[IsGranted('ROLE_USER')] public function documents(int $id, string $fileType): Response { /** @var User $user */ diff --git a/src/Controller/Booking/EditController.php b/src/Controller/Booking/EditController.php index 7d760e8..4bed922 100644 --- a/src/Controller/Booking/EditController.php +++ b/src/Controller/Booking/EditController.php @@ -38,7 +38,7 @@ class EditController extends AbstractController } #[Route('/bookings/{id}/edit', name: 'app_booking_edit', requirements: ['id' => '\d+'])] - #[IsGranted("ROLE_USER")] + #[IsGranted('ROLE_USER')] public function edit(int $id, Request $request): Response { /** @var User $user */ diff --git a/src/Controller/Booking/IndexController.php b/src/Controller/Booking/IndexController.php index 712c1a9..e51071d 100644 --- a/src/Controller/Booking/IndexController.php +++ b/src/Controller/Booking/IndexController.php @@ -27,7 +27,7 @@ class IndexController extends AbstractController } #[Route('/bookings', name: 'app_bookings')] - #[IsGranted("ROLE_USER")] + #[IsGranted('ROLE_USER')] public function index(Request $request): Response { /** @var User $user */ diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 9e2e6f0..eebe01d 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -26,7 +26,7 @@ class RegistrationController extends AbstractController public function index(Request $request): Response { $registrationData = new RegistrationData(); - $form = $this->createForm(RegistrationType::class, $registrationData,[ + $form = $this->createForm(RegistrationType::class, $registrationData, [ 'action' => $this->generateUrl('app_registration'), ]); $form->handleRequest($request); diff --git a/src/Controller/ResetPasswordController.php b/src/Controller/ResetPasswordController.php index 2b795e2..3c46c2c 100644 --- a/src/Controller/ResetPasswordController.php +++ b/src/Controller/ResetPasswordController.php @@ -4,7 +4,6 @@ namespace App\Controller; use App\BusProNet\ApiClient; use App\BusProNet\Exception\ApiClientException; -use App\BusProNet\Exception\ResponseParserException; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\EmailType; @@ -38,7 +37,7 @@ class ResetPasswordController extends AbstractController new Email([ 'mode' => 'strict', 'message' => 'Bitte eine gültige E-Mail Adresse angeben', - ]) + ]), ], ]) ->getForm() diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 0f25ab0..665c399 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -3,6 +3,7 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -12,7 +13,7 @@ class SecurityController extends AbstractController { #[Route('/', name: 'app_login')] #[IsGranted('PUBLIC_ACCESS')] - public function login(AuthenticationUtils $authenticationUtils): Response + public function login(AuthenticationUtils $authenticationUtils, Request $request): Response { if (null !== $this->getUser()) { return $this->redirectToRoute('app_personal_data'); @@ -21,6 +22,15 @@ class SecurityController extends AbstractController $error = $authenticationUtils->getLastAuthenticationError(); $lastUsername = $authenticationUtils->getLastUsername(); + // flag this request in the session as external OAuth2 request if + // applicable to immediately logout the current user after successful + // authentication (see App\EventListener\AuthorizationCodeListener). + $session = $request->getSession(); + $targetPath = $session->get('_security.main.target_path'); + if (str_contains($targetPath, '/authorize')) { + $session->set('_oauth2', true); + } + return $this->render('security/login.html.twig', [ 'last_username' => $lastUsername, 'error' => $error, @@ -30,5 +40,6 @@ class SecurityController extends AbstractController #[Route('/logout', name: 'app_logout')] #[IsGranted('ROLE_USER')] public function logout(): void - {} -} \ No newline at end of file + { + } +} diff --git a/src/EventListener/AuthorizationCodeListener.php b/src/EventListener/AuthorizationCodeListener.php new file mode 100644 index 0000000..7ea7548 --- /dev/null +++ b/src/EventListener/AuthorizationCodeListener.php @@ -0,0 +1,55 @@ +requestStack->getMainRequest(); + + /** @var User $user */ + $user = $this->security->getUser(); + + if (null === $user) { + $response = new RedirectResponse( + $this->urlGenerator->generate('app_login', $request->query->all()), + Response::HTTP_TEMPORARY_REDIRECT + ); + $event->setResponse($response); + $this->authenticationLogger->info('Authorization request without session'); + } else { + $event->resolveAuthorization(AuthorizationRequestResolveEvent::AUTHORIZATION_APPROVED); + // in case this authorization request has been flagged as external in login controller, + // immediately logout the current user (see App\Controller\Core\Security\LoginController). + if (true === $request->getSession()->get('_oauth2', false)) { + $this->security->logout(false); + } + } + } +} diff --git a/src/Form/Model/RegistrationData.php b/src/Form/Model/RegistrationData.php index e5c1321..8cac18f 100644 --- a/src/Form/Model/RegistrationData.php +++ b/src/Form/Model/RegistrationData.php @@ -19,4 +19,4 @@ class RegistrationData #[Assert\NotBlank(message: 'Bitte angeben')] #[Assert\Email(message: 'Bitte eine gültige E-Mail Adresse angeben', mode: 'strict')] public ?string $email = null; -} \ No newline at end of file +} diff --git a/src/Form/ParticipantType.php b/src/Form/ParticipantType.php index f725431..f516396 100644 --- a/src/Form/ParticipantType.php +++ b/src/Form/ParticipantType.php @@ -39,13 +39,13 @@ class ParticipantType extends AbstractType 'label' => 'Vorname', 'attr' => [ 'readonly' => false === $personalDataMutable, - ] + ], ]) ->add('lastName', TextType::class, [ 'label' => 'Nachname', 'attr' => [ 'readonly' => false === $personalDataMutable, - ] + ], ]) ->add('dateOfBirth', BirthdayType::class, [ 'label' => 'Geburtsdatum', @@ -54,7 +54,7 @@ class ParticipantType extends AbstractType 'input' => 'datetime_immutable', 'attr' => [ 'readonly' => false === $personalDataMutable, - ] + ], ]) ->add('gender', ChoiceType::class, [ 'label' => 'Geschlecht', @@ -68,7 +68,7 @@ class ParticipantType extends AbstractType 'attr' => [ 'readonly' => false === $personalDataMutable, 'style' => false === $personalDataMutable ? 'pointer-events: none' : null, - ] + ], ]) ->add('nationality', CountryType::class, [ 'label' => 'Nationalität', @@ -77,21 +77,21 @@ class ParticipantType extends AbstractType 'attr' => [ 'readonly' => false === $personalDataMutable, 'style' => false === $personalDataMutable ? 'pointer-events: none' : null, - ] + ], ]) ->add('email', EmailType::class, [ 'label' => 'E-Mail', 'required' => false, 'attr' => [ 'readonly' => false === $personalDataMutable, - ] + ], ]) ->add('mobile', TextType::class, [ 'label' => 'Telefon (mobil)', 'required' => false, 'attr' => [ 'readonly' => false === $personalDataMutable, - ] + ], ]) ->add('height', ChoiceType::class, [ 'label' => 'Körpergröße', @@ -144,7 +144,8 @@ class ParticipantType extends AbstractType return $service->label; } - return sprintf('%s (%s €)', + return sprintf( + '%s (%s €)', $service->label, number_format($price, 2, ',', '.') ); @@ -189,8 +190,8 @@ class ParticipantType extends AbstractType // remove choices only available in booking data and // which are not mapped to the current participant if ( - Service::SOURCE_TRAVEL === $service->source || - Service::CATEGORY_ADDITIONAL !== $service->category + Service::SOURCE_TRAVEL === $service->source + || Service::CATEGORY_ADDITIONAL !== $service->category ) { return true; } @@ -293,7 +294,8 @@ class ParticipantType extends AbstractType return $pickupLabel; } - return sprintf('%s %s€', + return sprintf( + '%s %s€', $pickupLabel, number_format($price, 2, ',', '.') ); @@ -308,7 +310,7 @@ class ParticipantType extends AbstractType }, 'attr' => [ 'readonly' => false === $options['pickups_mutable'], - ] + ], ]); }) ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { @@ -331,8 +333,9 @@ class ParticipantType extends AbstractType // forcibly select mandatory services that potentially have been disabled in PRE_SET_DATA $mandatoryServices = array_filter($options['selectable_services'], function (Service $service) use ($form) { $participantIndex = $form->getData()->index; - return true === $service->mandatory || - (Service::SOURCE_BOOKING === $service->source && in_array($participantIndex, $service->mapping)); + + return true === $service->mandatory + || (Service::SOURCE_BOOKING === $service->source && in_array($participantIndex, $service->mapping)); }); if (0 < count($mandatoryServices)) { diff --git a/src/Form/PersonalDataType.php b/src/Form/PersonalDataType.php index 66c7441..3ffe9ca 100644 --- a/src/Form/PersonalDataType.php +++ b/src/Form/PersonalDataType.php @@ -4,8 +4,6 @@ namespace App\Form; use App\BusProNet\Form\CountryType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\BirthdayType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; diff --git a/src/Form/RegistrationType.php b/src/Form/RegistrationType.php index a56f2bc..b0b86a2 100644 --- a/src/Form/RegistrationType.php +++ b/src/Form/RegistrationType.php @@ -43,4 +43,4 @@ class RegistrationType extends AbstractType 'anti_xss' => true, ]); } -} \ No newline at end of file +} diff --git a/src/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php index 986b799..390fb48 100644 --- a/src/Menu/MenuBuilder.php +++ b/src/Menu/MenuBuilder.php @@ -49,4 +49,4 @@ class MenuBuilder return $menu; } -} \ No newline at end of file +} diff --git a/src/Security/ApiKeyAuthenticator.php b/src/Security/ApiKeyAuthenticator.php deleted file mode 100644 index e2efff0..0000000 --- a/src/Security/ApiKeyAuthenticator.php +++ /dev/null @@ -1,60 +0,0 @@ -headers->has('X-BPN-API-KEY'); - } - - public function authenticate(Request $request): Passport - { - $apiKey = $request->headers->get('X-BPN-API-KEY'); - - if (null === $apiKey) { - throw new CustomUserMessageAuthenticationException('No API key provided'); - } - - if (false === in_array($apiKey, $this->apiKeys)) { - throw new CustomUserMessageAuthenticationException('Invalid API key'); - } - - return new SelfValidatingPassport( - new UserBadge($apiKey, function (string $userIdentifier) use ($apiKey): ?UserInterface { - return new ApiUser($apiKey); - }) - ); - } - - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response - { - return null; - } - - public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response - { - $data = [ - 'message' => 'Authentication missing or failed', - ]; - - return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); - } -} \ No newline at end of file diff --git a/src/Security/ApiUser.php b/src/Security/ApiUser.php deleted file mode 100644 index d18bfe2..0000000 --- a/src/Security/ApiUser.php +++ /dev/null @@ -1,26 +0,0 @@ -apiKey; - } -} \ No newline at end of file diff --git a/src/Security/BpnAuthenticator.php b/src/Security/BpnAuthenticator.php index b3c996e..5c0e4b3 100644 --- a/src/Security/BpnAuthenticator.php +++ b/src/Security/BpnAuthenticator.php @@ -22,9 +22,12 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\Util\TargetPathTrait; class BpnAuthenticator extends AbstractLoginFormAuthenticator implements AuthenticationEntryPointInterface { + use TargetPathTrait; + public function __construct( private readonly UrlGeneratorInterface $urlGenerator, private readonly ApiClient $apiClient, @@ -60,7 +63,7 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent $csrfToken = $request->request->getString('_csrf_token'); return new SelfValidatingPassport( - new UserBadge($email, function () use ($email, $password, $response, $request) { + new UserBadge($email, function () use ($email, $password, $response) { return $this->createOrUpdateLocalUser($email, $password, $response->personId, $response->addressId); }), [ @@ -106,6 +109,10 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent 'email' => $token->getUserIdentifier(), ]); + if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { + return new RedirectResponse($targetPath); + } + return new RedirectResponse($this->urlGenerator->generate('app_personal_data')); } diff --git a/src/Security/Crypt.php b/src/Security/Crypt.php index 19ead9a..df597f0 100644 --- a/src/Security/Crypt.php +++ b/src/Security/Crypt.php @@ -13,28 +13,28 @@ class Crypt public function encrypt(string $message): string { - $privateKey = PrivateKey::fromFile($this->path . '/private.key'); + $privateKey = PrivateKey::fromFile($this->path.'/private.key'); return $privateKey->encrypt($message); } public function decrypt(string $message): string { - $publicKey = PublicKey::fromFile($this->path . '/public.key'); + $publicKey = PublicKey::fromFile($this->path.'/public.key'); return $publicKey->decrypt($message); } public function sign(string $message): string { - $privateKey = PrivateKey::fromFile($this->path . '/private.key'); + $privateKey = PrivateKey::fromFile($this->path.'/private.key'); return $privateKey->sign($message); } public function verify(string $message, string $signature): bool { - $publicKey = PublicKey::fromFile($this->path . '/public.key'); + $publicKey = PublicKey::fromFile($this->path.'/public.key'); return $publicKey->verify($message, $signature); } diff --git a/src/Twig/AppRuntime.php b/src/Twig/AppRuntime.php index 73b4f61..762256d 100644 --- a/src/Twig/AppRuntime.php +++ b/src/Twig/AppRuntime.php @@ -32,7 +32,7 @@ class AppRuntime implements RuntimeExtensionInterface $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; $factor = floor((strlen($bytes) - 1) / 3); - return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)) . @$size[$factor]; + return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)).@$size[$factor]; } public function formatMoney(int $amount): string diff --git a/symfony.lock b/symfony.lock index aff7be2..f5a449b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -26,6 +26,18 @@ "migrations/.gitignore" ] }, + "friendsofphp/php-cs-fixer": { + "version": "3.75", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "be2103eb4a20942e28a6dd87736669b757132435" + }, + "files": [ + ".php-cs-fixer.dist.php" + ] + }, "knplabs/knp-menu-bundle": { "version": "v3.4.2" }, @@ -42,6 +54,31 @@ "var/storage/.gitignore" ] }, + "league/oauth2-server-bundle": { + "version": "0.11", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.11", + "ref": "80320e8e61b51f6965b83a7df1cc9d40bcc3fb78" + }, + "files": [ + "config/packages/league_oauth2_server.yaml", + "config/routes/league_oauth2_server.yaml" + ] + }, + "nyholm/psr7": { + "version": "1.8", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "4a8c0345442dcca1d8a2c65633dcf0285dd5a5a2" + }, + "files": [ + "config/packages/nyholm_psr7.yaml" + ] + }, "phpunit/phpunit": { "version": "9.6", "recipe": { diff --git a/tests/BusProNet/DataLoader/HotelDataLoaderTest.php b/tests/BusProNet/DataLoader/HotelDataLoaderTest.php index 19bc078..8eec4aa 100644 --- a/tests/BusProNet/DataLoader/HotelDataLoaderTest.php +++ b/tests/BusProNet/DataLoader/HotelDataLoaderTest.php @@ -2,8 +2,8 @@ namespace App\Tests\BusProNet\DataLoader; -use App\BusProNet\XmlLoader\HotelLoader; use App\BusProNet\Model\Hotel; +use App\BusProNet\XmlLoader\HotelLoader; use PHPUnit\Framework\TestCase; class HotelDataLoaderTest extends TestCase @@ -27,4 +27,4 @@ class HotelDataLoaderTest extends TestCase $this->assertEquals('Rue des Lacs', $hotel->street); $this->assertEquals('', $hotel->phone); } -} \ No newline at end of file +} diff --git a/tests/BusProNet/DataLoader/PickupDataLoaderTest.php b/tests/BusProNet/DataLoader/PickupDataLoaderTest.php index bdd933e..efe03f3 100644 --- a/tests/BusProNet/DataLoader/PickupDataLoaderTest.php +++ b/tests/BusProNet/DataLoader/PickupDataLoaderTest.php @@ -2,8 +2,8 @@ namespace App\Tests\BusProNet\DataLoader; -use App\BusProNet\XmlLoader\PickupLoader; use App\BusProNet\Model\Pickup; +use App\BusProNet\XmlLoader\PickupLoader; use PHPUnit\Framework\TestCase; class PickupDataLoaderTest extends TestCase @@ -25,4 +25,4 @@ class PickupDataLoaderTest extends TestCase $this->assertEquals('48143', $pickup->postalCode); $this->assertEquals('Hafenstr/Ecke Friedrich-Ebert-Str', $pickup->street); } -} \ No newline at end of file +} diff --git a/tests/BusProNet/DataLoader/TravelDataLoaderTest.php b/tests/BusProNet/DataLoader/TravelDataLoaderTest.php index 1d020e5..bb554a6 100644 --- a/tests/BusProNet/DataLoader/TravelDataLoaderTest.php +++ b/tests/BusProNet/DataLoader/TravelDataLoaderTest.php @@ -2,8 +2,8 @@ namespace App\Tests\BusProNet\DataLoader; -use App\BusProNet\XmlLoader\TravelLoader; use App\BusProNet\Model\Travel; +use App\BusProNet\XmlLoader\TravelLoader; use PHPUnit\Framework\TestCase; class TravelDataLoaderTest extends TestCase