diff --git a/Sources/API/.htaccess b/Sources/API/.htaccess deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/API/composer.json b/Sources/API/composer.json new file mode 100644 index 0000000..dd5a46e --- /dev/null +++ b/Sources/API/composer.json @@ -0,0 +1,8 @@ +{ + "require": { + "slim/slim": "4.*", + "slim/psr7": "^1.6", + "zircote/swagger-php": "^4.5", + "doctrine/annotations": "^1.14" + } +} diff --git a/Sources/API/composer.lock b/Sources/API/composer.lock new file mode 100644 index 0000000..e734c16 --- /dev/null +++ b/Sources/API/composer.lock @@ -0,0 +1,1385 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4ce9f1d66c47bb3eda2eab2ef793483c", + "packages": [ + { + "name": "doctrine/annotations", + "version": "1.14.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/9e034d7a70032d422169f27d8759e8d84abb4f51", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.1" + }, + "time": "2022-12-12T12:46:12+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" + }, + { + "name": "doctrine/lexer", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/3cf140b81e55d5d640f73367d829db7e3023ef69", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-12-11T10:51:23+00:00" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.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": "http://www.php-fig.org/" + } + ], + "description": "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/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.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": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^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": "http://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": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.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": "http://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/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "slim/psr7", + "version": "1.6", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/3471c22c1a0d26c51c78f6aeb06489d38cf46a4d", + "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0", + "symfony/polyfill-php80": "^1.26" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.3", + "ext-json": "*", + "http-interop/http-factory-tests": "^0.9.0", + "php-http/psr7-integration-tests": "dev-master", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.6" + }, + "time": "2022-11-05T18:50:24+00:00" + }, + { + "name": "slim/slim", + "version": "4.11.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7", + "reference": "b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.3", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.4", + "httpsoft/http-message": "^1.0", + "httpsoft/http-server-request": "^1.0", + "laminas/laminas-diactoros": "^2.17", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "slim/http": "^1.2", + "slim/psr7": "^1.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2022-11-06T16:33:39+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + }, + "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": "2022-11-25T10:21:52+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.0" + }, + "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": "2022-10-09T08:55:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f2570f21bd4adc3589aa3133323273995109bae0", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.0" + }, + "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": "2022-11-25T19:00:27+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.7", + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.5.1" + }, + "time": "2022-11-09T00:17:13+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/Sources/API/config/Autoload.php b/Sources/API/config/Autoload.php deleted file mode 100644 index 10eb20c..0000000 --- a/Sources/API/config/Autoload.php +++ /dev/null @@ -1,32 +0,0 @@ -$file

"; - if(file_exists($file)){ - include $file; - } - } - } -} - -?> \ No newline at end of file diff --git a/Sources/API/config/Config.php b/Sources/API/config/Config.php deleted file mode 100644 index 5b648a6..0000000 --- a/Sources/API/config/Config.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/Sources/API/config/Database.php b/Sources/API/config/Database.php new file mode 100644 index 0000000..cd59896 --- /dev/null +++ b/Sources/API/config/Database.php @@ -0,0 +1,22 @@ +conn = null; + + try{ + $this->conn = new PDO('mysql:host='.$this->host.';dbname='.$this->db_name, $this->username, $this->password); + $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch(PDOException $e){ + echo 'Connection Error :'.$e->getMessage(); + } + + return $this->conn; + } +} +?> \ No newline at end of file diff --git a/Sources/API/controller/.gitkeep b/Sources/API/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/API/controller/Controller.php b/Sources/API/controller/Controller.php deleted file mode 100644 index 81ef754..0000000 --- a/Sources/API/controller/Controller.php +++ /dev/null @@ -1,177 +0,0 @@ -executeQueryWithoutParameters($query); - - $query='INSERT INTO Devise VALUES("EUR","EURO"); - INSERT INTO Devise VALUES("USD","DOLLAR"); - INSERT INTO Devise VALUES("GBP","Livre Sterling"); - INSERT INTO Devise VALUES("JPY","YEN"); - INSERT INTO Devise VALUES("AUD","DOLLAR AUSTRALIEN"); - INSERT INTO Devise VALUES("NZD","DOLLAR NEO-ZELANDAIS"); - INSERT INTO Devise VALUES("ZAR","RANd"); - - - INSERT INTO Inscrit (nom,prenom,mail,mdp)VALUES("EVARD","LUCAS","lucasevard@gmail.com","test"); - INSERT INTO Inscrit (nom,prenom,mail,mdp)VALUES("MONCUL","STEPHANE","stef@gmail.com","teststef"); - INSERT INTO Inscrit (nom,prenom,mail,mdp)VALUES("MENFOUMETTOITOUTNU","RENAUD","renaudtoutnu@gmail.com","test000"); - INSERT INTO Inscrit (nom,prenom,mail,mdp)VALUES("YOUVOI","BENJAMIN","BENJAMIN@gmail.com","BENJAMIN"); - INSERT INTO Inscrit (nom,prenom,mail,mdp)VALUES("TUBEAU","RAOUL","raoullacouille@gmail.com","zizi"); - - INSERT INTO DeviseInscrit VALUES("EUR","1"); - INSERT INTO DeviseInscrit VALUES("JPY","2"); - INSERT INTO DeviseInscrit VALUES("USD","3"); - INSERT INTO DeviseInscrit VALUES("NZD","4"); - - - INSERT INtO Banque(nom,urlsite,urllogo) VALUES("BNP PARIBAS","mabanque","imagesitebnb.fr"); - INSERT INtO Banque(nom,urlsite,urllogo) VALUES("CREDIT AGRICOLE","credit-agricole.fr","imageca"); - INSERT INtO Banque(nom,urlsite,urllogo) VALUES("BANQUE POSTALE","labanquepostale.fr","imgbp"); - INSERT INtO Banque(nom,urlsite,urllogo) VALUES("CAISSE D EPARGNE","caisse-epargne.fr","imgcaissedepargne"); - - - INSERT INTO InscrBanque (nomBanque,idInscrit)VALUES("BNP PARIBAS","1"); - INSERT INTO InscrBanque (nomBanque,idInscrit)VALUES("CREDIT AGRICOLE","2"); - INSERT INTO InscrBanque (nomBanque,idInscrit)VALUES("BANQUE POSTALE","3"); - INSERT INTO InscrBanque (nomBanque,idInscrit)VALUES("CAISSE D EPARGNE","4"); - - - INSERT INTO Compte (nom,idInscritBanque)VALUES("LIVRET A","1"); - INSERT INTO Compte (nom,idInscritBanque)VALUES("LIVRET A","2"); - INSERT INTO Compte (nom,idInscritBanque)VALUES("LIVRET A","3"); - INSERT INTO Compte (nom,idInscritBanque)VALUES("LIVRET A","4"); - - - INSERT INTO Planification (nom,credit,compte,datep,datecrea,methodePayement) VALUES ("EDF","190","1",now(),now(),"CB"); - INSERT INTO Planification (nom,credit,compte,datep,datecrea,methodePayement) VALUES ("SPOTIFY","190","2",now(),now(),"Prélevement"); - INSERT INTO Planification (nom,credit,compte,datep,datecrea,methodePayement) VALUES ("NETFLIX","190","3",now(),now(),"Cheque"); - INSERT INTO Planification (nom,credit,compte,datep,datecrea,methodePayement) VALUES ("PLAYSTATION PLUS","190","4",now(),now(),"Espece");'; - - $con->ExecuteQueryWithoutParameters($query); - switch($url[0]){ - case "SELECT": - switch($url[1]){ - case "Inscrit": - $query = 'SELECT * FROM Inscrit'; - $con->executeQueryWithoutParameters($query); - $res = $con->getResults(); - print(json_encode($res)); - break; - } - break; - default: - echo "ERREUR"; - } - } -} - -?> \ No newline at end of file diff --git a/Sources/API/index.php b/Sources/API/index.php deleted file mode 100644 index 8ba788e..0000000 --- a/Sources/API/index.php +++ /dev/null @@ -1,13 +0,0 @@ - \ No newline at end of file diff --git a/Sources/API/modele/Connection.php b/Sources/API/modele/Connection.php deleted file mode 100644 index 9f3b119..0000000 --- a/Sources/API/modele/Connection.php +++ /dev/null @@ -1,36 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -} - -/** * @param string $query - * @param array $parameters * - * @return bool Returns `true` on success, `false` otherwise -*/ - -public function executeQuery(string $query, array $parameters = []) : bool{ - $this->stmt = parent::prepare($query); - foreach ($parameters as $name => $value) { - $this->stmt->bindValue($name, $value[0], $value[1]); - } - - return $this->stmt->execute(); -} - -public function getResults() : array { - return $this->stmt->fetchall(); -} - -public function executeQueryWithoutParameters($query) : bool{ - $this->stmt = parent::prepare($query); - return $this->stmt->execute(); -} -} - -?> \ No newline at end of file diff --git a/Sources/API/modele/MdlInscrit.php b/Sources/API/modele/MdlInscrit.php deleted file mode 100644 index ed71324..0000000 --- a/Sources/API/modele/MdlInscrit.php +++ /dev/null @@ -1,14 +0,0 @@ -selectAll(); - } -} - -?> \ No newline at end of file diff --git a/Sources/API/modele/gateways/InscritGateway.php b/Sources/API/modele/gateways/InscritGateway.php deleted file mode 100644 index de3bfa4..0000000 --- a/Sources/API/modele/gateways/InscritGateway.php +++ /dev/null @@ -1,17 +0,0 @@ -con=$con; - } - - public function selectAll(){ - $query="SELECT * FROM Inscrit;"; - $this->con->executeQueryWithoutParameters($query); - return $this->con->getResults(); - } -} - -?> \ No newline at end of file diff --git a/Sources/API/public/index.php b/Sources/API/public/index.php new file mode 100644 index 0000000..8e9fd6e --- /dev/null +++ b/Sources/API/public/index.php @@ -0,0 +1,20 @@ +get('/', function (Request $request, Response $response, $args) { + $response->getBody()->write("Hello world!"); + return $response; +}); + +require __DIR__.'/../routes/Inscrit.php'; + +$app->run(); +?> \ No newline at end of file diff --git a/Sources/API/routes/Inscrit.php b/Sources/API/routes/Inscrit.php new file mode 100644 index 0000000..54ebb12 --- /dev/null +++ b/Sources/API/routes/Inscrit.php @@ -0,0 +1,141 @@ +addBodyParsingMiddleware(); +$app->addRoutingMiddleware(); +$app->addErrorMiddleware(true, true, true); + +/** +* @OA\Get(path="/api/Inscrit", +* @OA\Response(response="200", description="Succes") +* @OA\Response(response="500", description="Bdd Error") +* ) +*/ +$app->get('/Inscrit/', function(Request $request, Response $response){ + $query = "SELECT * FROM Inscrit"; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->query($query); + $inscrits = $stmt->fetchAll(PDO::FETCH_OBJ); + + $db = null; + $response->getBody()->write(json_encode($inscrits)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}); + +$app->post('/Inscrit/FromMail/', function(Request $request, Response $response,array $args){ + $mail = $request->getParsedBody()["email"]; + $query = 'SELECT * FROM Inscrit WHERE mail=:mail'; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->prepare($query); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); + + $stmt->execute(); + $inscrit = $stmt->fetchAll(PDO::FETCH_OBJ); + + $db = null; + $response->getBody()->write(json_encode($inscrit)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}); + +$app->put('/Inscrit/UpdatePassword/', function(Request $request, Response $response, array $args){ + $mail = $request->getParsedBody()["email"]; + $password = $request->getParsedBody()["password"]; + $query = 'UPDATE Inscrit SET mdp=:password WHERE mail=:mail'; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->prepare($query); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); + $stmt->bindValue(':password', $password, PDO::PARAM_STR); + + $result = $stmt->execute(); + + $db = null; + $response->getBody()->write(json_encode($result)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}); + +$app->post('/Inscrit/add/', function(Request $request, Response $response, array $args){ + $nom = $request->getParsedBody()["nom"]; + $prenom = $request->getParsedBody()["prenom"]; + $mail = $request->getParsedbody()["email"]; + $password = $request->getParsedBody()["password"]; + + $query = "INSERT INTO Inscrit (nom, prenom, mail, mdp) VALUES (:nom, :prenom, :mail, :password);"; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->prepare($query); + $stmt->bindValue(':nom', $nom, PDO::PARAM_STR); + $stmt->bindValue(':prenom', $prenom, PDO::PARAM_STR); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); + $stmt->bindValue(':password', $password, PDO::PARAM_STR); + + $result = $stmt->execute(); + + $db = null; + $response->getBody()->write(json_encode($result)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}); +?> \ No newline at end of file diff --git a/Sources/API/vendor/autoload.php b/Sources/API/vendor/autoload.php new file mode 100644 index 0000000..86e67b0 --- /dev/null +++ b/Sources/API/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private + */ +function includeFile($file) +{ + include $file; +} diff --git a/Sources/API/vendor/composer/InstalledVersions.php b/Sources/API/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..d50e0c9 --- /dev/null +++ b/Sources/API/vendor/composer/InstalledVersions.php @@ -0,0 +1,350 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/Sources/API/vendor/composer/LICENSE b/Sources/API/vendor/composer/LICENSE new file mode 100644 index 0000000..62ecfd8 --- /dev/null +++ b/Sources/API/vendor/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/composer/autoload_classmap.php b/Sources/API/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..dc15c48 --- /dev/null +++ b/Sources/API/vendor/composer/autoload_classmap.php @@ -0,0 +1,15 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/Sources/API/vendor/composer/autoload_files.php b/Sources/API/vendor/composer/autoload_files.php new file mode 100644 index 0000000..33fe5e4 --- /dev/null +++ b/Sources/API/vendor/composer/autoload_files.php @@ -0,0 +1,14 @@ + $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', +); diff --git a/Sources/API/vendor/composer/autoload_namespaces.php b/Sources/API/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/Sources/API/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'), + 'Slim\\' => array($vendorDir . '/slim/slim/Slim'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'OpenApi\\' => array($vendorDir . '/zircote/swagger-php/src'), + 'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'), + 'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'), + 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), +); diff --git a/Sources/API/vendor/composer/autoload_real.php b/Sources/API/vendor/composer/autoload_real.php new file mode 100644 index 0000000..1db1e4c --- /dev/null +++ b/Sources/API/vendor/composer/autoload_real.php @@ -0,0 +1,80 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInita934429c0ea4f4482346c5d296943a81::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInita934429c0ea4f4482346c5d296943a81::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequirea934429c0ea4f4482346c5d296943a81($fileIdentifier, $file); + } + + return $loader; + } +} + +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ +function composerRequirea934429c0ea4f4482346c5d296943a81($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } +} diff --git a/Sources/API/vendor/composer/autoload_static.php b/Sources/API/vendor/composer/autoload_static.php new file mode 100644 index 0000000..c7f3e89 --- /dev/null +++ b/Sources/API/vendor/composer/autoload_static.php @@ -0,0 +1,143 @@ + __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\Finder\\' => 25, + 'Slim\\Psr7\\' => 10, + 'Slim\\' => 5, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Server\\' => 16, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + ), + 'O' => + array ( + 'OpenApi\\' => 8, + ), + 'F' => + array ( + 'Fig\\Http\\Message\\' => 17, + 'FastRoute\\' => 10, + ), + 'D' => + array ( + 'Doctrine\\Deprecations\\' => 22, + 'Doctrine\\Common\\Lexer\\' => 22, + 'Doctrine\\Common\\Annotations\\' => 28, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Slim\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/psr7/src', + ), + 'Slim\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/slim/Slim', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Http\\Server\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-server-handler/src', + 1 => __DIR__ . '/..' . '/psr/http-server-middleware/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'OpenApi\\' => + array ( + 0 => __DIR__ . '/..' . '/zircote/swagger-php/src', + ), + 'Fig\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/fig/http-message-util/src', + ), + 'FastRoute\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/fast-route/src', + ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/src', + ), + 'Doctrine\\Common\\Annotations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInita934429c0ea4f4482346c5d296943a81::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInita934429c0ea4f4482346c5d296943a81::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInita934429c0ea4f4482346c5d296943a81::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/Sources/API/vendor/composer/installed.json b/Sources/API/vendor/composer/installed.json new file mode 100644 index 0000000..b8e016a --- /dev/null +++ b/Sources/API/vendor/composer/installed.json @@ -0,0 +1,1435 @@ +{ + "packages": [ + { + "name": "doctrine/annotations", + "version": "1.14.1", + "version_normalized": "1.14.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/9e034d7a70032d422169f27d8759e8d84abb4f51", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "time": "2022-12-12T12:46:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.1" + }, + "install-path": "../doctrine/annotations" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2022-05-02T15:47:09+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "doctrine/lexer", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/3cf140b81e55d5d640f73367d829db7e3023ef69", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "time": "2022-12-11T10:51:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "install-path": "../doctrine/lexer" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "version_normalized": "1.1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "time": "2020-11-24T22:02:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "install-path": "../fig/http-message-util" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "time": "2018-02-13T20:26:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "install-path": "../nikic/fast-route" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "time": "2019-04-30T12:38:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "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/tree/master" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "time": "2018-10-30T16:46:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://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": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "install-path": "../psr/http-server-handler" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "time": "2018-10-30T17:12:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://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/master" + }, + "install-path": "../psr/http-server-middleware" + }, + { + "name": "psr/log", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-07-14T16:46:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "install-path": "../psr/log" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + }, + { + "name": "slim/psr7", + "version": "1.6", + "version_normalized": "1.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/3471c22c1a0d26c51c78f6aeb06489d38cf46a4d", + "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0", + "symfony/polyfill-php80": "^1.26" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.3", + "ext-json": "*", + "http-interop/http-factory-tests": "^0.9.0", + "php-http/psr7-integration-tests": "dev-master", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "time": "2022-11-05T18:50:24+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.6" + }, + "install-path": "../slim/psr7" + }, + { + "name": "slim/slim", + "version": "4.11.0", + "version_normalized": "4.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7", + "reference": "b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.3", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.4", + "httpsoft/http-message": "^1.0", + "httpsoft/http-server-request": "^1.0", + "laminas/laminas-diactoros": "^2.17", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "slim/http": "^1.2", + "slim/psr7": "^1.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "time": "2022-11-06T16:33:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "install-path": "../slim/slim" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2022-11-25T10:21:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + }, + "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" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/finder", + "version": "v6.2.0", + "version_normalized": "6.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "time": "2022-10-09T08:55:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.0" + }, + "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" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "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" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "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" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/yaml", + "version": "v6.2.0", + "version_normalized": "6.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f2570f21bd4adc3589aa3133323273995109bae0", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "time": "2022-11-25T19:00:27+00:00", + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.0" + }, + "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" + } + ], + "install-path": "../symfony/yaml" + }, + { + "name": "zircote/swagger-php", + "version": "4.5.1", + "version_normalized": "4.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.7", + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "time": "2022-11-09T00:17:13+00:00", + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.5.1" + }, + "install-path": "../zircote/swagger-php" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/Sources/API/vendor/composer/installed.php b/Sources/API/vendor/composer/installed.php new file mode 100644 index 0000000..d8400d0 --- /dev/null +++ b/Sources/API/vendor/composer/installed.php @@ -0,0 +1,224 @@ + array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => '0d6979f482a4dc4159775749125b647077c1dbcd', + 'name' => '__root__', + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => '0d6979f482a4dc4159775749125b647077c1dbcd', + 'dev_requirement' => false, + ), + 'doctrine/annotations' => array( + 'pretty_version' => '1.14.1', + 'version' => '1.14.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/annotations', + 'aliases' => array(), + 'reference' => '9e034d7a70032d422169f27d8759e8d84abb4f51', + 'dev_requirement' => false, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de', + 'dev_requirement' => false, + ), + 'doctrine/lexer' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/lexer', + 'aliases' => array(), + 'reference' => '3cf140b81e55d5d640f73367d829db7e3023ef69', + 'dev_requirement' => false, + ), + 'fig/http-message-util' => array( + 'pretty_version' => '1.1.5', + 'version' => '1.1.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../fig/http-message-util', + 'aliases' => array(), + 'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765', + 'dev_requirement' => false, + ), + 'nikic/fast-route' => array( + 'pretty_version' => 'v1.3.0', + 'version' => '1.3.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/fast-route', + 'aliases' => array(), + 'reference' => '181d480e08d9476e61381e04a71b34dc0432e812', + 'dev_requirement' => false, + ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'dev_requirement' => false, + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-server-handler' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-server-handler', + 'aliases' => array(), + 'reference' => 'aff2f80e33b7f026ec96bb42f63242dc50ffcae7', + 'dev_requirement' => false, + ), + 'psr/http-server-middleware' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-server-middleware', + 'aliases' => array(), + 'reference' => '2296f45510945530b9dceb8bcedb5cb84d40c5f5', + 'dev_requirement' => false, + ), + 'psr/log' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', + 'dev_requirement' => false, + ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'dev_requirement' => false, + ), + 'slim/psr7' => array( + 'pretty_version' => '1.6', + 'version' => '1.6.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../slim/psr7', + 'aliases' => array(), + 'reference' => '3471c22c1a0d26c51c78f6aeb06489d38cf46a4d', + 'dev_requirement' => false, + ), + 'slim/slim' => array( + 'pretty_version' => '4.11.0', + 'version' => '4.11.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../slim/slim', + 'aliases' => array(), + 'reference' => 'b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.2.0', + 'version' => '3.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => '1ee04c65529dea5d8744774d474e7cbd2f1206d3', + 'dev_requirement' => false, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v6.2.0', + 'version' => '6.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'reference' => 'eb2355f69519e4ef33f1835bca4c935f5d42e570', + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'dev_requirement' => false, + ), + 'symfony/yaml' => array( + 'pretty_version' => 'v6.2.0', + 'version' => '6.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/yaml', + 'aliases' => array(), + 'reference' => 'f2570f21bd4adc3589aa3133323273995109bae0', + 'dev_requirement' => false, + ), + 'zircote/swagger-php' => array( + 'pretty_version' => '4.5.1', + 'version' => '4.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../zircote/swagger-php', + 'aliases' => array(), + 'reference' => 'eb84fb4d65a327e604812fbddc6c27f69b9ed6e2', + 'dev_requirement' => false, + ), + ), +); diff --git a/Sources/API/vendor/composer/platform_check.php b/Sources/API/vendor/composer/platform_check.php new file mode 100644 index 0000000..4c3a5d6 --- /dev/null +++ b/Sources/API/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/Sources/API/vendor/doctrine/annotations/LICENSE b/Sources/API/vendor/doctrine/annotations/LICENSE new file mode 100644 index 0000000..5e781fc --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/annotations/README.md b/Sources/API/vendor/doctrine/annotations/README.md new file mode 100644 index 0000000..6b8c035 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/README.md @@ -0,0 +1,24 @@ +⚠️ PHP 8 introduced +[attributes](https://www.php.net/manual/en/language.attributes.overview.php), +which are a native replacement for annotations. As such, this library is +considered feature complete, and should receive exclusively bugfixes and +security fixes. + +# Doctrine Annotations + +[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) +[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) +[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) +[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations) + +Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). + +## Documentation + +See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). + +## Contributing + +When making a pull request, make sure your changes follow the +[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). diff --git a/Sources/API/vendor/doctrine/annotations/composer.json b/Sources/API/vendor/doctrine/annotations/composer.json new file mode 100644 index 0000000..e322d82 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/composer.json @@ -0,0 +1,72 @@ +{ + "name": "doctrine/annotations", + "description": "Docblock Annotations Parser", + "license": "MIT", + "type": "library", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "require": { + "php": "^7.1 || ^8.0", + "ext-tokenizer": "*", + "doctrine/lexer": "^1 || ^2", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", + "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" + }, + "files": [ + "tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php", + "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst b/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst new file mode 100644 index 0000000..2c3c428 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst @@ -0,0 +1,252 @@ +Handling Annotations +==================== + +There are several different approaches to handling annotations in PHP. +Doctrine Annotations maps docblock annotations to PHP classes. Because +not all docblock annotations are used for metadata purposes a filter is +applied to ignore or skip classes that are not Doctrine annotations. + +Take a look at the following code snippet: + +.. code-block:: php + + namespace MyProject\Entities; + + use Doctrine\ORM\Mapping AS ORM; + use Symfony\Component\Validator\Constraints AS Assert; + + /** + * @author Benjamin Eberlei + * @ORM\Entity + * @MyProject\Annotations\Foobarable + */ + class User + { + /** + * @ORM\Id @ORM\Column @ORM\GeneratedValue + * @dummy + * @var int + */ + private $id; + + /** + * @ORM\Column(type="string") + * @Assert\NotEmpty + * @Assert\Email + * @var string + */ + private $email; + } + +In this snippet you can see a variety of different docblock annotations: + +- Documentation annotations such as ``@var`` and ``@author``. These + annotations are ignored and never considered for throwing an + exception due to wrongly used annotations. +- Annotations imported through use statements. The statement ``use + Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace + available as ``@ORM\ClassName``. Same goes for the import of + ``@Assert``. +- The ``@dummy`` annotation. It is not a documentation annotation and + not ignored. For Doctrine Annotations it is not entirely clear how + to handle this annotation. Depending on the configuration an exception + (unknown annotation) will be thrown when parsing this annotation. +- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. + This is transformed directly into the given class name. + +How are these annotations loaded? From looking at the code you could +guess that the ORM Mapping, Assert Validation and the fully qualified +annotation can just be loaded using +the defined PHP autoloaders. This is not the case however: For error +handling reasons every check for class existence inside the +``AnnotationReader`` sets the second parameter $autoload +of ``class_exists($name, $autoload)`` to false. To work flawlessly the +``AnnotationReader`` requires silent autoloaders which many autoloaders are +not. Silent autoloading is NOT part of the `PSR-0 specification +`_ +for autoloading. + +This is why Doctrine Annotations uses its own autoloading mechanism +through a global registry. If you are wondering about the annotation +registry being global, there is no other way to solve the architectural +problems of autoloading annotation classes in a straightforward fashion. +Additionally if you think about PHP autoloading then you recognize it is +a global as well. + +To anticipate the configuration section, making the above PHP class work +with Doctrine Annotations requires this setup: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); + AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); + AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); + + $reader = new AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('dummy'); + +The second block with the annotation registry calls registers all the +three different annotation namespaces that are used. +Doctrine Annotations saves all its annotations in a single file, that is +why ``AnnotationRegistry#registerFile`` is used in contrast to +``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 +compatible loading mechanism for class to file names. + +In the third block, we create the actual ``AnnotationReader`` instance. +Note that we also add ``dummy`` to the global list of ignored +annotations for which we do not throw exceptions. Setting this is +necessary in our example case, otherwise ``@dummy`` would trigger an +exception to be thrown during the parsing of the docblock of +``MyProject\Entities\User#id``. + +Setup and Configuration +----------------------- + +To use the annotations library is simple, you just need to create a new +``AnnotationReader`` instance: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + +This creates a simple annotation reader with no caching other than in +memory (in php arrays). Since parsing docblocks can be expensive you +should cache this process by using a caching reader. + +To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``. +This reader decorates the original reader and stores all annotations in a PSR-6 +cache: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\PsrCachedReader; + + $cache = ... // instantiate a PSR-6 Cache pool + + $reader = new PsrCachedReader( + new AnnotationReader(), + $cache, + $debug = true + ); + +The ``debug`` flag is used here as well to invalidate the cache files +when the PHP class with annotations changed and should be used during +development. + +.. warning :: + + The ``AnnotationReader`` works and caches under the + assumption that all annotations of a doc-block are processed at + once. That means that annotation classes that do not exist and + aren't loaded and cannot be autoloaded (using the + AnnotationRegistry) would never be visible and not accessible if a + cache is used unless the cache is cleared and the annotations + requested again, this time with all annotations defined. + +By default the annotation reader returns a list of annotations with +numeric indexes. If you want your annotations to be indexed by their +class name you can wrap the reader in an ``IndexedReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\IndexedReader; + + $reader = new IndexedReader(new AnnotationReader()); + +.. warning:: + + You should never wrap the indexed reader inside a cached reader, + only the other way around. This way you can re-use the cache with + indexed or numeric keys, otherwise your code may experience failures + due to caching in a numerical or indexed format. + +Registering Annotations +~~~~~~~~~~~~~~~~~~~~~~~ + +As explained in the introduction, Doctrine Annotations uses its own +autoloading mechanism to determine if a given annotation has a +corresponding PHP class that can be autoloaded. For annotation +autoloading you have to configure the +``Doctrine\Common\Annotations\AnnotationRegistry``. There are three +different mechanisms to configure annotation autoloading: + +- Calling ``AnnotationRegistry#registerFile($file)`` to register a file + that contains one or more annotation classes. +- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = + null)`` to register that the given namespace contains annotations and + that their base directory is located at the given $dirs or in the + include path if ``NULL`` is passed. The given directories should *NOT* + be the directory where classes of the namespace are in, but the base + directory of the root namespace. The AnnotationRegistry uses a + namespace to directory separator approach to resolve the correct path. +- Calling ``AnnotationRegistry#registerLoader($callable)`` to register + an autoloader callback. The callback accepts the class as first and + only parameter and has to return ``true`` if the corresponding file + was found and included. + +.. note:: + + Loaders have to fail silently, if a class is not found even if it + matches for example the namespace prefix of that loader. Never is a + loader to throw a warning or exception if the loading failed + otherwise parsing doc block annotations will become a huge pain. + +A sample loader callback could look like: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationRegistry; + use Symfony\Component\ClassLoader\UniversalClassLoader; + + AnnotationRegistry::registerLoader(function($class) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + + if (file_exists("/my/base/path/" . $file)) { + // file_exists() makes sure that the loader fails silently + require "/my/base/path/" . $file; + } + }); + + $loader = new UniversalClassLoader(); + AnnotationRegistry::registerLoader(array($loader, "loadClass")); + + +Ignoring missing exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default an exception is thrown from the ``AnnotationReader`` if an +annotation was found that: + +- is not part of the list of ignored "documentation annotations"; +- was not imported through a use statement; +- is not a fully qualified class that exists. + +You can disable this behavior for specific names if your docblocks do +not follow strict requirements: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('foo'); + +PHP Imports +~~~~~~~~~~~ + +By default the annotation reader parses the use-statement of a php file +to gain access to the import rules and register them for the annotation +processing. Only if you are using PHP Imports can you validate the +correct usage of annotations and throw exceptions if you misspelled an +annotation. This mechanism is enabled by default. + +To ease the upgrade path, we still allow you to disable this mechanism. +Note however that we will remove this in future versions: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setEnabledPhpImports(false); diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst b/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst new file mode 100644 index 0000000..e8f79af --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst @@ -0,0 +1,443 @@ +Custom Annotation Classes +========================= + +If you want to define your own annotations, you just have to group them +in a namespace and register this namespace in the ``AnnotationRegistry``. +Annotation classes have to contain a class-level docblock with the text +``@Annotation``: + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** @Annotation */ + class Bar + { + // some code + } + +Inject annotation values +------------------------ + +The annotation parser checks if the annotation constructor has arguments, +if so then it will pass the value array, otherwise it will try to inject +values into public properties directly: + + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * + * Some Annotation using a constructor + */ + class Bar + { + private $foo; + + public function __construct(array $values) + { + $this->foo = $values['foo']; + } + } + + /** + * @Annotation + * + * Some Annotation without a constructor + */ + class Foo + { + public $bar; + } + +Optional: Constructors with Named Parameters +-------------------------------------------- + +Starting with Annotations v1.11 a new annotation instantiation strategy +is available that aims at compatibility of Annotation classes with the PHP 8 +attribute feature. You need to declare a constructor with regular parameter +names that match the named arguments in the annotation syntax. + +To enable this feature, you can tag your annotation class with +``@NamedArgumentConstructor`` (available from v1.12) or implement the +``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface +(available from v1.11 and deprecated as of v1.12). +When using the ``@NamedArgumentConstructor`` tag, the first argument of the +constructor is considered as the default one. + + +Usage with the ``@NamedArgumentConstructor`` tag + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @NamedArgumentConstructor + */ + class Bar implements NamedArgumentConstructorAnnotation + { + private $foo; + + public function __construct(string $foo) + { + $this->foo = $foo; + } + } + + /** Usable with @Bar(foo="baz") */ + /** Usable with @Bar("baz") */ + +In combination with PHP 8's constructor property promotion feature +you can simplify this to: + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @NamedArgumentConstructor + */ + class Bar implements NamedArgumentConstructorAnnotation + { + public function __construct(private string $foo) {} + } + + +Usage with the +``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` +interface (v1.11, deprecated as of v1.12): +.. code-block:: php + + namespace MyCompany\Annotations; + + use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; + + /** @Annotation */ + class Bar implements NamedArgumentConstructorAnnotation + { + private $foo; + + public function __construct(private string $foo) {} + } + + /** Usable with @Bar(foo="baz") */ + +Annotation Target +----------------- + +``@Target`` indicates the kinds of class elements to which an annotation +type is applicable. Then you could define one or more targets: + +- ``CLASS`` Allowed in class docblocks +- ``PROPERTY`` Allowed in property docblocks +- ``METHOD`` Allowed in the method docblocks +- ``FUNCTION`` Allowed in function dockblocks +- ``ALL`` Allowed in class, property, method and function docblocks +- ``ANNOTATION`` Allowed inside other annotations + +If the annotations is not allowed in the current context, an +``AnnotationException`` is thrown. + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + // some code + } + + /** + * @Annotation + * @Target("CLASS") + */ + class Foo + { + // some code + } + +Attribute types +--------------- + +The annotation parser checks the given parameters using the phpdoc +annotation ``@var``, The data type could be validated using the ``@var`` +annotation on the annotation properties or using the ``@Attributes`` and +``@Attribute`` annotations. + +If the data type does not match you get an ``AnnotationException`` + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + /** @var mixed */ + public $mixed; + + /** @var boolean */ + public $boolean; + + /** @var bool */ + public $bool; + + /** @var float */ + public $float; + + /** @var string */ + public $string; + + /** @var integer */ + public $integer; + + /** @var array */ + public $array; + + /** @var SomeAnnotationClass */ + public $annotation; + + /** @var array */ + public $arrayOfIntegers; + + /** @var array */ + public $arrayOfAnnotations; + } + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + * @Attributes({ + * @Attribute("stringProperty", type = "string"), + * @Attribute("annotProperty", type = "SomeAnnotationClass"), + * }) + */ + class Foo + { + public function __construct(array $values) + { + $this->stringProperty = $values['stringProperty']; + $this->annotProperty = $values['annotProperty']; + } + + // some code + } + +Annotation Required +------------------- + +``@Required`` indicates that the field must be specified when the +annotation is used. If it is not used you get an ``AnnotationException`` +stating that this value can not be null. + +Declaring a required field: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Foo + { + /** @Required */ + public $requiredField; + } + +Usage: + +.. code-block:: php + + /** @Foo(requiredField="value") */ + public $direction; // Valid + + /** @Foo */ + public $direction; // Required field missing, throws an AnnotationException + + +Enumerated values +----------------- + +- An annotation property marked with ``@Enum`` is a field that accepts a + fixed set of scalar values. +- You should use ``@Enum`` fields any time you need to represent fixed + values. +- The annotation parser checks the given value and throws an + ``AnnotationException`` if the value does not match. + + +Declaring an enumerated property: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Direction + { + /** + * @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) + */ + public $value; + } + +Annotation usage: + +.. code-block:: php + + /** @Direction("NORTH") */ + public $direction; // Valid value + + /** @Direction("NORTHEAST") */ + public $direction; // Invalid value, throws an AnnotationException + + +Constants +--------- + +The use of constants and class constants is available on the annotations +parser. + +The following usages are allowed: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + use MyCompany\Entity\SomeClass; + + /** + * @Foo(PHP_EOL) + * @Bar(Bar::FOO) + * @Foo({SomeClass::FOO, SomeClass::BAR}) + * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) + */ + class User + { + } + + +Be careful with constants and the cache ! + +.. note:: + + The cached reader will not re-evaluate each time an annotation is + loaded from cache. When a constant is changed the cache must be + cleaned. + + +Usage +----- + +Using the library API is simple. Using the annotations described in the +previous section, you can now annotate other classes with your +annotations: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + + /** + * @Foo(bar="foo") + * @Bar(foo="bar") + */ + class User + { + } + +Now we can write a script to get the annotations above: + +.. code-block:: php + + $reflClass = new ReflectionClass('MyCompany\Entity\User'); + $classAnnotations = $reader->getClassAnnotations($reflClass); + + foreach ($classAnnotations AS $annot) { + if ($annot instanceof \MyCompany\Annotations\Foo) { + echo $annot->bar; // prints "foo"; + } else if ($annot instanceof \MyCompany\Annotations\Bar) { + echo $annot->foo; // prints "bar"; + } + } + +You have a complete API for retrieving annotation class instances from a +class, property or method docblock: + + +Reader API +~~~~~~~~~~ + +Access all annotations of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotations(\ReflectionClass $class); + +Access one annotation of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotation(\ReflectionClass $class, $annotationName); + +Access all annotations of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotations(\ReflectionMethod $method); + +Access one annotation of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + +Access all annotations of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotations(\ReflectionProperty $property); + +Access one annotation of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); + +Access all annotations of a function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getFunctionAnnotations(\ReflectionFunction $property); + +Access one annotation of a function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName); diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/index.rst b/Sources/API/vendor/doctrine/annotations/docs/en/index.rst new file mode 100644 index 0000000..7caffb5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/index.rst @@ -0,0 +1,110 @@ +Deprecation notice +================== + +PHP 8 introduced `attributes +`_, +which are a native replacement for annotations. As such, this library is +considered feature complete, and should receive exclusively bugfixes and +security fixes. + +Introduction +============ + +Doctrine Annotations allows to implement custom annotation +functionality for PHP classes and functions. + +.. code-block:: php + + class Foo + { + /** + * @MyAnnotation(myProperty="value") + */ + private $bar; + } + +Annotations aren't implemented in PHP itself which is why this component +offers a way to use the PHP doc-blocks as a place for the well known +annotation syntax using the ``@`` char. + +Annotations in Doctrine are used for the ORM configuration to build the +class mapping, but it can be used in other projects for other purposes +too. + +Installation +============ + +You can install the Annotation component with composer: + +.. code-block:: + +   $ composer require doctrine/annotations + +Create an annotation class +========================== + +An annotation class is a representation of the later used annotation +configuration in classes. The annotation class of the previous example +looks like this: + +.. code-block:: php + + /** + * @Annotation + */ + final class MyAnnotation + { + public $myProperty; + } + +The annotation class is declared as an annotation by ``@Annotation``. + +:ref:`Read more about custom annotations. ` + +Reading annotations +=================== + +The access to the annotations happens by reflection of the class or function +containing them. There are multiple reader-classes implementing the +``Doctrine\Common\Annotations\Reader`` interface, that can access the +annotations of a class. A common one is +``Doctrine\Common\Annotations\AnnotationReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + // Deprecated and will be removed in 2.0 but currently needed + AnnotationRegistry::registerLoader('class_exists'); + + $reflectionClass = new ReflectionClass(Foo::class); + $property = $reflectionClass->getProperty('bar'); + + $reader = new AnnotationReader(); + $myAnnotation = $reader->getPropertyAnnotation( + $property, + MyAnnotation::class + ); + + echo $myAnnotation->myProperty; // result: "value" + +Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works +if you already have an autoloader configured (i.e. composer autoloader). +Otherwise, :ref:`please take a look to the other annotation autoload mechanisms `. + +A reader has multiple methods to access the annotations of a class or +function. + +:ref:`Read more about handling annotations. ` + +IDE Support +----------- + +Some IDEs already provide support for annotations: + +- Eclipse via the `Symfony2 Plugin `_ +- PhpStorm via the `PHP Annotations Plugin `_ or the `Symfony Plugin `_ + +.. _Read more about handling annotations.: annotations +.. _Read more about custom annotations.: custom diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst b/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst new file mode 100644 index 0000000..6f5d13c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst @@ -0,0 +1,6 @@ +.. toctree:: + :depth: 3 + + index + annotations + custom diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 0000000..9cae3da --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,57 @@ + $data Key-value for properties to be defined in this class. */ + final public function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name. + * + * @throws BadMethodCallException + */ + public function __get($name) + { + throw new BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + ); + } + + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unknown property name. + * @param mixed $value Property value. + * + * @throws BadMethodCallException + */ + public function __set($name, $value) + { + throw new BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + ); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php new file mode 100644 index 0000000..b1f8514 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php @@ -0,0 +1,21 @@ + */ + public $value; +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php new file mode 100644 index 0000000..6f24d9f --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php @@ -0,0 +1,69 @@ + */ + public $value; + + /** + * Literal target declaration. + * + * @var mixed[] + */ + public $literal; + + /** + * @phpstan-param array{literal?: mixed[], value: list} $values + * + * @throws InvalidArgumentException + */ + public function __construct(array $values) + { + if (! isset($values['literal'])) { + $values['literal'] = []; + } + + foreach ($values['value'] as $var) { + if (! is_scalar($var)) { + throw new InvalidArgumentException(sprintf( + '@Enum supports only scalar values "%s" given.', + is_object($var) ? get_class($var) : gettype($var) + )); + } + } + + foreach ($values['literal'] as $key => $var) { + if (! in_array($key, $values['value'])) { + throw new InvalidArgumentException(sprintf( + 'Undefined enumerator value "%s" for literal "%s".', + $key, + $var + )); + } + } + + $this->value = $values['value']; + $this->literal = $values['literal']; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php new file mode 100644 index 0000000..97a15c2 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php @@ -0,0 +1,43 @@ + */ + public $names; + + /** + * @phpstan-param array{value: string|list} $values + * + * @throws RuntimeException + */ + public function __construct(array $values) + { + if (is_string($values['value'])) { + $values['value'] = [$values['value']]; + } + + if (! is_array($values['value'])) { + throw new RuntimeException(sprintf( + '@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', + json_encode($values['value']) + )); + } + + $this->names = $values['value']; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php new file mode 100644 index 0000000..1690601 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php @@ -0,0 +1,13 @@ + */ + private static $map = [ + 'ALL' => self::TARGET_ALL, + 'CLASS' => self::TARGET_CLASS, + 'METHOD' => self::TARGET_METHOD, + 'PROPERTY' => self::TARGET_PROPERTY, + 'FUNCTION' => self::TARGET_FUNCTION, + 'ANNOTATION' => self::TARGET_ANNOTATION, + ]; + + /** @phpstan-var list */ + public $value; + + /** + * Targets as bitmask. + * + * @var int + */ + public $targets; + + /** + * Literal target declaration. + * + * @var string + */ + public $literal; + + /** + * @phpstan-param array{value?: string|list} $values + * + * @throws InvalidArgumentException + */ + public function __construct(array $values) + { + if (! isset($values['value'])) { + $values['value'] = null; + } + + if (is_string($values['value'])) { + $values['value'] = [$values['value']]; + } + + if (! is_array($values['value'])) { + throw new InvalidArgumentException( + sprintf( + '@Target expects either a string value, or an array of strings, "%s" given.', + is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) + ) + ); + } + + $bitmask = 0; + foreach ($values['value'] as $literal) { + if (! isset(self::$map[$literal])) { + throw new InvalidArgumentException( + sprintf( + 'Invalid Target "%s". Available targets: [%s]', + $literal, + implode(', ', array_keys(self::$map)) + ) + ); + } + + $bitmask |= self::$map[$literal]; + } + + $this->targets = $bitmask; + $this->value = $values['value']; + $this->literal = implode(', ', $this->value); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php new file mode 100644 index 0000000..dcdfe4d --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -0,0 +1,167 @@ + $available + * + * @return AnnotationException + */ + public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) + { + return new self(sprintf( + '[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', + $attributeName, + $annotationName, + $context, + implode(', ', $available), + is_object($given) ? get_class($given) : $given + )); + } + + /** @return AnnotationException */ + public static function optimizerPlusSaveComments() + { + return new self( + 'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.' + ); + } + + /** @return AnnotationException */ + public static function optimizerPlusLoadComments() + { + return new self( + 'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.' + ); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 0000000..1f538ee --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,389 @@ + + */ + private static $globalImports = [ + 'ignoreannotation' => Annotation\IgnoreAnnotation::class, + ]; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNamespaces = []; + + /** + * Add a new annotation to the globally ignored annotation names with regard to exception handling. + * + * @param string $name + */ + public static function addGlobalIgnoredName($name) + { + self::$globalIgnoredNames[$name] = true; + } + + /** + * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. + * + * @param string $namespace + */ + public static function addGlobalIgnoredNamespace($namespace) + { + self::$globalIgnoredNamespaces[$namespace] = true; + } + + /** + * Annotations parser. + * + * @var DocParser + */ + private $parser; + + /** + * Annotations parser used to collect parsing metadata. + * + * @var DocParser + */ + private $preParser; + + /** + * PHP parser used to collect imports. + * + * @var PhpParser + */ + private $phpParser; + + /** + * In-memory cache mechanism to store imported annotations per class. + * + * @psalm-var array<'class'|'function', array>> + */ + private $imports = []; + + /** + * In-memory cache mechanism to store ignored annotations per class. + * + * @psalm-var array<'class'|'function', array>> + */ + private $ignoredAnnotationNames = []; + + /** + * Initializes a new AnnotationReader. + * + * @throws AnnotationException + */ + public function __construct(?DocParser $parser = null) + { + if ( + extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || + ini_get('opcache.save_comments') === '0') + ) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + // Make sure that the IgnoreAnnotation annotation is loaded + class_exists(IgnoreAnnotation::class); + + $this->parser = $parser ?: new DocParser(); + + $this->preParser = new DocParser(); + + $this->preParser->setImports(self::$globalImports); + $this->preParser->setIgnoreNotImportedAnnotations(true); + $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); + + $this->phpParser = new PhpParser(); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $this->parser->setTarget(Target::TARGET_CLASS); + $this->parser->setImports($this->getImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $context = 'property ' . $class->getName() . '::$' . $property->getName(); + + $this->parser->setTarget(Target::TARGET_PROPERTY); + $this->parser->setImports($this->getPropertyImports($property)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($property->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $this->parser->setTarget(Target::TARGET_METHOD); + $this->parser->setImports($this->getMethodImports($method)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($method->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets the annotations applied to a function. + * + * @phpstan-return list An array of Annotations. + */ + public function getFunctionAnnotations(ReflectionFunction $function): array + { + $context = 'function ' . $function->getName(); + + $this->parser->setTarget(Target::TARGET_FUNCTION); + $this->parser->setImports($this->getImports($function)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($function->getDocComment(), $context); + } + + /** + * Gets a function annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) + { + $annotations = $this->getFunctionAnnotations($function); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Returns the ignored annotations for the given class or function. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @return array + */ + private function getIgnoredAnnotationNames($reflection): array + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + if (isset($this->ignoredAnnotationNames[$type][$name])) { + return $this->ignoredAnnotationNames[$type][$name]; + } + + $this->collectParsingMetadata($reflection); + + return $this->ignoredAnnotationNames[$type][$name]; + } + + /** + * Retrieves imports for a class or a function. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @return array + */ + private function getImports($reflection): array + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + if (isset($this->imports[$type][$name])) { + return $this->imports[$type][$name]; + } + + $this->collectParsingMetadata($reflection); + + return $this->imports[$type][$name]; + } + + /** + * Retrieves imports for methods. + * + * @return array + */ + private function getMethodImports(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $classImports = $this->getImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if ( + ! $trait->hasMethod($method->getName()) + || $trait->getFileName() !== $method->getFileName() + ) { + continue; + } + + $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); + } + + return array_merge($classImports, $traitImports); + } + + /** + * Retrieves imports for properties. + * + * @return array + */ + private function getPropertyImports(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $classImports = $this->getImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if (! $trait->hasProperty($property->getName())) { + continue; + } + + $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); + } + + return array_merge($classImports, $traitImports); + } + + /** + * Collects parsing metadata for a given class or function. + * + * @param ReflectionClass|ReflectionFunction $reflection + */ + private function collectParsingMetadata($reflection): void + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + $ignoredAnnotationNames = self::$globalIgnoredNames; + $annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); + + foreach ($annotations as $annotation) { + if (! ($annotation instanceof IgnoreAnnotation)) { + continue; + } + + foreach ($annotation->names as $annot) { + $ignoredAnnotationNames[$annot] = true; + } + } + + $this->imports[$type][$name] = array_merge( + self::$globalImports, + $this->phpParser->parseUseStatements($reflection), + [ + '__NAMESPACE__' => $reflection->getNamespaceName(), + 'self' => $name, + ] + ); + + $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php new file mode 100644 index 0000000..259d497 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -0,0 +1,190 @@ +|null $dirs + */ + public static function registerAutoloadNamespace(string $namespace, $dirs = null): void + { + self::$autoloadNamespaces[$namespace] = $dirs; + } + + /** + * Registers multiple namespaces. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + * + * @param string[][]|string[]|null[] $namespaces indexed by namespace name + */ + public static function registerAutoloadNamespaces(array $namespaces): void + { + self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); + } + + /** + * Registers an autoloading callable for annotations, much like spl_autoload_register(). + * + * NOTE: These class loaders HAVE to be silent when a class was not found! + * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerLoader(callable $callable): void + { + // Reset our static cache now that we have a new loader to work with + self::$failedToAutoload = []; + self::$loaders[] = $callable; + } + + /** + * Registers an autoloading callable for annotations, if it is not already registered + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerUniqueLoader(callable $callable): void + { + if (in_array($callable, self::$loaders, true)) { + return; + } + + self::registerLoader($callable); + } + + /** + * Autoloads an annotation class silently. + */ + public static function loadAnnotationClass(string $class): bool + { + if (class_exists($class, false)) { + return true; + } + + if (array_key_exists($class, self::$failedToAutoload)) { + return false; + } + + foreach (self::$autoloadNamespaces as $namespace => $dirs) { + if (strpos($class, $namespace) !== 0) { + continue; + } + + $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; + + if ($dirs === null) { + $path = stream_resolve_include_path($file); + if ($path) { + require $path; + + return true; + } + } else { + foreach ((array) $dirs as $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { + require $dir . DIRECTORY_SEPARATOR . $file; + + return true; + } + } + } + } + + foreach (self::$loaders as $loader) { + if ($loader($class) === true) { + return true; + } + } + + if ( + self::$loaders === [] && + self::$autoloadNamespaces === [] && + self::$registerFileUsed === false && + class_exists($class) + ) { + return true; + } + + self::$failedToAutoload[$class] = null; + + return false; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php new file mode 100644 index 0000000..85dbefa --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php @@ -0,0 +1,266 @@ +> */ + private $loadedAnnotations = []; + + /** @var int[] */ + private $loadedFilemtimes = []; + + /** @param bool $debug */ + public function __construct(Reader $reader, Cache $cache, $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (bool) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getClassAnnotations($class); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName() . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getPropertyAnnotations($property); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName() . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getMethodAnnotations($method); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + $this->loadedFilemtimes = []; + } + + /** + * Fetches a value from the cache. + * + * @param string $cacheKey The cache key. + * + * @return mixed The cached value or false when the value is not in cache. + */ + private function fetchFromCache($cacheKey, ReflectionClass $class) + { + $data = $this->cache->fetch($cacheKey); + if ($data !== false) { + if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { + return $data; + } + } + + return false; + } + + /** + * Saves a value to the cache. + * + * @param string $cacheKey The cache key. + * @param mixed $value The value. + * + * @return void + */ + private function saveToCache($cacheKey, $value) + { + $this->cache->save($cacheKey, $value); + if (! $this->debug) { + return; + } + + $this->cache->save('[C]' . $cacheKey, time()); + } + + /** + * Checks if the cache is fresh. + * + * @param string $cacheKey + * + * @return bool + */ + private function isCacheFresh($cacheKey, ReflectionClass $class) + { + $lastModification = $this->getLastModification($class); + if ($lastModification === 0) { + return true; + } + + return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; + } + + /** + * Returns the time the class was last modified, testing traits and parents + */ + private function getLastModification(ReflectionClass $class): int + { + $filename = $class->getFileName(); + + if (isset($this->loadedFilemtimes[$filename])) { + return $this->loadedFilemtimes[$filename]; + } + + $parent = $class->getParentClass(); + + $lastModification = max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $class->getTraits()), + array_map(function (ReflectionClass $class): int { + return $this->getLastModification($class); + }, $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + + assert($lastModification !== false); + + return $this->loadedFilemtimes[$filename] = $lastModification; + } + + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int + { + $fileName = $reflectionTrait->getFileName(); + + if (isset($this->loadedFilemtimes[$fileName])) { + return $this->loadedFilemtimes[$fileName]; + } + + $lastModificationTime = max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $reflectionTrait->getTraits()) + )); + + assert($lastModificationTime !== false); + + return $this->loadedFilemtimes[$fileName] = $lastModificationTime; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php new file mode 100644 index 0000000..dbf60c0 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php @@ -0,0 +1,143 @@ + + */ +final class DocLexer extends AbstractLexer +{ + public const T_NONE = 1; + public const T_INTEGER = 2; + public const T_STRING = 3; + public const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + public const T_IDENTIFIER = 100; + public const T_AT = 101; + public const T_CLOSE_CURLY_BRACES = 102; + public const T_CLOSE_PARENTHESIS = 103; + public const T_COMMA = 104; + public const T_EQUALS = 105; + public const T_FALSE = 106; + public const T_NAMESPACE_SEPARATOR = 107; + public const T_OPEN_CURLY_BRACES = 108; + public const T_OPEN_PARENTHESIS = 109; + public const T_TRUE = 110; + public const T_NULL = 111; + public const T_COLON = 112; + public const T_MINUS = 113; + + /** @var array */ + protected $noCase = [ + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '-' => self::T_MINUS, + '\\' => self::T_NAMESPACE_SEPARATOR, + ]; + + /** @var array */ + protected $withCase = [ + 'true' => self::T_TRUE, + 'false' => self::T_FALSE, + 'null' => self::T_NULL, + ]; + + /** + * Whether the next token starts immediately, or if there were + * non-captured symbols before that + */ + public function nextTokenIsAdjacent(): bool + { + return $this->token === null + || ($this->lookahead !== null + && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); + } + + /** + * {@inheritdoc} + */ + protected function getCatchablePatterns() + { + return [ + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNonCatchablePatterns() + { + return ['\s+', '\*+', '(.)']; + } + + /** + * {@inheritdoc} + */ + protected function getType(&$value) + { + $type = self::T_NONE; + + if ($value[0] === '"') { + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { + return self::T_IDENTIFIER; + } + + $lowerValue = strtolower($value); + + if (isset($this->withCase[$lowerValue])) { + return $this->withCase[$lowerValue]; + } + + // Checking numeric value + if (is_numeric($value)) { + return strpos($value, '.') !== false || stripos($value, 'e') !== false + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } + + /** @return array{value: int|string, type:self::T_*|null, position:int} */ + public function peek(): ?array + { + $token = parent::peek(); + + if ($token === null) { + return null; + } + + return (array) $token; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php new file mode 100644 index 0000000..4133fe5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php @@ -0,0 +1,1485 @@ + + */ + private static $classIdentifiers = [ + DocLexer::T_IDENTIFIER, + DocLexer::T_TRUE, + DocLexer::T_FALSE, + DocLexer::T_NULL, + ]; + + /** + * The lexer. + * + * @var DocLexer + */ + private $lexer; + + /** + * Current target context. + * + * @var int + */ + private $target; + + /** + * Doc parser used to collect annotation target. + * + * @var DocParser + */ + private static $metadataParser; + + /** + * Flag to control if the current annotation is nested or not. + * + * @var bool + */ + private $isNestedAnnotation = false; + + /** + * Hashmap containing all use-statements that are to be used when parsing + * the given doc block. + * + * @var array + */ + private $imports = []; + + /** + * This hashmap is used internally to cache results of class_exists() + * look-ups. + * + * @var array + */ + private $classExists = []; + + /** + * Whether annotations that have not been imported should be ignored. + * + * @var bool + */ + private $ignoreNotImportedAnnotations = false; + + /** + * An array of default namespaces if operating in simple mode. + * + * @var string[] + */ + private $namespaces = []; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names must be the raw names as used in the class, not the fully qualified + * + * @var bool[] indexed by annotation name + */ + private $ignoredAnnotationNames = []; + + /** + * A list with annotations in namespaced format + * that are not causing exceptions when not resolved to an annotation class. + * + * @var bool[] indexed by namespace name + */ + private $ignoredAnnotationNamespaces = []; + + /** @var string */ + private $context = ''; + + /** + * Hash-map for caching annotation metadata. + * + * @var array + */ + private static $annotationMetadata = [ + Annotation\Target::class => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'has_named_argument_constructor' => false, + 'properties' => [], + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'attribute_types' => [ + 'value' => [ + 'required' => false, + 'type' => 'array', + 'array_type' => 'string', + 'value' => 'array', + ], + ], + ], + Annotation\Attribute::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_ANNOTATION', + 'targets' => Target::TARGET_ANNOTATION, + 'default_property' => 'name', + 'properties' => [ + 'name' => 'name', + 'type' => 'type', + 'required' => 'required', + ], + 'attribute_types' => [ + 'value' => [ + 'required' => true, + 'type' => 'string', + 'value' => 'string', + ], + 'type' => [ + 'required' => true, + 'type' => 'string', + 'value' => 'string', + ], + 'required' => [ + 'required' => false, + 'type' => 'boolean', + 'value' => 'boolean', + ], + ], + ], + Annotation\Attributes::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'properties' => ['value' => 'value'], + 'attribute_types' => [ + 'value' => [ + 'type' => 'array', + 'required' => true, + 'array_type' => Annotation\Attribute::class, + 'value' => 'array<' . Annotation\Attribute::class . '>', + ], + ], + ], + Annotation\Enum::class => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_PROPERTY', + 'targets' => Target::TARGET_PROPERTY, + 'default_property' => 'value', + 'properties' => ['value' => 'value'], + 'attribute_types' => [ + 'value' => [ + 'type' => 'array', + 'required' => true, + ], + 'literal' => [ + 'type' => 'array', + 'required' => false, + ], + ], + ], + Annotation\NamedArgumentConstructor::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => null, + 'properties' => [], + 'attribute_types' => [], + ], + ]; + + /** + * Hash-map for handle types declaration. + * + * @var array + */ + private static $typeMap = [ + 'float' => 'double', + 'bool' => 'boolean', + // allow uppercase Boolean in honor of George Boole + 'Boolean' => 'boolean', + 'int' => 'integer', + ]; + + /** + * Constructs a new DocParser. + */ + public function __construct() + { + $this->lexer = new DocLexer(); + } + + /** + * Sets the annotation names that are ignored during the parsing process. + * + * The names are supposed to be the raw names as used in the class, not the + * fully qualified class names. + * + * @param bool[] $names indexed by annotation name + * + * @return void + */ + public function setIgnoredAnnotationNames(array $names) + { + $this->ignoredAnnotationNames = $names; + } + + /** + * Sets the annotation namespaces that are ignored during the parsing process. + * + * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name + * + * @return void + */ + public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) + { + $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; + } + + /** + * Sets ignore on not-imported annotations. + * + * @param bool $bool + * + * @return void + */ + public function setIgnoreNotImportedAnnotations($bool) + { + $this->ignoreNotImportedAnnotations = (bool) $bool; + } + + /** + * Sets the default namespaces. + * + * @param string $namespace + * + * @return void + * + * @throws RuntimeException + */ + public function addNamespace($namespace) + { + if ($this->imports) { + throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->namespaces[] = $namespace; + } + + /** + * Sets the imports. + * + * @param array $imports + * + * @return void + * + * @throws RuntimeException + */ + public function setImports(array $imports) + { + if ($this->namespaces) { + throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->imports = $imports; + } + + /** + * Sets current target context as bitmask. + * + * @param int $target + * + * @return void + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Parses the given docblock string for annotations. + * + * @param string $input The docblock string to parse. + * @param string $context The parsing context. + * + * @phpstan-return list Array of annotations. If no annotations are found, an empty array is returned. + * + * @throws AnnotationException + * @throws ReflectionException + */ + public function parse($input, $context = '') + { + $pos = $this->findInitialTokenPosition($input); + if ($pos === null) { + return []; + } + + $this->context = $context; + + $this->lexer->setInput(trim(substr($input, $pos), '* /')); + $this->lexer->moveNext(); + + return $this->Annotations(); + } + + /** + * Finds the first valid annotation + * + * @param string $input The docblock string to parse + */ + private function findInitialTokenPosition($input): ?int + { + $pos = 0; + + // search for first valid annotation + while (($pos = strpos($input, '@', $pos)) !== false) { + $preceding = substr($input, $pos - 1, 1); + + // if the @ is preceded by a space, a tab or * it is valid + if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { + return $pos; + } + + $pos++; + } + + return null; + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param int $token Type of token. + * + * @return bool True if tokens match; false otherwise. + * + * @throws AnnotationException + */ + private function match(int $token): bool + { + if (! $this->lexer->isNextToken($token)) { + throw $this->syntaxError($this->lexer->getLiteral($token)); + } + + return $this->lexer->moveNext(); + } + + /** + * Attempts to match the current lookahead token with any of the given tokens. + * + * If any of them matches, this method updates the lookahead token; otherwise + * a syntax error is raised. + * + * @phpstan-param list $tokens + * + * @throws AnnotationException + */ + private function matchAny(array $tokens): bool + { + if (! $this->lexer->isNextTokenAny($tokens)) { + throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); + } + + return $this->lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param mixed[]|null $token Optional token. + */ + private function syntaxError(string $expected, ?array $token = null): AnnotationException + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $message = sprintf('Expected %s, got ', $expected); + $message .= $this->lexer->lookahead === null + ? 'end of string' + : sprintf("'%s' at position %s", $token['value'], $token['position']); + + if (strlen($this->context)) { + $message .= ' in ' . $this->context; + } + + $message .= '.'; + + return AnnotationException::syntaxError($message); + } + + /** + * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism + * but uses the {@link AnnotationRegistry} to load classes. + * + * @param class-string $fqcn + */ + private function classExists(string $fqcn): bool + { + if (isset($this->classExists[$fqcn])) { + return $this->classExists[$fqcn]; + } + + // first check if the class already exists, maybe loaded through another AnnotationReader + if (class_exists($fqcn, false)) { + return $this->classExists[$fqcn] = true; + } + + // final check, does this class exist? + return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); + } + + /** + * Collects parsing metadata for a given annotation class + * + * @param class-string $name The annotation name + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function collectAnnotationMetadata(string $name): void + { + if (self::$metadataParser === null) { + self::$metadataParser = new self(); + + self::$metadataParser->setIgnoreNotImportedAnnotations(true); + self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); + self::$metadataParser->setImports([ + 'enum' => Enum::class, + 'target' => Target::class, + 'attribute' => Attribute::class, + 'attributes' => Attributes::class, + 'namedargumentconstructor' => NamedArgumentConstructor::class, + ]); + + // Make sure that annotations from metadata are loaded + class_exists(Enum::class); + class_exists(Target::class); + class_exists(Attribute::class); + class_exists(Attributes::class); + class_exists(NamedArgumentConstructor::class); + } + + $class = new ReflectionClass($name); + $docComment = $class->getDocComment(); + + // Sets default values for annotation metadata + $constructor = $class->getConstructor(); + $metadata = [ + 'default_property' => null, + 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0, + 'constructor_args' => [], + 'properties' => [], + 'property_types' => [], + 'attribute_types' => [], + 'targets_literal' => null, + 'targets' => Target::TARGET_ALL, + 'is_annotation' => strpos($docComment, '@Annotation') !== false, + ]; + + $metadata['has_named_argument_constructor'] = $metadata['has_constructor'] + && $class->implementsInterface(NamedArgumentConstructorAnnotation::class); + + // verify that the class is really meant to be an annotation + if ($metadata['is_annotation']) { + self::$metadataParser->setTarget(Target::TARGET_CLASS); + + foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { + if ($annotation instanceof Target) { + $metadata['targets'] = $annotation->targets; + $metadata['targets_literal'] = $annotation->literal; + + continue; + } + + if ($annotation instanceof NamedArgumentConstructor) { + $metadata['has_named_argument_constructor'] = $metadata['has_constructor']; + if ($metadata['has_named_argument_constructor']) { + // choose the first argument as the default property + $metadata['default_property'] = $constructor->getParameters()[0]->getName(); + } + } + + if (! ($annotation instanceof Attributes)) { + continue; + } + + foreach ($annotation->value as $attribute) { + $this->collectAttributeTypeMetadata($metadata, $attribute); + } + } + + // if not has a constructor will inject values into public properties + if ($metadata['has_constructor'] === false) { + // collect all public properties + foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $metadata['properties'][$property->name] = $property->name; + + $propertyComment = $property->getDocComment(); + if ($propertyComment === false) { + continue; + } + + $attribute = new Attribute(); + + $attribute->required = (strpos($propertyComment, '@Required') !== false); + $attribute->name = $property->name; + $attribute->type = (strpos($propertyComment, '@var') !== false && + preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches)) + ? $matches[1] + : 'mixed'; + + $this->collectAttributeTypeMetadata($metadata, $attribute); + + // checks if the property has @Enum + if (strpos($propertyComment, '@Enum') === false) { + continue; + } + + $context = 'property ' . $class->name . '::$' . $property->name; + + self::$metadataParser->setTarget(Target::TARGET_PROPERTY); + + foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { + if (! $annotation instanceof Enum) { + continue; + } + + $metadata['enum'][$property->name]['value'] = $annotation->value; + $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal)) + ? $annotation->literal + : $annotation->value; + } + } + + // choose the first property as default property + $metadata['default_property'] = reset($metadata['properties']); + } elseif ($metadata['has_named_argument_constructor']) { + foreach ($constructor->getParameters() as $parameter) { + $metadata['constructor_args'][$parameter->getName()] = [ + 'position' => $parameter->getPosition(), + 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null, + ]; + } + } + } + + self::$annotationMetadata[$name] = $metadata; + } + + /** + * Collects parsing metadata for a given attribute. + * + * @param mixed[] $metadata + */ + private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void + { + // handle internal type declaration + $type = self::$typeMap[$attribute->type] ?? $attribute->type; + + // handle the case if the property type is mixed + if ($type === 'mixed') { + return; + } + + // Evaluate type + $pos = strpos($type, '<'); + if ($pos !== false) { + // Checks if the property has array + $arrayType = substr($type, $pos + 1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + } else { + // Checks if the property has type[] + $pos = strrpos($type, '['); + if ($pos !== false) { + $arrayType = substr($type, 0, $pos); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + } + } + + $metadata['attribute_types'][$attribute->name]['type'] = $type; + $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; + $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; + } + + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @phpstan-return list + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Annotations(): array + { + $annotations = []; + + while ($this->lexer->lookahead !== null) { + if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is preceded by non-catchable pattern + if ( + $this->lexer->token !== null && + $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen( + $this->lexer->token['value'] + ) + ) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is followed by either a namespace separator, or + // an identifier token + $peek = $this->lexer->glimpse(); + if ( + ($peek === null) + || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array( + $peek['type'], + self::$classIdentifiers, + true + )) + || $peek['position'] !== $this->lexer->lookahead['position'] + 1 + ) { + $this->lexer->moveNext(); + continue; + } + + $this->isNestedAnnotation = false; + $annot = $this->Annotation(); + if ($annot === false) { + continue; + } + + $annotations[] = $annot; + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName MethodCall + * AnnotationName ::= QualifiedName | SimpleName + * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName + * NameSpacePart ::= identifier | null | false | true + * SimpleName ::= identifier | null | false | true + * + * @return object|false False if it is not a valid annotation. + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Annotation() + { + $this->match(DocLexer::T_AT); + + // check if we have an annotation + $name = $this->Identifier(); + + if ( + $this->lexer->isNextToken(DocLexer::T_MINUS) + && $this->lexer->nextTokenIsAdjacent() + ) { + // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded + return false; + } + + // only process names which are not fully qualified, yet + // fully qualified names must start with a \ + $originalName = $name; + + if ($name[0] !== '\\') { + $pos = strpos($name, '\\'); + $alias = ($pos === false) ? $name : substr($name, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + if ($this->namespaces) { + foreach ($this->namespaces as $namespace) { + if ($this->classExists($namespace . '\\' . $name)) { + $name = $namespace . '\\' . $name; + $found = true; + break; + } + } + } elseif (isset($this->imports[$loweredAlias])) { + $namespace = ltrim($this->imports[$loweredAlias], '\\'); + $name = ($pos !== false) + ? $namespace . substr($name, $pos) + : $namespace; + $found = $this->classExists($name); + } elseif ( + ! isset($this->ignoredAnnotationNames[$name]) + && isset($this->imports['__NAMESPACE__']) + && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) + ) { + $name = $this->imports['__NAMESPACE__'] . '\\' . $name; + $found = true; + } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { + $found = true; + } + + if (! $found) { + if ($this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf( + <<<'EXCEPTION' +The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation? +EXCEPTION + , + $name, + $this->context + )); + } + } + + $name = ltrim($name, '\\'); + + if (! $this->classExists($name)) { + throw AnnotationException::semanticalError(sprintf( + 'The annotation "@%s" in %s does not exist, or could not be auto-loaded.', + $name, + $this->context + )); + } + + // at this point, $name contains the fully qualified class name of the + // annotation, and it is also guaranteed that this class exists, and + // that it is loaded + + // collects the metadata annotation only if there is not yet + if (! isset(self::$annotationMetadata[$name])) { + $this->collectAnnotationMetadata($name); + } + + // verify that the class is really meant to be an annotation and not just any ordinary class + if (self::$annotationMetadata[$name]['is_annotation'] === false) { + if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf( + <<<'EXCEPTION' +The class "%s" is not annotated with @Annotation. +Are you sure this class can be used as annotation? +If so, then you need to add @Annotation to the _class_ doc comment of "%s". +If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s. +EXCEPTION + , + $name, + $name, + $originalName, + $this->context + )); + } + + //if target is nested annotation + $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; + + // Next will be nested + $this->isNestedAnnotation = true; + + //if annotation does not support current target + if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) { + throw AnnotationException::semanticalError( + sprintf( + <<<'EXCEPTION' +Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s. +EXCEPTION + , + $originalName, + $this->context, + self::$annotationMetadata[$name]['targets_literal'] + ) + ); + } + + $arguments = $this->MethodCall(); + $values = $this->resolvePositionalValues($arguments, $name); + + if (isset(self::$annotationMetadata[$name]['enum'])) { + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { + // checks if the attribute is a valid enumerator + if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { + throw AnnotationException::enumeratorError( + $property, + $name, + $this->context, + $enum['literal'], + $values[$property] + ); + } + } + } + + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { + if ( + $property === self::$annotationMetadata[$name]['default_property'] + && ! isset($values[$property]) && isset($values['value']) + ) { + $property = 'value'; + } + + // handle a not given attribute or null value + if (! isset($values[$property])) { + if ($type['required']) { + throw AnnotationException::requiredError( + $property, + $originalName, + $this->context, + 'a(n) ' . $type['value'] + ); + } + + continue; + } + + if ($type['type'] === 'array') { + // handle the case of a single value + if (! is_array($values[$property])) { + $values[$property] = [$values[$property]]; + } + + // checks if the attribute has array type declaration, such as "array" + if (isset($type['array_type'])) { + foreach ($values[$property] as $item) { + if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) { + throw AnnotationException::attributeTypeError( + $property, + $originalName, + $this->context, + 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', + $item + ); + } + } + } + } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) { + throw AnnotationException::attributeTypeError( + $property, + $originalName, + $this->context, + 'a(n) ' . $type['value'], + $values[$property] + ); + } + } + + if (self::$annotationMetadata[$name]['has_named_argument_constructor']) { + if (PHP_VERSION_ID >= 80000) { + return $this->instantiateAnnotiation($originalName, $this->context, $name, $values); + } + + $positionalValues = []; + foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { + $positionalValues[$parameter['position']] = $parameter['default']; + } + + foreach ($values as $property => $value) { + if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { + throw AnnotationException::creationError(sprintf( + <<<'EXCEPTION' +The annotation @%s declared on %s does not have a property named "%s" +that can be set through its named arguments constructor. +Available named arguments: %s +EXCEPTION + , + $originalName, + $this->context, + $property, + implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])) + )); + } + + $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value; + } + + return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues); + } + + // check if the annotation expects values via the constructor, + // or directly injected into public properties + if (self::$annotationMetadata[$name]['has_constructor'] === true) { + return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]); + } + + $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []); + + foreach ($values as $property => $value) { + if (! isset(self::$annotationMetadata[$name]['properties'][$property])) { + if ($property !== 'value') { + throw AnnotationException::creationError(sprintf( + <<<'EXCEPTION' +The annotation @%s declared on %s does not have a property named "%s". +Available properties: %s +EXCEPTION + , + $originalName, + $this->context, + $property, + implode(', ', self::$annotationMetadata[$name]['properties']) + )); + } + + // handle the case if the property has no annotations + $property = self::$annotationMetadata[$name]['default_property']; + if (! $property) { + throw AnnotationException::creationError(sprintf( + 'The annotation @%s declared on %s does not accept any values, but got %s.', + $originalName, + $this->context, + json_encode($values) + )); + } + } + + $instance->{$property} = $value; + } + + return $instance; + } + + /** + * MethodCall ::= ["(" [Values] ")"] + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function MethodCall(): array + { + $values = []; + + if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { + return $values; + } + + $this->match(DocLexer::T_OPEN_PARENTHESIS); + + if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + $values = $this->Values(); + } + + $this->match(DocLexer::T_CLOSE_PARENTHESIS); + + return $values; + } + + /** + * Values ::= Array | Value {"," Value}* [","] + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Values(): array + { + $values = [$this->Value()]; + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + break; + } + + $token = $this->lexer->lookahead; + $value = $this->Value(); + + $values[] = $value; + } + + $namedArguments = []; + $positionalArguments = []; + foreach ($values as $k => $value) { + if (is_object($value) && $value instanceof stdClass) { + $namedArguments[$value->name] = $value->value; + } else { + $positionalArguments[$k] = $value; + } + } + + return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments]; + } + + /** + * Constant ::= integer | string | float | boolean + * + * @return mixed + * + * @throws AnnotationException + */ + private function Constant() + { + $identifier = $this->Identifier(); + + if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') { + [$className, $const] = explode('::', $identifier); + + $pos = strpos($className, '\\'); + $alias = ($pos === false) ? $className : substr($className, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + switch (true) { + case ! empty($this->namespaces): + foreach ($this->namespaces as $ns) { + if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { + $className = $ns . '\\' . $className; + $found = true; + break; + } + } + + break; + + case isset($this->imports[$loweredAlias]): + $found = true; + $className = ($pos !== false) + ? $this->imports[$loweredAlias] . substr($className, $pos) + : $this->imports[$loweredAlias]; + break; + + default: + if (isset($this->imports['__NAMESPACE__'])) { + $ns = $this->imports['__NAMESPACE__']; + + if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { + $className = $ns . '\\' . $className; + $found = true; + } + } + + break; + } + + if ($found) { + $identifier = $className . '::' . $const; + } + } + + /** + * Checks if identifier ends with ::class and remove the leading backslash if it exists. + */ + if ( + $this->identifierEndsWithClassConstant($identifier) && + ! $this->identifierStartsWithBackslash($identifier) + ) { + return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); + } + + if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { + return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); + } + + if (! defined($identifier)) { + throw AnnotationException::semanticalErrorConstants($identifier, $this->context); + } + + return constant($identifier); + } + + private function identifierStartsWithBackslash(string $identifier): bool + { + return $identifier[0] === '\\'; + } + + private function identifierEndsWithClassConstant(string $identifier): bool + { + return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); + } + + /** @return int|false */ + private function getClassConstantPositionInIdentifier(string $identifier) + { + return stripos($identifier, '::class'); + } + + /** + * Identifier ::= string + * + * @throws AnnotationException + */ + private function Identifier(): string + { + // check if we have an annotation + if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { + throw $this->syntaxError('namespace separator or identifier'); + } + + $this->lexer->moveNext(); + + $className = $this->lexer->token['value']; + + while ( + $this->lexer->lookahead !== null && + $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + + strlen($this->lexer->token['value'])) && + $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR) + ) { + $this->match(DocLexer::T_NAMESPACE_SEPARATOR); + $this->matchAny(self::$classIdentifiers); + + $className .= '\\' . $this->lexer->token['value']; + } + + return $className; + } + + /** + * Value ::= PlainValue | FieldAssignment + * + * @return mixed + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Value() + { + $peek = $this->lexer->glimpse(); + + if ($peek['type'] === DocLexer::T_EQUALS) { + return $this->FieldAssignment(); + } + + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | boolean | Array | Annotation + * + * @return mixed + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function PlainValue() + { + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + return $this->Arrayx(); + } + + if ($this->lexer->isNextToken(DocLexer::T_AT)) { + return $this->Annotation(); + } + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + return $this->Constant(); + } + + switch ($this->lexer->lookahead['type']) { + case DocLexer::T_STRING: + $this->match(DocLexer::T_STRING); + + return $this->lexer->token['value']; + + case DocLexer::T_INTEGER: + $this->match(DocLexer::T_INTEGER); + + return (int) $this->lexer->token['value']; + + case DocLexer::T_FLOAT: + $this->match(DocLexer::T_FLOAT); + + return (float) $this->lexer->token['value']; + + case DocLexer::T_TRUE: + $this->match(DocLexer::T_TRUE); + + return true; + + case DocLexer::T_FALSE: + $this->match(DocLexer::T_FALSE); + + return false; + + case DocLexer::T_NULL: + $this->match(DocLexer::T_NULL); + + return null; + + default: + throw $this->syntaxError('PlainValue'); + } + } + + /** + * FieldAssignment ::= FieldName "=" PlainValue + * FieldName ::= identifier + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function FieldAssignment(): stdClass + { + $this->match(DocLexer::T_IDENTIFIER); + $fieldName = $this->lexer->token['value']; + + $this->match(DocLexer::T_EQUALS); + + $item = new stdClass(); + $item->name = $fieldName; + $item->value = $this->PlainValue(); + + return $item; + } + + /** + * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Arrayx(): array + { + $array = $values = []; + + $this->match(DocLexer::T_OPEN_CURLY_BRACES); + + // If the array is empty, stop parsing and return. + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + return $array; + } + + $values[] = $this->ArrayEntry(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + // optional trailing comma + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + break; + } + + $values[] = $this->ArrayEntry(); + } + + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + foreach ($values as $value) { + [$key, $val] = $value; + + if ($key !== null) { + $array[$key] = $val; + } else { + $array[] = $val; + } + } + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant + * Key ::= string | integer | Constant + * + * @phpstan-return array{mixed, mixed} + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function ArrayEntry(): array + { + $peek = $this->lexer->glimpse(); + + if ( + $peek['type'] === DocLexer::T_EQUALS + || $peek['type'] === DocLexer::T_COLON + ) { + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + $key = $this->Constant(); + } else { + $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); + $key = $this->lexer->token['value']; + } + + $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); + + return [$key, $this->PlainValue()]; + } + + return [null, $this->Value()]; + } + + /** + * Checks whether the given $name matches any ignored annotation name or namespace + */ + private function isIgnoredAnnotation(string $name): bool + { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { + return true; + } + + foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { + $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; + + if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) { + return true; + } + } + + return false; + } + + /** + * Resolve positional arguments (without name) to named ones + * + * @param array $arguments + * + * @return array + */ + private function resolvePositionalValues(array $arguments, string $name): array + { + $positionalArguments = $arguments['positional_arguments'] ?? []; + $values = $arguments['named_arguments'] ?? []; + + if ( + self::$annotationMetadata[$name]['has_named_argument_constructor'] + && self::$annotationMetadata[$name]['default_property'] !== null + ) { + // We must ensure that we don't have positional arguments after named ones + $positions = array_keys($positionalArguments); + $lastPosition = null; + foreach ($positions as $position) { + if ( + ($lastPosition === null && $position !== 0) || + ($lastPosition !== null && $position !== $lastPosition + 1) + ) { + throw $this->syntaxError('Positional arguments after named arguments is not allowed'); + } + + $lastPosition = $position; + } + + foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { + $position = $parameter['position']; + if (isset($values[$property]) || ! isset($positionalArguments[$position])) { + continue; + } + + $values[$property] = $positionalArguments[$position]; + } + } else { + if (count($positionalArguments) > 0 && ! isset($values['value'])) { + if (count($positionalArguments) === 1) { + $value = array_pop($positionalArguments); + } else { + $value = array_values($positionalArguments); + } + + $values['value'] = $value; + } + } + + return $values; + } + + /** + * Try to instantiate the annotation and catch and process any exceptions related to failure + * + * @param class-string $name + * @param array $arguments + * + * @return object + * + * @throws AnnotationException + */ + private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments) + { + try { + return new $name(...$arguments); + } catch (Throwable $exception) { + throw AnnotationException::creationError( + sprintf( + 'An error occurred while instantiating the annotation @%s declared on %s: "%s".', + $originalName, + $context, + $exception->getMessage() + ), + $exception + ); + } + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php new file mode 100644 index 0000000..6c6c22c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php @@ -0,0 +1,315 @@ +> */ + private $loadedAnnotations = []; + + /** @var array */ + private $classNameHashes = []; + + /** @var int */ + private $umask; + + /** + * @param string $cacheDir + * @param bool $debug + * @param int $umask + * + * @throws InvalidArgumentException + */ + public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) + { + if (! is_int($umask)) { + throw new InvalidArgumentException(sprintf( + 'The parameter umask must be an integer, was: %s', + gettype($umask) + )); + } + + $this->reader = $reader; + $this->umask = $umask; + + if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { + throw new InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $cacheDir + )); + } + + $this->dir = rtrim($cacheDir, '\\/'); + $this->debug = $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name]; + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Saves the cache file. + * + * @param string $path + * @param mixed $data + * + * @return void + */ + private function saveCacheFile($path, $data) + { + if (! is_writable($this->dir)) { + throw new InvalidArgumentException(sprintf( + <<<'EXCEPTION' +The directory "%s" is not writable. Both the webserver and the console user need access. +You can manage access rights for multiple users with "chmod +a". +If your system does not support this, check out the acl package., +EXCEPTION + , + $this->dir + )); + } + + $tempfile = tempnam($this->dir, uniqid('', true)); + + if ($tempfile === false) { + throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); + } + + @chmod($tempfile, 0666 & (~$this->umask)); + + $written = file_put_contents( + $tempfile, + 'umask)); + + if (rename($tempfile, $path) === false) { + @unlink($tempfile); + + throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); + } + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php new file mode 100644 index 0000000..ab27f8a --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php @@ -0,0 +1,178 @@ + true, + 'Attribute' => true, + 'Attributes' => true, + /* Can we enable this? 'Enum' => true, */ + 'Required' => true, + 'Target' => true, + 'NamedArgumentConstructor' => true, + ]; + + private const WidelyUsedNonStandard = [ + 'fix' => true, + 'fixme' => true, + 'override' => true, + ]; + + private const PhpDocumentor1 = [ + 'abstract' => true, + 'access' => true, + 'code' => true, + 'deprec' => true, + 'endcode' => true, + 'exception' => true, + 'final' => true, + 'ingroup' => true, + 'inheritdoc' => true, + 'inheritDoc' => true, + 'magic' => true, + 'name' => true, + 'private' => true, + 'static' => true, + 'staticvar' => true, + 'staticVar' => true, + 'toc' => true, + 'tutorial' => true, + 'throw' => true, + ]; + + private const PhpDocumentor2 = [ + 'api' => true, + 'author' => true, + 'category' => true, + 'copyright' => true, + 'deprecated' => true, + 'example' => true, + 'filesource' => true, + 'global' => true, + 'ignore' => true, + /* Can we enable this? 'index' => true, */ + 'internal' => true, + 'license' => true, + 'link' => true, + 'method' => true, + 'package' => true, + 'param' => true, + 'property' => true, + 'property-read' => true, + 'property-write' => true, + 'return' => true, + 'see' => true, + 'since' => true, + 'source' => true, + 'subpackage' => true, + 'throws' => true, + 'todo' => true, + 'TODO' => true, + 'usedby' => true, + 'uses' => true, + 'var' => true, + 'version' => true, + ]; + + private const PHPUnit = [ + 'author' => true, + 'after' => true, + 'afterClass' => true, + 'backupGlobals' => true, + 'backupStaticAttributes' => true, + 'before' => true, + 'beforeClass' => true, + 'codeCoverageIgnore' => true, + 'codeCoverageIgnoreStart' => true, + 'codeCoverageIgnoreEnd' => true, + 'covers' => true, + 'coversDefaultClass' => true, + 'coversNothing' => true, + 'dataProvider' => true, + 'depends' => true, + 'doesNotPerformAssertions' => true, + 'expectedException' => true, + 'expectedExceptionCode' => true, + 'expectedExceptionMessage' => true, + 'expectedExceptionMessageRegExp' => true, + 'group' => true, + 'large' => true, + 'medium' => true, + 'preserveGlobalState' => true, + 'requires' => true, + 'runTestsInSeparateProcesses' => true, + 'runInSeparateProcess' => true, + 'small' => true, + 'test' => true, + 'testdox' => true, + 'testWith' => true, + 'ticket' => true, + 'uses' => true, + ]; + + private const PhpCheckStyle = ['SuppressWarnings' => true]; + + private const PhpStorm = ['noinspection' => true]; + + private const PEAR = ['package_version' => true]; + + private const PlainUML = [ + 'startuml' => true, + 'enduml' => true, + ]; + + private const Symfony = ['experimental' => true]; + + private const PhpCodeSniffer = [ + 'codingStandardsIgnoreStart' => true, + 'codingStandardsIgnoreEnd' => true, + ]; + + private const SlevomatCodingStandard = ['phpcsSuppress' => true]; + + private const Phan = ['suppress' => true]; + + private const Rector = ['noRector' => true]; + + private const StaticAnalysis = [ + // PHPStan, Psalm + 'extends' => true, + 'implements' => true, + 'readonly' => true, + 'template' => true, + 'use' => true, + + // Psalm + 'pure' => true, + 'immutable' => true, + ]; + + public const LIST = self::Reserved + + self::WidelyUsedNonStandard + + self::PhpDocumentor1 + + self::PhpDocumentor2 + + self::PHPUnit + + self::PhpCheckStyle + + self::PhpStorm + + self::PEAR + + self::PlainUML + + self::Symfony + + self::SlevomatCodingStandard + + self::PhpCodeSniffer + + self::Phan + + self::Rector + + self::StaticAnalysis; + + private function __construct() + { + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php new file mode 100644 index 0000000..62dcf74 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php @@ -0,0 +1,100 @@ +delegate = $reader; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $annotations = []; + foreach ($this->delegate->getClassAnnotations($class) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + return $this->delegate->getClassAnnotation($class, $annotationName); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $annotations = []; + foreach ($this->delegate->getMethodAnnotations($method) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + return $this->delegate->getMethodAnnotation($method, $annotationName); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $annotations = []; + foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + return $this->delegate->getPropertyAnnotation($property, $annotationName); + } + + /** + * Proxies all methods to the delegate. + * + * @param string $method + * @param mixed[] $args + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->delegate, $method], $args); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php new file mode 100644 index 0000000..8af224c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php @@ -0,0 +1,14 @@ +ReflectionClass object. + * + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseClass(ReflectionClass $class) + { + return $this->parseUseStatements($class); + } + + /** + * Parse a class or function for use statements. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @psalm-return array a list with use statements in the form (Alias => FQN). + */ + public function parseUseStatements($reflection): array + { + if (method_exists($reflection, 'getUseStatements')) { + return $reflection->getUseStatements(); + } + + $filename = $reflection->getFileName(); + + if ($filename === false) { + return []; + } + + $content = $this->getFileContent($filename, $reflection->getStartLine()); + + if ($content === null) { + return []; + } + + $namespace = preg_quote($reflection->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($reflection->getNamespaceName()); + } + + /** + * Gets the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param int $lineNumber The number of lines to read from file. + * + * @return string|null The content of the file or null if the file does not exist. + */ + private function getFileContent($filename, $lineNumber) + { + if (! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (! $file->eof()) { + if ($lineCnt++ === $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php new file mode 100644 index 0000000..a7099d5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php @@ -0,0 +1,232 @@ +> */ + private $loadedAnnotations = []; + + /** @var int[] */ + private $loadedFilemtimes = []; + + public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (bool) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName() . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName() . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + public function clearLoadedAnnotations(): void + { + $this->loadedAnnotations = []; + $this->loadedFilemtimes = []; + } + + /** @return mixed[] */ + private function fetchFromCache( + string $cacheKey, + ReflectionClass $class, + string $method, + Reflector $reflector + ): array { + $cacheKey = rawurlencode($cacheKey); + + $item = $this->cache->getItem($cacheKey); + if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) { + $this->cache->save($item->set($this->delegate->{$method}($reflector))); + } + + return $item->get(); + } + + /** + * Used in debug mode to check if the cache is fresh. + * + * @return bool Returns true if the cache was fresh, or false if the class + * being read was modified since writing to the cache. + */ + private function refresh(string $cacheKey, ReflectionClass $class): bool + { + $lastModification = $this->getLastModification($class); + if ($lastModification === 0) { + return true; + } + + $item = $this->cache->getItem('[C]' . $cacheKey); + if ($item->isHit() && $item->get() >= $lastModification) { + return true; + } + + $this->cache->save($item->set(time())); + + return false; + } + + /** + * Returns the time the class was last modified, testing traits and parents + */ + private function getLastModification(ReflectionClass $class): int + { + $filename = $class->getFileName(); + + if (isset($this->loadedFilemtimes[$filename])) { + return $this->loadedFilemtimes[$filename]; + } + + $parent = $class->getParentClass(); + + $lastModification = max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $class->getTraits()), + array_map(function (ReflectionClass $class): int { + return $this->getLastModification($class); + }, $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + + assert($lastModification !== false); + + return $this->loadedFilemtimes[$filename] = $lastModification; + } + + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int + { + $fileName = $reflectionTrait->getFileName(); + + if (isset($this->loadedFilemtimes[$fileName])) { + return $this->loadedFilemtimes[$fileName]; + } + + $lastModificationTime = max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $reflectionTrait->getTraits()) + )); + + assert($lastModificationTime !== false); + + return $this->loadedFilemtimes[$fileName] = $lastModificationTime; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php new file mode 100644 index 0000000..0663ffd --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php @@ -0,0 +1,80 @@ + An array of Annotations. + */ + public function getClassAnnotations(ReflectionClass $class); + + /** + * Gets a class annotation. + * + * @param ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName); + + /** + * Gets the annotations applied to a method. + * + * @param ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getMethodAnnotations(ReflectionMethod $method); + + /** + * Gets a method annotation. + * + * @param ReflectionMethod $method The ReflectionMethod to read the annotations from. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName); + + /** + * Gets the annotations applied to a property. + * + * @param ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getPropertyAnnotations(ReflectionProperty $property); + + /** + * Gets a property annotation. + * + * @param ReflectionProperty $property The ReflectionProperty to read the annotations from. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php new file mode 100644 index 0000000..8a78c11 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php @@ -0,0 +1,114 @@ +parser = new DocParser(); + $this->parser->setIgnoreNotImportedAnnotations(true); + } + + /** + * Adds a namespace in which we will look for annotations. + * + * @param string $namespace + * + * @return void + */ + public function addNamespace($namespace) + { + $this->parser->addNamespace($namespace); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + return $this->parser->parse( + $method->getDocComment(), + 'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()' + ); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + return $this->parser->parse( + $property->getDocComment(), + 'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName() + ); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php new file mode 100644 index 0000000..69259fc --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php @@ -0,0 +1,206 @@ + + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + /** @param string $contents */ + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + + // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it + // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored + // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a + // docblock. If the first thing in the file is a class without a doc block this would cause calls to + // getDocBlock() on said class to return our long lost doc_comment. Argh. + // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least + // it's harmless to us. + token_get_all("numTokens = count($this->tokens); + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return mixed[]|string|null The token if exists, null otherwise. + */ + public function next($docCommentIsComment = true) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ( + $this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) + ) { + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parses a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + public function parseUseStatement() + { + $groupRoot = ''; + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + while (($token = $this->next())) { + if (! $explicitAlias && $token[0] === T_STRING) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $token[0] === T_STRING) { + $alias = $token[1]; + } elseif ( + PHP_VERSION_ID >= 80000 && + ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) + ) { + $class .= $token[1]; + + $classSplit = explode('\\', $token[1]); + $alias = $classSplit[count($classSplit) - 1]; + } elseif ($token[0] === T_NS_SEPARATOR) { + $class .= '\\'; + $alias = ''; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token === ',') { + $statements[strtolower($alias)] = $groupRoot . $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[strtolower($alias)] = $groupRoot . $class; + break; + } elseif ($token === '{') { + $groupRoot = $class; + $class = ''; + } elseif ($token === '}') { + continue; + } else { + break; + } + } + + return $statements; + } + + /** + * Gets all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = []; + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = []; + } + + return $statements; + } + + /** + * Gets the namespace. + * + * @return string The found namespace. + */ + public function parseNamespace() + { + $name = ''; + while ( + ($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || ( + PHP_VERSION_ID >= 80000 && + ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) + )) + ) { + $name .= $token[1]; + } + + return $name; + } + + /** + * Gets the class name. + * + * @return string The found class name. + */ + public function parseClass() + { + // Namespaces and class names are tokenized the same: T_STRINGs + // separated by T_NS_SEPARATOR so we can use one function to provide + // both. + return $this->parseNamespace(); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/psalm.xml b/Sources/API/vendor/doctrine/annotations/psalm.xml new file mode 100644 index 0000000..e6af389 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/Sources/API/vendor/doctrine/deprecations/LICENSE b/Sources/API/vendor/doctrine/deprecations/LICENSE new file mode 100644 index 0000000..156905c --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2021 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/deprecations/README.md b/Sources/API/vendor/doctrine/deprecations/README.md new file mode 100644 index 0000000..22f0cce --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/README.md @@ -0,0 +1,154 @@ +# Doctrine Deprecations + +A small (side-effect free by default) layer on top of +`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. + +- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under +- options to avoid having to rely on error handlers global state by using PSR-3 logging +- deduplicate deprecation messages to avoid excessive triggering and reduce overhead + +We recommend to collect Deprecations using a PSR logger instead of relying on +the global error handler. + +## Usage from consumer perspective: + +Enable Doctrine deprecations to be sent to a PSR3 logger: + +```php +\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); +``` + +Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` +messages. + +```php +\Doctrine\Deprecations\Deprecation::enableWithTriggerError(); +``` + +If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call: + +```php +\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); +``` + +Tracking is enabled with all three modes and provides access to all triggered +deprecations and their individual count: + +```php +$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); + +foreach ($deprecations as $identifier => $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/Sources/API/vendor/doctrine/deprecations/composer.json b/Sources/API/vendor/doctrine/deprecations/composer.json new file mode 100644 index 0000000..c79e38c --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,32 @@ +{ + "name": "doctrine/deprecations", + "type": "library", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "license": "MIT", + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3", + "doctrine/coding-standard": "^9" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"} + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php new file mode 100644 index 0000000..1029372 --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php @@ -0,0 +1,266 @@ + */ + private static $ignoredPackages = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param mixed $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param mixed $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * @param array $backtrace + */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + if ((self::$type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'], + 'line' => $backtrace[0]['line'], + 'package' => $package, + 'link' => $link, + ]; + + self::$logger->notice($message, $context); + } + + if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file']), + $backtrace[0]['line'], + self::basename($backtrace[1]['file']), + $backtrace[1]['line'], + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + + foreach (self::$ignoredLinks as $link => $count) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$ignoredLinks; + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php new file mode 100644 index 0000000..4c3366a --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** + * @before + */ + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** + * @after + */ + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/phpcs.xml b/Sources/API/vendor/doctrine/deprecations/phpcs.xml new file mode 100644 index 0000000..f115e43 --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/phpcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + lib + tests + + + + + + diff --git a/Sources/API/vendor/doctrine/lexer/LICENSE b/Sources/API/vendor/doctrine/lexer/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/lexer/README.md b/Sources/API/vendor/doctrine/lexer/README.md new file mode 100644 index 0000000..784f2a2 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/README.md @@ -0,0 +1,9 @@ +# Doctrine Lexer + +[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions) + +Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. + +This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). + +https://www.doctrine-project.org/projects/lexer.html diff --git a/Sources/API/vendor/doctrine/lexer/UPGRADE.md b/Sources/API/vendor/doctrine/lexer/UPGRADE.md new file mode 100644 index 0000000..42b85b3 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/UPGRADE.md @@ -0,0 +1,14 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 2.0.0 + +`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return +instances of `Doctrine\Common\Lexer\Token`, which is an array-like class +Using it as an array is deprecated in favor of using properties of that class. +Using `count()` on it is deprecated with no replacement. diff --git a/Sources/API/vendor/doctrine/lexer/composer.json b/Sources/API/vendor/doctrine/lexer/composer.json new file mode 100644 index 0000000..be3013c --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/composer.json @@ -0,0 +1,56 @@ +{ + "name": "doctrine/lexer", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "license": "MIT", + "type": "library", + "keywords": [ + "php", + "parser", + "lexer", + "annotations", + "docblock" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "require": { + "php": "^7.1 || ^8.0", + "doctrine/deprecations": "^1.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\Common\\Lexer\\": "tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php b/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php new file mode 100644 index 0000000..7d33e41 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php @@ -0,0 +1,336 @@ +> + */ + private $tokens = []; + + /** + * Current lexer position in input string. + * + * @var int + */ + private $position = 0; + + /** + * Current peek of current lexer position. + * + * @var int + */ + private $peek = 0; + + /** + * The next token in the input. + * + * @var mixed[]|null + * @psalm-var Token|null + */ + public $lookahead; + + /** + * The last matched/seen token. + * + * @var mixed[]|null + * @psalm-var Token|null + */ + public $token; + + /** + * Composed regex for input parsing. + * + * @var string|null + */ + private $regex; + + /** + * Sets the input data to be tokenized. + * + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. + * + * @return void + */ + public function setInput($input) + { + $this->input = $input; + $this->tokens = []; + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param int $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @param int $position + * + * @return string + */ + public function getInputUntilPosition($position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param T $type + * + * @return bool + */ + public function isNextToken($type) + { + return $this->lookahead !== null && $this->lookahead->isA($type); + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param list $types + * + * @return bool + */ + public function isNextTokenAny(array $types) + { + return $this->lookahead !== null && $this->lookahead->isA(...$types); + } + + /** + * Moves to the next token in the input string. + * + * @return bool + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = isset($this->tokens[$this->position]) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param T $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && ! $this->lookahead->isA($type)) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param string $value + * @param int|string $token + * + * @return bool + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return mixed[]|null The next token or NULL if there are no more tokens ahead. + * @psalm-return Token|null + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return mixed[]|null The next token or NULL if there are no more tokens ahead. + * @psalm-return Token|null + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + if (! isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers() + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($this->regex, $input, -1, $flags); + + if ($matches === false) { + // Work around https://bugs.php.net/78122 + $matches = [[$input, 0]]; + } + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = new Token( + $match[0], + $type, + $match[1] + ); + } + } + + /** + * Gets the literal for a given token. + * + * @param T $token + * + * @return int|string + */ + public function getLiteral($token) + { + if ($token instanceof UnitEnum) { + return get_class($token) . '::' . $token->name; + } + + $className = static::class; + + $reflClass = new ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } + + /** + * Lexical catchable patterns. + * + * @return string[] + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return string[] + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return T|null + */ + abstract protected function getType(&$value); +} diff --git a/Sources/API/vendor/doctrine/lexer/src/Token.php b/Sources/API/vendor/doctrine/lexer/src/Token.php new file mode 100644 index 0000000..e2e0f55 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/src/Token.php @@ -0,0 +1,129 @@ + + */ +final class Token implements ArrayAccess +{ + /** + * The string value of the token in the input string + * + * @readonly + * @var string|int + */ + public $value; + + /** + * The type of the token (identifier, numeric, string, input parameter, none) + * + * @readonly + * @var T|null + */ + public $type; + + /** + * The position of the token in the input string + * + * @readonly + * @var int + */ + public $position; + + /** + * @param string|int $value + * @param T|null $type + */ + public function __construct($value, $type, int $position) + { + $this->value = $value; + $this->type = $type; + $this->position = $position; + } + + /** @param T ...$types */ + public function isA(...$types): bool + { + return in_array($this->type, $types, true); + } + + /** + * @deprecated Use the value, type or position property instead + * {@inheritDoc} + */ + public function offsetExists($offset): bool + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', + self::class + ); + + return in_array($offset, ['value', 'type', 'position'], true); + } + + /** + * @deprecated Use the value, type or position property instead + * {@inheritDoc} + * + * @param array-key $offset + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($offset) + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', + self::class + ); + + return $this->$offset; + } + + /** + * @deprecated no replacement planned + * {@inheritDoc} + */ + public function offsetSet($offset, $value): void + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Setting %s properties via ArrayAccess is deprecated', + self::class + ); + + $this->$offset = $value; + } + + /** + * @deprecated no replacement planned + * {@inheritDoc} + */ + public function offsetUnset($offset): void + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Setting %s properties via ArrayAccess is deprecated', + self::class + ); + + $this->$offset = null; + } +} diff --git a/Sources/API/vendor/fig/http-message-util/.gitignore b/Sources/API/vendor/fig/http-message-util/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/Sources/API/vendor/fig/http-message-util/CHANGELOG.md b/Sources/API/vendor/fig/http-message-util/CHANGELOG.md new file mode 100644 index 0000000..1a02e54 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/CHANGELOG.md @@ -0,0 +1,147 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.1.5 - 2020-11-24 + +### Added + +- [#19](https://github.com/php-fig/http-message-util/pull/19) adds support for PHP 8. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.1.4 - 2020-02-05 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [#15](https://github.com/php-fig/http-message-util/pull/15) removes the dependency on psr/http-message, as it is not technically necessary for usage of this package. + +### Fixed + +- Nothing. + +## 1.1.3 - 2018-11-19 + +### Added + +- [#10](https://github.com/php-fig/http-message-util/pull/10) adds the constants `StatusCodeInterface::STATUS_EARLY_HINTS` (103) and + `StatusCodeInterface::STATUS_TOO_EARLY` (425). + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.1.2 - 2017-02-09 + +### Added + +- [#4](https://github.com/php-fig/http-message-util/pull/4) adds the constant + `StatusCodeInterface::STATUS_MISDIRECTED_REQUEST` (421). + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.1.1 - 2017-02-06 + +### Added + +- [#3](https://github.com/php-fig/http-message-util/pull/3) adds the constant + `StatusCodeInterface::STATUS_IM_A_TEAPOT` (418). + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.1.0 - 2016-09-19 + +### Added + +- [#1](https://github.com/php-fig/http-message-util/pull/1) adds + `Fig\Http\Message\StatusCodeInterface`, with constants named after common + status reason phrases, with values indicating the status codes themselves. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.0.0 - 2017-08-05 + +### Added + +- Adds `Fig\Http\Message\RequestMethodInterface`, with constants covering the + most common HTTP request methods as specified by the IETF. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. diff --git a/Sources/API/vendor/fig/http-message-util/LICENSE b/Sources/API/vendor/fig/http-message-util/LICENSE new file mode 100644 index 0000000..e2fa347 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/fig/http-message-util/README.md b/Sources/API/vendor/fig/http-message-util/README.md new file mode 100644 index 0000000..ea5b5aa --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/README.md @@ -0,0 +1,17 @@ +# PSR Http Message Util + +This repository holds utility classes and constants to facilitate common +operations of [PSR-7](https://www.php-fig.org/psr/psr-7/); the primary purpose is +to provide constants for referring to request methods, response status codes and +messages, and potentially common headers. + +Implementation of PSR-7 interfaces is **not** within the scope of this package. + +## Installation + +Install by adding the package as a [Composer](https://getcomposer.org) +requirement: + +```bash +$ composer require fig/http-message-util +``` diff --git a/Sources/API/vendor/fig/http-message-util/composer.json b/Sources/API/vendor/fig/http-message-util/composer.json new file mode 100644 index 0000000..8645893 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/composer.json @@ -0,0 +1,28 @@ +{ + "name": "fig/http-message-util", + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/Sources/API/vendor/fig/http-message-util/src/RequestMethodInterface.php b/Sources/API/vendor/fig/http-message-util/src/RequestMethodInterface.php new file mode 100644 index 0000000..97d9a93 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/src/RequestMethodInterface.php @@ -0,0 +1,34 @@ + + * class RequestFactory implements RequestMethodInterface + * { + * public static function factory( + * $uri = '/', + * $method = self::METHOD_GET, + * $data = [] + * ) { + * } + * } + * + */ +interface RequestMethodInterface +{ + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; +} diff --git a/Sources/API/vendor/fig/http-message-util/src/StatusCodeInterface.php b/Sources/API/vendor/fig/http-message-util/src/StatusCodeInterface.php new file mode 100644 index 0000000..99b7e78 --- /dev/null +++ b/Sources/API/vendor/fig/http-message-util/src/StatusCodeInterface.php @@ -0,0 +1,107 @@ + + * class ResponseFactory implements StatusCodeInterface + * { + * public function createResponse($code = self::STATUS_OK) + * { + * } + * } + * + */ +interface StatusCodeInterface +{ + // Informational 1xx + const STATUS_CONTINUE = 100; + const STATUS_SWITCHING_PROTOCOLS = 101; + const STATUS_PROCESSING = 102; + const STATUS_EARLY_HINTS = 103; + // Successful 2xx + const STATUS_OK = 200; + const STATUS_CREATED = 201; + const STATUS_ACCEPTED = 202; + const STATUS_NON_AUTHORITATIVE_INFORMATION = 203; + const STATUS_NO_CONTENT = 204; + const STATUS_RESET_CONTENT = 205; + const STATUS_PARTIAL_CONTENT = 206; + const STATUS_MULTI_STATUS = 207; + const STATUS_ALREADY_REPORTED = 208; + const STATUS_IM_USED = 226; + // Redirection 3xx + const STATUS_MULTIPLE_CHOICES = 300; + const STATUS_MOVED_PERMANENTLY = 301; + const STATUS_FOUND = 302; + const STATUS_SEE_OTHER = 303; + const STATUS_NOT_MODIFIED = 304; + const STATUS_USE_PROXY = 305; + const STATUS_RESERVED = 306; + const STATUS_TEMPORARY_REDIRECT = 307; + const STATUS_PERMANENT_REDIRECT = 308; + // Client Errors 4xx + const STATUS_BAD_REQUEST = 400; + const STATUS_UNAUTHORIZED = 401; + const STATUS_PAYMENT_REQUIRED = 402; + const STATUS_FORBIDDEN = 403; + const STATUS_NOT_FOUND = 404; + const STATUS_METHOD_NOT_ALLOWED = 405; + const STATUS_NOT_ACCEPTABLE = 406; + const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407; + const STATUS_REQUEST_TIMEOUT = 408; + const STATUS_CONFLICT = 409; + const STATUS_GONE = 410; + const STATUS_LENGTH_REQUIRED = 411; + const STATUS_PRECONDITION_FAILED = 412; + const STATUS_PAYLOAD_TOO_LARGE = 413; + const STATUS_URI_TOO_LONG = 414; + const STATUS_UNSUPPORTED_MEDIA_TYPE = 415; + const STATUS_RANGE_NOT_SATISFIABLE = 416; + const STATUS_EXPECTATION_FAILED = 417; + const STATUS_IM_A_TEAPOT = 418; + const STATUS_MISDIRECTED_REQUEST = 421; + const STATUS_UNPROCESSABLE_ENTITY = 422; + const STATUS_LOCKED = 423; + const STATUS_FAILED_DEPENDENCY = 424; + const STATUS_TOO_EARLY = 425; + const STATUS_UPGRADE_REQUIRED = 426; + const STATUS_PRECONDITION_REQUIRED = 428; + const STATUS_TOO_MANY_REQUESTS = 429; + const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + // Server Errors 5xx + const STATUS_INTERNAL_SERVER_ERROR = 500; + const STATUS_NOT_IMPLEMENTED = 501; + const STATUS_BAD_GATEWAY = 502; + const STATUS_SERVICE_UNAVAILABLE = 503; + const STATUS_GATEWAY_TIMEOUT = 504; + const STATUS_VERSION_NOT_SUPPORTED = 505; + const STATUS_VARIANT_ALSO_NEGOTIATES = 506; + const STATUS_INSUFFICIENT_STORAGE = 507; + const STATUS_LOOP_DETECTED = 508; + const STATUS_NOT_EXTENDED = 510; + const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511; +} diff --git a/Sources/API/vendor/nikic/fast-route/.gitignore b/Sources/API/vendor/nikic/fast-route/.gitignore new file mode 100644 index 0000000..e378a07 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/.gitignore @@ -0,0 +1,5 @@ +/vendor/ +.idea/ + +# ignore lock file since we have no extra dependencies +composer.lock diff --git a/Sources/API/vendor/nikic/fast-route/.hhconfig b/Sources/API/vendor/nikic/fast-route/.hhconfig new file mode 100644 index 0000000..0c2153c --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/.hhconfig @@ -0,0 +1 @@ +assume_php=false diff --git a/Sources/API/vendor/nikic/fast-route/.travis.yml b/Sources/API/vendor/nikic/fast-route/.travis.yml new file mode 100644 index 0000000..10f8381 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm + +script: + - ./vendor/bin/phpunit + +before_install: + - travis_retry composer self-update + +install: + - composer install diff --git a/Sources/API/vendor/nikic/fast-route/FastRoute.hhi b/Sources/API/vendor/nikic/fast-route/FastRoute.hhi new file mode 100644 index 0000000..8d50738 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/FastRoute.hhi @@ -0,0 +1,126 @@ +; + } + + class RouteCollector { + public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator); + public function addRoute(mixed $httpMethod, string $route, mixed $handler): void; + public function getData(): array; + } + + class Route { + public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables); + public function matches(string $str): bool; + } + + interface DataGenerator { + public function addRoute(string $httpMethod, array $routeData, mixed $handler); + public function getData(): array; + } + + interface Dispatcher { + const int NOT_FOUND = 0; + const int FOUND = 1; + const int METHOD_NOT_ALLOWED = 2; + public function dispatch(string $httpMethod, string $uri): array; + } + + function simpleDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ) $options = shape()): Dispatcher; + + function cachedDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ?'cacheDisabled' => bool, + ?'cacheFile' => string, + ) $options = shape()): Dispatcher; +} + +namespace FastRoute\DataGenerator { + abstract class RegexBasedAbstract implements \FastRoute\DataGenerator { + protected abstract function getApproxChunkSize(); + protected abstract function processChunk($regexToRoutesMap); + + public function addRoute(string $httpMethod, array $routeData, mixed $handler): void; + public function getData(): array; + } + + class CharCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupPosBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class MarkBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } +} + +namespace FastRoute\Dispatcher { + abstract class RegexBasedAbstract implements \FastRoute\Dispatcher { + protected abstract function dispatchVariableRoute(array $routeData, string $uri): array; + + public function dispatch(string $httpMethod, string $uri): array; + } + + class GroupPosBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class GroupCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class CharCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class MarkBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } +} + +namespace FastRoute\RouteParser { + class Std implements \FastRoute\RouteParser { + const string VARIABLE_REGEX = <<<'REGEX' +\{ + \s* ([a-zA-Z][a-zA-Z0-9_]*) \s* + (?: + : \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*) + )? +\} +REGEX; + const string DEFAULT_DISPATCH_REGEX = '[^/]+'; + public function parse(string $route): array; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/LICENSE b/Sources/API/vendor/nikic/fast-route/LICENSE new file mode 100644 index 0000000..478e764 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2013 by Nikita Popov. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Sources/API/vendor/nikic/fast-route/README.md b/Sources/API/vendor/nikic/fast-route/README.md new file mode 100644 index 0000000..91bd466 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/README.md @@ -0,0 +1,313 @@ +FastRoute - Fast request router for PHP +======================================= + +This library provides a fast implementation of a regular expression based router. [Blog post explaining how the +implementation works and why it is fast.][blog_post] + +Install +------- + +To install with composer: + +```sh +composer require nikic/fast-route +``` + +Requires PHP 5.4 or newer. + +Usage +----- + +Here's a basic usage example: + +```php +addRoute('GET', '/users', 'get_all_users_handler'); + // {id} must be a number (\d+) + $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); + // The /{title} suffix is optional + $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); +}); + +// Fetch method and URI from somewhere +$httpMethod = $_SERVER['REQUEST_METHOD']; +$uri = $_SERVER['REQUEST_URI']; + +// Strip query string (?foo=bar) and decode URI +if (false !== $pos = strpos($uri, '?')) { + $uri = substr($uri, 0, $pos); +} +$uri = rawurldecode($uri); + +$routeInfo = $dispatcher->dispatch($httpMethod, $uri); +switch ($routeInfo[0]) { + case FastRoute\Dispatcher::NOT_FOUND: + // ... 404 Not Found + break; + case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $allowedMethods = $routeInfo[1]; + // ... 405 Method Not Allowed + break; + case FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2]; + // ... call $handler with $vars + break; +} +``` + +### Defining routes + +The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts +a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling +`addRoute()` on the collector instance: + +```php +$r->addRoute($method, $routePattern, $handler); +``` + +The `$method` is an uppercase HTTP method string for which a certain route should match. It +is possible to specify multiple valid methods using an array: + +```php +// These two calls +$r->addRoute('GET', '/test', 'handler'); +$r->addRoute('POST', '/test', 'handler'); +// Are equivalent to this one call +$r->addRoute(['GET', 'POST'], '/test', 'handler'); +``` + +By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` +and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify +a custom pattern by writing `{bar:[0-9]+}`. Some examples: + +```php +// Matches /user/42, but not /user/xyz +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); + +// Matches /user/foobar, but not /user/foo/bar +$r->addRoute('GET', '/user/{name}', 'handler'); + +// Matches /user/foo/bar as well +$r->addRoute('GET', '/user/{name:.+}', 'handler'); +``` + +Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` +is not a valid placeholder, because `()` is a capturing group. Instead you can use either +`{lang:en|de}` or `{lang:(?:en|de)}`. + +Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` +will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, +not in the middle of a route. + +```php +// This route +$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); +// Is equivalent to these two routes +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); +$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); + +// Multiple nested optional parts are possible as well +$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); + +// This route is NOT valid, because optional parts can only occur at the end +$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); +``` + +The `$handler` parameter does not necessarily have to be a callback, it could also be a controller +class name or any other kind of data you wish to associate with the route. FastRoute only tells you +which handler corresponds to your URI, how you interpret it is up to you. + +#### Shorcut methods for common request methods + +For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example: + +```php +$r->get('/get-route', 'get_handler'); +$r->post('/post-route', 'post_handler'); +``` + +Is equivalent to: + +```php +$r->addRoute('GET', '/get-route', 'get_handler'); +$r->addRoute('POST', '/post-route', 'post_handler'); +``` + +#### Route Groups + +Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix. + +For example, defining your routes as: + +```php +$r->addGroup('/admin', function (RouteCollector $r) { + $r->addRoute('GET', '/do-something', 'handler'); + $r->addRoute('GET', '/do-another-thing', 'handler'); + $r->addRoute('GET', '/do-something-else', 'handler'); +}); +``` + +Will have the same result as: + + ```php +$r->addRoute('GET', '/admin/do-something', 'handler'); +$r->addRoute('GET', '/admin/do-another-thing', 'handler'); +$r->addRoute('GET', '/admin/do-something-else', 'handler'); + ``` + +Nested groups are also supported, in which case the prefixes of all the nested groups are combined. + +### Caching + +The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless +caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated +routing data and construct the dispatcher from the cached information: + +```php +addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); +}, [ + 'cacheFile' => __DIR__ . '/route.cache', /* required */ + 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ +]); +``` + +The second parameter to the function is an options array, which can be used to specify the cache +file location, among other things. + +### Dispatching a URI + +A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method +accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them +appropriately) is your job - this library is not bound to the PHP web SAPIs. + +The `dispatch()` method returns an array whose first element contains a status code. It is one +of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the +method not allowed status the second array element contains a list of HTTP methods allowed for +the supplied URI. For example: + + [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] + +> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the +`Allow:` header to detail available methods for the requested resource. Applications using FastRoute +should use the second array element to add this header when relaying a 405 response. + +For the found status the second array element is the handler that was associated with the route +and the third array element is a dictionary of placeholder names to their values. For example: + + /* Routing against GET /user/nikic/42 */ + + [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] + +### Overriding the route parser and dispatcher + +The routing process makes use of three components: A route parser, a data generator and a +dispatcher. The three components adhere to the following interfaces: + +```php + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', +]); +``` + +The above options array corresponds to the defaults. By replacing `GroupCountBased` by +`GroupPosBased` you could switch to a different dispatching strategy. + +### A Note on HEAD Requests + +The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: + +> The methods GET and HEAD MUST be supported by all general-purpose servers + +To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an +available GET route for a given resource. The PHP web SAPI transparently removes the entity body +from HEAD responses so this behavior has no effect on the vast majority of users. + +However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST +NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is +*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. + +Finally, note that applications MAY always specify their own HEAD method route for a given +resource to bypass this behavior entirely. + +### Credits + +This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. + +A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. + + +[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" +[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html +[levi]: https://github.com/morrisonlevi +[rdlowrey]: https://github.com/rdlowrey diff --git a/Sources/API/vendor/nikic/fast-route/composer.json b/Sources/API/vendor/nikic/fast-route/composer.json new file mode 100644 index 0000000..fb446a2 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/composer.json @@ -0,0 +1,24 @@ +{ + "name": "nikic/fast-route", + "description": "Fast request router for PHP", + "keywords": ["routing", "router"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": ["src/functions.php"] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + } +} diff --git a/Sources/API/vendor/nikic/fast-route/phpunit.xml b/Sources/API/vendor/nikic/fast-route/phpunit.xml new file mode 100644 index 0000000..3c807b6 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/phpunit.xml @@ -0,0 +1,24 @@ + + + + + + ./test/ + + + + + + ./src/ + + + diff --git a/Sources/API/vendor/nikic/fast-route/psalm.xml b/Sources/API/vendor/nikic/fast-route/psalm.xml new file mode 100644 index 0000000..0dca5d7 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/psalm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/API/vendor/nikic/fast-route/src/BadRouteException.php b/Sources/API/vendor/nikic/fast-route/src/BadRouteException.php new file mode 100644 index 0000000..62262ec --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/BadRouteException.php @@ -0,0 +1,7 @@ + $route) { + $suffixLen++; + $suffix .= "\t"; + + $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; + $routeMap[$suffix] = [$route->handler, $route->variables]; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php new file mode 100644 index 0000000..54d9a05 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php @@ -0,0 +1,30 @@ + $route) { + $numVariables = count($route->variables); + $numGroups = max($numGroups, $numVariables); + + $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); + $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; + + ++$numGroups; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php new file mode 100644 index 0000000..fc4dc0a --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex; + $routeMap[$offset] = [$route->handler, $route->variables]; + + $offset += count($route->variables); + } + + $regex = '~^(?:' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php new file mode 100644 index 0000000..0aebed9 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex . '(*MARK:' . $markName . ')'; + $routeMap[$markName] = [$route->handler, $route->variables]; + + ++$markName; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php new file mode 100644 index 0000000..6457290 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php @@ -0,0 +1,186 @@ +isStaticRoute($routeData)) { + $this->addStaticRoute($httpMethod, $routeData, $handler); + } else { + $this->addVariableRoute($httpMethod, $routeData, $handler); + } + } + + /** + * @return mixed[] + */ + public function getData() + { + if (empty($this->methodToRegexToRoutesMap)) { + return [$this->staticRoutes, []]; + } + + return [$this->staticRoutes, $this->generateVariableRouteData()]; + } + + /** + * @return mixed[] + */ + private function generateVariableRouteData() + { + $data = []; + foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { + $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); + $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); + $data[$method] = array_map([$this, 'processChunk'], $chunks); + } + return $data; + } + + /** + * @param int + * @return int + */ + private function computeChunkSize($count) + { + $numParts = max(1, round($count / $this->getApproxChunkSize())); + return (int) ceil($count / $numParts); + } + + /** + * @param mixed[] + * @return bool + */ + private function isStaticRoute($routeData) + { + return count($routeData) === 1 && is_string($routeData[0]); + } + + private function addStaticRoute($httpMethod, $routeData, $handler) + { + $routeStr = $routeData[0]; + + if (isset($this->staticRoutes[$httpMethod][$routeStr])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $routeStr, $httpMethod + )); + } + + if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { + foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { + if ($route->matches($routeStr)) { + throw new BadRouteException(sprintf( + 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', + $routeStr, $route->regex, $httpMethod + )); + } + } + } + + $this->staticRoutes[$httpMethod][$routeStr] = $handler; + } + + private function addVariableRoute($httpMethod, $routeData, $handler) + { + list($regex, $variables) = $this->buildRegexForRoute($routeData); + + if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $regex, $httpMethod + )); + } + + $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( + $httpMethod, $handler, $regex, $variables + ); + } + + /** + * @param mixed[] + * @return mixed[] + */ + private function buildRegexForRoute($routeData) + { + $regex = ''; + $variables = []; + foreach ($routeData as $part) { + if (is_string($part)) { + $regex .= preg_quote($part, '~'); + continue; + } + + list($varName, $regexPart) = $part; + + if (isset($variables[$varName])) { + throw new BadRouteException(sprintf( + 'Cannot use the same placeholder "%s" twice', $varName + )); + } + + if ($this->regexHasCapturingGroups($regexPart)) { + throw new BadRouteException(sprintf( + 'Regex "%s" for parameter "%s" contains a capturing group', + $regexPart, $varName + )); + } + + $variables[$varName] = $varName; + $regex .= '(' . $regexPart . ')'; + } + + return [$regex, $variables]; + } + + /** + * @param string + * @return bool + */ + private function regexHasCapturingGroups($regex) + { + if (false === strpos($regex, '(')) { + // Needs to have at least a ( to contain a capturing group + return false; + } + + // Semi-accurate detection for capturing groups + return (bool) preg_match( + '~ + (?: + \(\?\( + | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] + | \\\\ . + ) (*SKIP)(*FAIL) | + \( + (?! + \? (?! <(?![!=]) | P< | \' ) + | \* + ) + ~x', + $regex + ); + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher.php new file mode 100644 index 0000000..4ae72a3 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher.php @@ -0,0 +1,26 @@ + 'value', ...]] + * + * @param string $httpMethod + * @param string $uri + * + * @return array + */ + public function dispatch($httpMethod, $uri); +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php new file mode 100644 index 0000000..ef1eec1 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][end($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php new file mode 100644 index 0000000..493e7a9 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][count($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php new file mode 100644 index 0000000..498220e --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php @@ -0,0 +1,33 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + // find first non-empty match + for ($i = 1; '' === $matches[$i]; ++$i); + + list($handler, $varNames) = $data['routeMap'][$i]; + + $vars = []; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[$i++]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php new file mode 100644 index 0000000..22eb09b --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php new file mode 100644 index 0000000..206e879 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php @@ -0,0 +1,88 @@ +staticRouteMap[$httpMethod][$uri])) { + $handler = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $handler, []]; + } + + $varRouteData = $this->variableRouteData; + if (isset($varRouteData[$httpMethod])) { + $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + if (isset($this->staticRouteMap['GET'][$uri])) { + $handler = $this->staticRouteMap['GET'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['GET'])) { + $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + } + + // If nothing else matches, try fallback routes + if (isset($this->staticRouteMap['*'][$uri])) { + $handler = $this->staticRouteMap['*'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['*'])) { + $result = $this->dispatchVariableRoute($varRouteData['*'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // Find allowed methods for this URI by matching against all other HTTP methods as well + $allowedMethods = []; + + foreach ($this->staticRouteMap as $method => $uriMap) { + if ($method !== $httpMethod && isset($uriMap[$uri])) { + $allowedMethods[] = $method; + } + } + + foreach ($varRouteData as $method => $routeData) { + if ($method === $httpMethod) { + continue; + } + + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[] = $method; + } + } + + // If there are no allowed methods the route simply does not exist + if ($allowedMethods) { + return [self::METHOD_NOT_ALLOWED, $allowedMethods]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/Route.php b/Sources/API/vendor/nikic/fast-route/src/Route.php new file mode 100644 index 0000000..e1bf7dd --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/Route.php @@ -0,0 +1,47 @@ +httpMethod = $httpMethod; + $this->handler = $handler; + $this->regex = $regex; + $this->variables = $variables; + } + + /** + * Tests whether this route matches the given string. + * + * @param string $str + * + * @return bool + */ + public function matches($str) + { + $regex = '~^' . $this->regex . '$~'; + return (bool) preg_match($regex, $str); + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/RouteCollector.php b/Sources/API/vendor/nikic/fast-route/src/RouteCollector.php new file mode 100644 index 0000000..c1c1762 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/RouteCollector.php @@ -0,0 +1,152 @@ +routeParser = $routeParser; + $this->dataGenerator = $dataGenerator; + $this->currentGroupPrefix = ''; + } + + /** + * Adds a route to the collection. + * + * The syntax used in the $route string depends on the used route parser. + * + * @param string|string[] $httpMethod + * @param string $route + * @param mixed $handler + */ + public function addRoute($httpMethod, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $routeDatas = $this->routeParser->parse($route); + foreach ((array) $httpMethod as $method) { + foreach ($routeDatas as $routeData) { + $this->dataGenerator->addRoute($method, $routeData, $handler); + } + } + } + + /** + * Create a route group with a common prefix. + * + * All routes created in the passed callback will have the given group prefix prepended. + * + * @param string $prefix + * @param callable $callback + */ + public function addGroup($prefix, callable $callback) + { + $previousGroupPrefix = $this->currentGroupPrefix; + $this->currentGroupPrefix = $previousGroupPrefix . $prefix; + $callback($this); + $this->currentGroupPrefix = $previousGroupPrefix; + } + + /** + * Adds a GET route to the collection + * + * This is simply an alias of $this->addRoute('GET', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function get($route, $handler) + { + $this->addRoute('GET', $route, $handler); + } + + /** + * Adds a POST route to the collection + * + * This is simply an alias of $this->addRoute('POST', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function post($route, $handler) + { + $this->addRoute('POST', $route, $handler); + } + + /** + * Adds a PUT route to the collection + * + * This is simply an alias of $this->addRoute('PUT', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function put($route, $handler) + { + $this->addRoute('PUT', $route, $handler); + } + + /** + * Adds a DELETE route to the collection + * + * This is simply an alias of $this->addRoute('DELETE', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function delete($route, $handler) + { + $this->addRoute('DELETE', $route, $handler); + } + + /** + * Adds a PATCH route to the collection + * + * This is simply an alias of $this->addRoute('PATCH', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function patch($route, $handler) + { + $this->addRoute('PATCH', $route, $handler); + } + + /** + * Adds a HEAD route to the collection + * + * This is simply an alias of $this->addRoute('HEAD', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function head($route, $handler) + { + $this->addRoute('HEAD', $route, $handler); + } + + /** + * Returns the collected route data, as provided by the data generator. + * + * @return array + */ + public function getData() + { + return $this->dataGenerator->getData(); + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/RouteParser.php b/Sources/API/vendor/nikic/fast-route/src/RouteParser.php new file mode 100644 index 0000000..6a7685c --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/RouteParser.php @@ -0,0 +1,37 @@ + $segment) { + if ($segment === '' && $n !== 0) { + throw new BadRouteException('Empty optional part'); + } + + $currentRoute .= $segment; + $routeDatas[] = $this->parsePlaceholders($currentRoute); + } + return $routeDatas; + } + + /** + * Parses a route string that does not contain optional segments. + * + * @param string + * @return mixed[] + */ + private function parsePlaceholders($route) + { + if (!preg_match_all( + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER + )) { + return [$route]; + } + + $offset = 0; + $routeData = []; + foreach ($matches as $set) { + if ($set[0][1] > $offset) { + $routeData[] = substr($route, $offset, $set[0][1] - $offset); + } + $routeData[] = [ + $set[1][0], + isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX + ]; + $offset = $set[0][1] + strlen($set[0][0]); + } + + if ($offset !== strlen($route)) { + $routeData[] = substr($route, $offset); + } + + return $routeData; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/src/bootstrap.php b/Sources/API/vendor/nikic/fast-route/src/bootstrap.php new file mode 100644 index 0000000..0bce3a4 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/src/bootstrap.php @@ -0,0 +1,12 @@ + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + ]; + + /** @var RouteCollector $routeCollector */ + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + return new $options['dispatcher']($routeCollector->getData()); + } + + /** + * @param callable $routeDefinitionCallback + * @param array $options + * + * @return Dispatcher + */ + function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) + { + $options += [ + 'routeParser' => 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + 'cacheDisabled' => false, + ]; + + if (!isset($options['cacheFile'])) { + throw new \LogicException('Must specify "cacheFile" option'); + } + + if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { + $dispatchData = require $options['cacheFile']; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + if (!$options['cacheDisabled']) { + file_put_contents( + $options['cacheFile'], + ' $this->getDataGeneratorClass(), + 'dispatcher' => $this->getDispatcherClass() + ]; + } + + /** + * @dataProvider provideFoundDispatchCases + */ + public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $info = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::FOUND, $info[0]); + $this->assertSame($handler, $info[1]); + $this->assertSame($argDict, $info[2]); + } + + /** + * @dataProvider provideNotFoundDispatchCases + */ + public function testNotFoundDispatches($method, $uri, $callback) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayNotHasKey(1, $routeInfo, + 'NOT_FOUND result must only contain a single element in the returned info array' + ); + $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]); + } + + /** + * @dataProvider provideMethodNotAllowedDispatchCases + */ + public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayHasKey(1, $routeInfo, + 'METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1' + ); + + list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus); + $this->assertSame($availableMethods, $methodArray); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot use the same placeholder "test" twice + */ + public function testDuplicateVariableNameError() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET" + */ + public function testDuplicateVariableRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;) + $r->addRoute('GET', '/user/{name}', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET" + */ + public function testDuplicateStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET" + */ + public function testShadowedStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/nikic', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group + */ + public function testCapturing() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/{lang:(en|de)}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + public function provideFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/resource/123/456'; + $handler = 'handler0'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/handler2'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $handler = 'handler2'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 3 --------------------------------------------------------------------------------------> + + // reuse $callback from #2 + + $method = 'GET'; + $uri = '/user/12345'; + $handler = 'handler1'; + $argDict = ['id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 4 --------------------------------------------------------------------------------------> + + // reuse $callback from #3 + + $method = 'GET'; + $uri = '/user/NaN'; + $handler = 'handler2'; + $argDict = ['name' => 'NaN']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse $callback from #4 + + $method = 'GET'; + $uri = '/user/rdlowrey/12345'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey', 'id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 6 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/12345/extension', 'handler1'); + $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/12345.svg'; + $handler = 'handler2'; + $argDict = ['id' => '12345', 'extension' => 'svg']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/static0', 'handler2'); + $r->addRoute('GET', '/static1', 'handler3'); + $r->addRoute('HEAD', '/static1', 'handler4'); + }; + + $method = 'HEAD'; + $uri = '/user/rdlowrey'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #7 + + $method = 'HEAD'; + $uri = '/user/rdlowrey/1234'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey', 'id' => '1234']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #8 + + $method = 'HEAD'; + $uri = '/static0'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 10 ---- Test existing HEAD route used if available (no fallback) -----------------------> + + // reuse $callback from #9 + + $method = 'HEAD'; + $uri = '/static1'; + $handler = 'handler4'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 11 ---- More specified routes are not shadowed by less specific of another method ------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 12 ---- Handler of more specific routes is used, if it occurs first --------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + $r->addRoute('POST', '/user/{name}', 'handler2'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 13 ---- Route with constant suffix -----------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/edit', 'handler1'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey/edit'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 14 ---- Handle multiple methods with the same handler ----------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $argDict = []; + $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict]; + + // 17 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']]; + + // 18 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '', 'handler0'); + }; + + $cases[] = ['GET', '', $callback, 'handler0', []]; + + // 19 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a/{foo}', 'handler0'); + $r->addRoute('GET', '/b/{foo}', 'handler1'); + }; + + $cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // 20 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a', 'handler0'); + $r->addRoute('GET', '/b', 'handler1'); + }; + + $cases[] = ['HEAD', '/b', $callback, 'handler1', []]; + + // 21 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/foo', 'handler0'); + $r->addRoute('HEAD', '/{bar}', 'handler1'); + }; + + $cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']]; + + // 22 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('*', '/{user}', 'handler1'); + $r->addRoute('GET', '/user', 'handler2'); + }; + + $cases[] = ['GET', '/user', $callback, 'handler2', []]; + + // 23 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }; + + $cases[] = ['POST', '/user', $callback, 'handler0', []]; + + // 24 ---- + + $cases[] = ['HEAD', '/user', $callback, 'handler1', []]; + + // 25 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/{bar}', 'handler0'); + $r->addRoute('*', '/foo', 'handler1'); + }; + + $cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']]; + + // 26 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('*', '/{foo:.*}', 'handler1'); + }; + + $cases[] = ['POST', '/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideNotFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 1 --------------------------------------------------------------------------------------> + + // reuse callback from #0 + $method = 'POST'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 2 --------------------------------------------------------------------------------------> + + // reuse callback from #1 + $method = 'PUT'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse callback from #4 + $method = 'GET'; + $uri = '/user/rdlowrey/12345/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 6 --------------------------------------------------------------------------------------> + + // reuse callback from #5 + $method = 'HEAD'; + + $cases[] = [$method, $uri, $callback]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideMethodNotAllowedDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'POST'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + $r->addRoute('POST', '/resource/123/456', 'handler1'); + $r->addRoute('PUT', '/resource/123/456', 'handler2'); + $r->addRoute('*', '/', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET', 'POST', 'PUT']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/user/rdlowrey/42'; + $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user/{name}', 'handler1'); + $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $allowedMethods = ['POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']]; + + // 5 + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php b/Sources/API/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php new file mode 100644 index 0000000..f821ef5 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php @@ -0,0 +1,16 @@ +markTestSkipped('PHP 5.6 required for MARK support'); + } + } + + protected function getDispatcherClass() + { + return 'FastRoute\\Dispatcher\\MarkBased'; + } + + protected function getDataGeneratorClass() + { + return 'FastRoute\\DataGenerator\\MarkBased'; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php new file mode 100644 index 0000000..b6fc53f --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php @@ -0,0 +1,44 @@ +markTestSkipped('HHVM only'); + } + if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) { + $this->markTestSkipped('classname requires HHVM 3.9+'); + } + + // The typechecker recurses the whole tree, so it makes sure + // that everything in fixtures/ is valid when this runs. + + $output = []; + $exit_code = null; + exec( + 'hh_server --check ' . escapeshellarg(__DIR__ . '/../../') . ' 2>&1', + $output, + $exit_code + ); + if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) { + $this->assertTrue( + $recurse, + 'Typechecker still running after running hh_client stop' + ); + // Server already running - 3.10 => 3.11 regression: + // https://github.com/facebook/hhvm/issues/6646 + exec('hh_client stop 2>/dev/null'); + $this->testTypechecks(/* recurse = */ false); + return; + + } + $this->assertSame(0, $exit_code, implode("\n", $output)); + } +} diff --git a/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php new file mode 100644 index 0000000..05a9af2 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php @@ -0,0 +1,29 @@ + {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + ), + ); +} + +function all_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher( + $collector ==> {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + 'cacheFile' => '/dev/null', + 'cacheDisabled' => false, + ), + ); +} diff --git a/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php new file mode 100644 index 0000000..61eb541 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php @@ -0,0 +1,11 @@ + {}, shape()); +} + +function empty_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}, shape()); +} diff --git a/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php new file mode 100644 index 0000000..44b5422 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php @@ -0,0 +1,11 @@ + {}); +} + +function no_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}); +} diff --git a/Sources/API/vendor/nikic/fast-route/test/RouteCollectorTest.php b/Sources/API/vendor/nikic/fast-route/test/RouteCollectorTest.php new file mode 100644 index 0000000..cc54407 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/RouteCollectorTest.php @@ -0,0 +1,108 @@ +delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ]; + + $this->assertSame($expected, $r->routes); + } + + public function testGroups() + { + $r = new DummyRouteCollector(); + + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-one', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-two', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + }); + }); + + $r->addGroup('/admin', function (DummyRouteCollector $r) { + $r->get('-some-info', 'admin-some-info'); + }); + $r->addGroup('/admin-', function (DummyRouteCollector $r) { + $r->get('more-info', 'admin-more-info'); + }); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ['DELETE', '/group-one/delete', 'delete'], + ['GET', '/group-one/get', 'get'], + ['HEAD', '/group-one/head', 'head'], + ['PATCH', '/group-one/patch', 'patch'], + ['POST', '/group-one/post', 'post'], + ['PUT', '/group-one/put', 'put'], + ['DELETE', '/group-one/group-two/delete', 'delete'], + ['GET', '/group-one/group-two/get', 'get'], + ['HEAD', '/group-one/group-two/head', 'head'], + ['PATCH', '/group-one/group-two/patch', 'patch'], + ['POST', '/group-one/group-two/post', 'post'], + ['PUT', '/group-one/group-two/put', 'put'], + ['GET', '/admin-some-info', 'admin-some-info'], + ['GET', '/admin-more-info', 'admin-more-info'], + ]; + + $this->assertSame($expected, $r->routes); + } +} + +class DummyRouteCollector extends RouteCollector +{ + public $routes = []; + + public function __construct() + { + } + + public function addRoute($method, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $this->routes[] = [$method, $route, $handler]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/test/RouteParser/StdTest.php b/Sources/API/vendor/nikic/fast-route/test/RouteParser/StdTest.php new file mode 100644 index 0000000..e13e4de --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/RouteParser/StdTest.php @@ -0,0 +1,154 @@ +parse($routeString); + $this->assertSame($expectedRouteDatas, $routeDatas); + } + + /** @dataProvider provideTestParseError */ + public function testParseError($routeString, $expectedExceptionMessage) + { + $parser = new Std(); + $this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage); + $parser->parse($routeString); + } + + public function provideTestParse() + { + return [ + [ + '/test', + [ + ['/test'], + ] + ], + [ + '/test/{param}', + [ + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/te{ param }st', + [ + ['/te', ['param', '[^/]+'], 'st'] + ] + ], + [ + '/test/{param1}/test2/{param2}', + [ + ['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']] + ] + ], + [ + '/test/{param:\d+}', + [ + ['/test/', ['param', '\d+']] + ] + ], + [ + '/test/{ param : \d{1,9} }', + [ + ['/test/', ['param', '\d{1,9}']] + ] + ], + [ + '/test[opt]', + [ + ['/test'], + ['/testopt'], + ] + ], + [ + '/test[/{param}]', + [ + ['/test'], + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/{param}[opt]', + [ + ['/', ['param', '[^/]+']], + ['/', ['param', '[^/]+'], 'opt'] + ] + ], + [ + '/test[/{name}[/{id:[0-9]+}]]', + [ + ['/test'], + ['/test/', ['name', '[^/]+']], + ['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']], + ] + ], + [ + '', + [ + [''], + ] + ], + [ + '[test]', + [ + [''], + ['test'], + ] + ], + [ + '/{foo-bar}', + [ + ['/', ['foo-bar', '[^/]+']] + ] + ], + [ + '/{_foo:.*}', + [ + ['/', ['_foo', '.*']] + ] + ], + ]; + } + + public function provideTestParseError() + { + return [ + [ + '/test[opt', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[opt[opt2]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/testopt]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[]', + 'Empty optional part' + ], + [ + '/test[[opt]]', + 'Empty optional part' + ], + [ + '[[test]]', + 'Empty optional part' + ], + [ + '/test[/opt]/required', + 'Optional segments can only occur at the end of a route' + ], + ]; + } +} diff --git a/Sources/API/vendor/nikic/fast-route/test/bootstrap.php b/Sources/API/vendor/nikic/fast-route/test/bootstrap.php new file mode 100644 index 0000000..3023f41 --- /dev/null +++ b/Sources/API/vendor/nikic/fast-route/test/bootstrap.php @@ -0,0 +1,11 @@ +=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/cache/src/CacheException.php b/Sources/API/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..bb785f4 --- /dev/null +++ b/Sources/API/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/container/src/ContainerExceptionInterface.php b/Sources/API/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/Sources/API/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +=7.0.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/http-factory/src/RequestFactoryInterface.php b/Sources/API/vendor/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 0000000..cb39a08 --- /dev/null +++ b/Sources/API/vendor/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/http-message/src/MessageInterface.php b/Sources/API/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/Sources/API/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/Sources/API/vendor/psr/http-message/src/RequestInterface.php b/Sources/API/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/Sources/API/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/Sources/API/vendor/psr/http-message/src/StreamInterface.php b/Sources/API/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/Sources/API/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/Sources/API/vendor/psr/http-server-handler/LICENSE b/Sources/API/vendor/psr/http-server-handler/LICENSE new file mode 100644 index 0000000..b71ec5d --- /dev/null +++ b/Sources/API/vendor/psr/http-server-handler/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/psr/http-server-handler/README.md b/Sources/API/vendor/psr/http-server-handler/README.md new file mode 100644 index 0000000..1b7b486 --- /dev/null +++ b/Sources/API/vendor/psr/http-server-handler/README.md @@ -0,0 +1,6 @@ +HTTP Server Handler +=================== + +Provides the `RequestHandlerInterface` of [PSR-15][psr-15]. + +[psr-15]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-15-request-handlers.md diff --git a/Sources/API/vendor/psr/http-server-handler/composer.json b/Sources/API/vendor/psr/http-server-handler/composer.json new file mode 100644 index 0000000..9e8b51d --- /dev/null +++ b/Sources/API/vendor/psr/http-server-handler/composer.json @@ -0,0 +1,36 @@ +{ + "name": "psr/http-server-handler", + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "psr", + "psr-7", + "psr-15", + "http-interop", + "http", + "server", + "handler", + "request", + "response" + ], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/http-server-handler/src/RequestHandlerInterface.php b/Sources/API/vendor/psr/http-server-handler/src/RequestHandlerInterface.php new file mode 100644 index 0000000..83911e2 --- /dev/null +++ b/Sources/API/vendor/psr/http-server-handler/src/RequestHandlerInterface.php @@ -0,0 +1,22 @@ +=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/http-server-middleware/src/MiddlewareInterface.php b/Sources/API/vendor/psr/http-server-middleware/src/MiddlewareInterface.php new file mode 100644 index 0000000..a6c14f8 --- /dev/null +++ b/Sources/API/vendor/psr/http-server-middleware/src/MiddlewareInterface.php @@ -0,0 +1,25 @@ += 5.3. + +[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) +[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) + + +This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). + +## Install + +For PHP version **`>= 5.6`**: + +``` +composer require ralouphie/getallheaders +``` + +For PHP version **`< 5.6`**: + +``` +composer require ralouphie/getallheaders "^2" +``` diff --git a/Sources/API/vendor/ralouphie/getallheaders/composer.json b/Sources/API/vendor/ralouphie/getallheaders/composer.json new file mode 100644 index 0000000..de8ce62 --- /dev/null +++ b/Sources/API/vendor/ralouphie/getallheaders/composer.json @@ -0,0 +1,26 @@ +{ + "name": "ralouphie/getallheaders", + "description": "A polyfill for getallheaders.", + "license": "MIT", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6.5", + "php-coveralls/php-coveralls": "^2.1" + }, + "autoload": { + "files": ["src/getallheaders.php"] + }, + "autoload-dev": { + "psr-4": { + "getallheaders\\Tests\\": "tests/" + } + } +} diff --git a/Sources/API/vendor/ralouphie/getallheaders/src/getallheaders.php b/Sources/API/vendor/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 0000000..c7285a5 --- /dev/null +++ b/Sources/API/vendor/ralouphie/getallheaders/src/getallheaders.php @@ -0,0 +1,46 @@ + 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ); + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + +} diff --git a/Sources/API/vendor/slim/psr7/LICENSE.md b/Sources/API/vendor/slim/psr7/LICENSE.md new file mode 100644 index 0000000..2bd2d5a --- /dev/null +++ b/Sources/API/vendor/slim/psr7/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Slim Framework + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Sources/API/vendor/slim/psr7/MAINTAINERS.md b/Sources/API/vendor/slim/psr7/MAINTAINERS.md new file mode 100644 index 0000000..5c2496e --- /dev/null +++ b/Sources/API/vendor/slim/psr7/MAINTAINERS.md @@ -0,0 +1,17 @@ +# Maintainers + +There aren't many rules for maintainers of Slim-Psr7 to remember; what we have is listed here. + +## We don't merge our own PRs + +Our code is better if more than one set of eyes looks at it. Therefore we do not merge our own pull requests unless there is an exceptional circumstance. This helps to spot errors in the patch and also enables us to share information about the project around the maintainer team. + +## PRs tagged `[WIP]` are not ready to be merged + +Sometimes it's helpful to collaborate on a patch before it's ready to be merged. We use the `[WIP]` tag (for _Work in Progress_) in the title to mark these PRs. + +If a PR has `[WIP]` in its title, then it is not to be merged. The person who raised the PR will remove the `[WIP]` tag when they are ready for a full review and merge. + +## Assign a merged PR to a milestone + +By ensuring that all merged PRs are assigned to a milestone, we can easily find which PRs were in which release. diff --git a/Sources/API/vendor/slim/psr7/composer.json b/Sources/API/vendor/slim/psr7/composer.json new file mode 100644 index 0000000..318f710 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/composer.json @@ -0,0 +1,76 @@ +{ + "name": "slim/psr7", + "type": "library", + "description": "Strict PSR-7 implementation", + "keywords": ["psr7","psr-7","http"], + "homepage": "https://www.slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "fig/http-message-util": "^1.1.5", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "ext-json": "*", + "adriansuter/php-autoload-override": "^1.3", + "http-interop/http-factory-tests": "^0.9.0", + "php-http/psr7-integration-tests": "dev-master", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "provide": { + "psr/http-message-implementation": "1.0", + "psr/http-factory-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Slim\\Tests\\Psr7\\": "tests" + } + }, + "scripts": { + "test": [ + "@phpunit", + "@phpcs", + "@phpstan" + ], + "phpunit": "phpunit", + "phpcs": "phpcs", + "phpstan": "phpstan --memory-limit=-1" + }, + "config": { + "sort-packages": true + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Cookies.php b/Sources/API/vendor/slim/psr7/src/Cookies.php new file mode 100644 index 0000000..044d438 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Cookies.php @@ -0,0 +1,218 @@ + '', + 'domain' => null, + 'hostonly' => null, + 'path' => null, + 'expires' => null, + 'secure' => false, + 'httponly' => false, + 'samesite' => null + ]; + + /** + * @param array $cookies + */ + public function __construct(array $cookies = []) + { + $this->requestCookies = $cookies; + } + + /** + * Set default cookie properties + * + * @param array $settings + * + * @return static + */ + public function setDefaults(array $settings): self + { + $this->defaults = array_replace($this->defaults, $settings); + + return $this; + } + + /** + * Get cookie + * + * @param string $name + * @param string|array|null $default + * @return mixed|null + */ + public function get(string $name, $default = null) + { + return array_key_exists($name, $this->requestCookies) ? $this->requestCookies[$name] : $default; + } + + /** + * Set cookie + * + * @param string $name + * @param string|array $value + * @return static + */ + public function set(string $name, $value): self + { + if (!is_array($value)) { + $value = ['value' => $value]; + } + + $this->responseCookies[$name] = array_replace($this->defaults, $value); + + return $this; + } + + /** + * Convert all response cookies into an associate array of header values + * + * @return array + */ + public function toHeaders(): array + { + $headers = []; + + foreach ($this->responseCookies as $name => $properties) { + $headers[] = $this->toHeader($name, $properties); + } + + return $headers; + } + + /** + * Convert to `Set-Cookie` header + * + * @param string $name Cookie name + * @param array $properties Cookie properties + * + * @return string + */ + protected function toHeader(string $name, array $properties): string + { + $result = urlencode($name) . '=' . urlencode($properties['value']); + + if (isset($properties['domain'])) { + $result .= '; domain=' . $properties['domain']; + } + + if (isset($properties['path'])) { + $result .= '; path=' . $properties['path']; + } + + if (isset($properties['expires'])) { + if (is_string($properties['expires'])) { + $timestamp = strtotime($properties['expires']); + } else { + $timestamp = (int) $properties['expires']; + } + if ($timestamp && $timestamp !== 0) { + $result .= '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp); + } + } + + if (isset($properties['secure']) && $properties['secure']) { + $result .= '; secure'; + } + + if (isset($properties['hostonly']) && $properties['hostonly']) { + $result .= '; HostOnly'; + } + + if (isset($properties['httponly']) && $properties['httponly']) { + $result .= '; HttpOnly'; + } + + if ( + isset($properties['samesite']) + && in_array(strtolower($properties['samesite']), ['lax', 'strict', 'none'], true) + ) { + // While strtolower is needed for correct comparison, the RFC doesn't care about case + $result .= '; SameSite=' . $properties['samesite']; + } + + return $result; + } + + /** + * Parse cookie values from header value + * + * Returns an associative array of cookie names and values + * + * @param string|array $header + * + * @return array + */ + public static function parseHeader($header): array + { + if (is_array($header)) { + $header = $header[0] ?? ''; + } + + if (!is_string($header)) { + throw new InvalidArgumentException('Cannot parse Cookie data. Header value must be a string.'); + } + + $header = rtrim($header, "\r\n"); + $pieces = preg_split('@[;]\s*@', $header); + $cookies = []; + + if (is_array($pieces)) { + foreach ($pieces as $cookie) { + $cookie = explode('=', $cookie, 2); + + if (count($cookie) === 2) { + $key = urldecode($cookie[0]); + $value = urldecode($cookie[1]); + + if (!isset($cookies[$key])) { + $cookies[$key] = $value; + } + } + } + } + + return $cookies; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Environment.php b/Sources/API/vendor/slim/psr7/src/Environment.php new file mode 100644 index 0000000..55f187b --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Environment.php @@ -0,0 +1,55 @@ + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', + 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', + 'HTTP_USER_AGENT' => 'Slim Framework', + 'QUERY_STRING' => '', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'REQUEST_SCHEME' => $scheme, + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + 'REQUEST_URI' => '', + 'SCRIPT_NAME' => '', + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => $port, + 'SERVER_PROTOCOL' => 'HTTP/1.1', + ], $data); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/RequestFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/RequestFactory.php new file mode 100644 index 0000000..e825ee3 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/RequestFactory.php @@ -0,0 +1,59 @@ +streamFactory = $streamFactory ?? new StreamFactory(); + $this->uriFactory = $uriFactory ?? new UriFactory(); + } + + /** + * {@inheritdoc} + */ + public function createRequest(string $method, $uri): RequestInterface + { + if (is_string($uri)) { + $uri = $this->uriFactory->createUri($uri); + } + + if (!$uri instanceof UriInterface) { + throw new InvalidArgumentException( + 'Parameter 2 of RequestFactory::createRequest() must be a string or a compatible UriInterface.' + ); + } + + $body = $this->streamFactory->createStream(); + + return new Request($method, $uri, new Headers(), [], [], $body); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/ResponseFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/ResponseFactory.php new file mode 100644 index 0000000..c1a23a7 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/ResponseFactory.php @@ -0,0 +1,35 @@ +withStatus($code, $reasonPhrase); + } + + return $res; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/ServerRequestFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/ServerRequestFactory.php new file mode 100644 index 0000000..5bc2447 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/ServerRequestFactory.php @@ -0,0 +1,110 @@ +streamFactory = $streamFactory ?? new StreamFactory(); + $this->uriFactory = $uriFactory ?? new UriFactory(); + } + + /** + * {@inheritdoc} + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (is_string($uri)) { + $uri = $this->uriFactory->createUri($uri); + } + + if (!$uri instanceof UriInterface) { + throw new InvalidArgumentException('URI must either be string or instance of ' . UriInterface::class); + } + + $body = $this->streamFactory->createStream(); + $headers = new Headers(); + $cookies = []; + + if (!empty($serverParams)) { + $headers = Headers::createFromGlobals(); + $cookies = Cookies::parseHeader($headers->getHeader('Cookie', [])); + } + + return new Request($method, $uri, $headers, $cookies, $serverParams, $body); + } + + /** + * Create new ServerRequest from environment. + * + * @internal This method is not part of PSR-17 + * + * @return Request + */ + public static function createFromGlobals(): Request + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $uri = (new UriFactory())->createFromGlobals($_SERVER); + + $headers = Headers::createFromGlobals(); + $cookies = Cookies::parseHeader($headers->getHeader('Cookie', [])); + + // Cache the php://input stream as it cannot be re-read + $cacheResource = fopen('php://temp', 'wb+'); + $cache = $cacheResource ? new Stream($cacheResource) : null; + + $body = (new StreamFactory())->createStreamFromFile('php://input', 'r', $cache); + $uploadedFiles = UploadedFile::createFromGlobals($_SERVER); + + $request = new Request($method, $uri, $headers, $cookies, $_SERVER, $body, $uploadedFiles); + $contentTypes = $request->getHeader('Content-Type'); + + $parsedContentType = ''; + foreach ($contentTypes as $contentType) { + $fragments = explode(';', $contentType); + $parsedContentType = current($fragments); + } + + $contentTypesWithParsedBodies = ['application/x-www-form-urlencoded', 'multipart/form-data']; + if ($method === 'POST' && in_array($parsedContentType, $contentTypesWithParsedBodies)) { + return $request->withParsedBody($_POST); + } + + return $request; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/StreamFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/StreamFactory.php new file mode 100644 index 0000000..31d99b5 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/StreamFactory.php @@ -0,0 +1,95 @@ +createStreamFromResource($resource); + } + + /** + * {@inheritdoc} + */ + public function createStreamFromFile( + string $filename, + string $mode = 'r', + StreamInterface $cache = null + ): StreamInterface { + set_error_handler( + static function (int $errno, string $errstr) use ($filename, $mode): void { + throw new RuntimeException( + "Unable to open $filename using mode $mode: $errstr", + $errno + ); + } + ); + + try { + $resource = fopen($filename, $mode); + } catch (ValueError $exception) { + throw new RuntimeException("Unable to open $filename using mode $mode: " . $exception->getMessage()); + } finally { + restore_error_handler(); + } + + if (!is_resource($resource)) { + throw new RuntimeException( + "StreamFactory::createStreamFromFile() could not create resource from file `$filename`" + ); + } + + return new Stream($resource, $cache); + } + + /** + * {@inheritdoc} + */ + public function createStreamFromResource($resource, StreamInterface $cache = null): StreamInterface + { + if (!is_resource($resource)) { + throw new InvalidArgumentException( + 'Parameter 1 of StreamFactory::createStreamFromResource() must be a resource.' + ); + } + + return new Stream($resource, $cache); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/UploadedFileFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/UploadedFileFactory.php new file mode 100644 index 0000000..5699e96 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/UploadedFileFactory.php @@ -0,0 +1,47 @@ +getMetadata('uri'); + + if (!is_string($file) || !$stream->isReadable()) { + throw new InvalidArgumentException('File is not readable.'); + } + + if ($size === null) { + $size = $stream->getSize(); + } + + return new UploadedFile($stream, $clientFilename, $clientMediaType, $size, $error); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Factory/UriFactory.php b/Sources/API/vendor/slim/psr7/src/Factory/UriFactory.php new file mode 100644 index 0000000..74ab377 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Factory/UriFactory.php @@ -0,0 +1,113 @@ + 1) { + $queryString = parse_url('https://www.example.com' . $globals['REQUEST_URI'], PHP_URL_QUERY) ?? ''; + } + } + + // Build Uri and return + return new Uri($scheme, $host, $port, $requestUri, $queryString, '', $username, $password); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Header.php b/Sources/API/vendor/slim/psr7/src/Header.php new file mode 100644 index 0000000..ecdad32 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Header.php @@ -0,0 +1,96 @@ +originalName = $originalName; + $this->normalizedName = $normalizedName; + $this->values = $values; + } + + /** + * @return string + */ + public function getOriginalName(): string + { + return $this->originalName; + } + + /** + * @return string + */ + public function getNormalizedName(): string + { + return $this->normalizedName; + } + + /** + * @param string $value + * + * @return self + */ + public function addValue(string $value): self + { + $this->values[] = $value; + + return $this; + } + + /** + * @param array|string $values + * + * @return self + */ + public function addValues($values): self + { + if (is_string($values)) { + return $this->addValue($values); + } + + if (!is_array($values)) { + throw new InvalidArgumentException('Parameter 1 of Header::addValues() should be a string or an array.'); + } + + $this->values = array_merge($this->values, $values); + + return $this; + } + + /** + * @return array + */ + public function getValues(): array + { + return $this->values; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Headers.php b/Sources/API/vendor/slim/psr7/src/Headers.php new file mode 100644 index 0000000..038e792 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Headers.php @@ -0,0 +1,318 @@ +globals = $globals ?? $_SERVER; + $this->setHeaders($headers); + } + + /** + * {@inheritdoc} + */ + public function addHeader($name, $value): HeadersInterface + { + [$values, $originalName, $normalizedName] = $this->prepareHeader($name, $value); + + if (isset($this->headers[$normalizedName])) { + $header = $this->headers[$normalizedName]; + $header->addValues($values); + } else { + $this->headers[$normalizedName] = new Header($originalName, $normalizedName, $values); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeHeader(string $name): HeadersInterface + { + $name = $this->normalizeHeaderName($name); + unset($this->headers[$name]); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeader(string $name, $default = []): array + { + $name = $this->normalizeHeaderName($name); + + if (isset($this->headers[$name])) { + $header = $this->headers[$name]; + return $header->getValues(); + } + + if (empty($default)) { + return $default; + } + + $this->validateHeader($name, $default); + return $this->trimHeaderValue($default); + } + + /** + * {@inheritdoc} + */ + public function setHeader($name, $value): HeadersInterface + { + [$values, $originalName, $normalizedName] = $this->prepareHeader($name, $value); + + // Ensure we preserve original case if the header already exists in the stack + if (isset($this->headers[$normalizedName])) { + $existingHeader = $this->headers[$normalizedName]; + $originalName = $existingHeader->getOriginalName(); + } + + $this->headers[$normalizedName] = new Header($originalName, $normalizedName, $values); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setHeaders(array $headers): HeadersInterface + { + $this->headers = []; + + foreach ($this->parseAuthorizationHeader($headers) as $name => $value) { + $this->addHeader($name, $value); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasHeader(string $name): bool + { + $name = $this->normalizeHeaderName($name); + return isset($this->headers[$name]); + } + + /** + * {@inheritdoc} + */ + public function getHeaders(bool $originalCase = false): array + { + $headers = []; + + foreach ($this->headers as $header) { + $name = $originalCase ? $header->getOriginalName() : $header->getNormalizedName(); + $headers[$name] = $header->getValues(); + } + + return $headers; + } + + /** + * @param string $name + * @param bool $preserveCase + * @return string + */ + protected function normalizeHeaderName(string $name, bool $preserveCase = false): string + { + $name = strtr($name, '_', '-'); + + if (!$preserveCase) { + $name = strtolower($name); + } + + if (strpos(strtolower($name), 'http-') === 0) { + $name = substr($name, 5); + } + + return $name; + } + + /** + * Parse incoming headers and determine Authorization header from original headers + * + * @param array $headers + * @return array + */ + protected function parseAuthorizationHeader(array $headers): array + { + $hasAuthorizationHeader = false; + foreach ($headers as $name => $value) { + if (strtolower((string) $name) === 'authorization') { + $hasAuthorizationHeader = true; + break; + } + } + + if (!$hasAuthorizationHeader) { + if (isset($this->globals['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $this->globals['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($this->globals['PHP_AUTH_USER'])) { + $pw = $this->globals['PHP_AUTH_PW'] ?? ''; + $headers['Authorization'] = 'Basic ' . base64_encode($this->globals['PHP_AUTH_USER'] . ':' . $pw); + } elseif (isset($this->globals['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $this->globals['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + + /** + * @param array|string $value + * + * @return array + */ + protected function trimHeaderValue($value): array + { + $items = is_array($value) ? $value : [$value]; + $result = []; + foreach ($items as $item) { + $result[] = trim((string) $item, " \t"); + } + return $result; + } + + /** + * @param string $name + * @param array|string $value + * + * @throws InvalidArgumentException + * + * @return array + */ + protected function prepareHeader($name, $value): array + { + $this->validateHeader($name, $value); + $values = $this->trimHeaderValue($value); + $originalName = $this->normalizeHeaderName($name, true); + $normalizedName = $this->normalizeHeaderName($name); + return [$values, $originalName, $normalizedName]; + } + + /** + * Make sure the header complies with RFC 7230. + * + * Header names must be a non-empty string consisting of token characters. + * + * Header values must be strings consisting of visible characters with all optional + * leading and trailing whitespace stripped. This method will always strip such + * optional whitespace. Note that the method does not allow folding whitespace within + * the values as this was deprecated for almost all instances by the RFC. + * + * header-field = field-name ":" OWS field-value OWS + * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" + * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) + * OWS = *( SP / HTAB ) + * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + * + * @param string $name + * @param array|string $value + * + * @throws InvalidArgumentException; + */ + protected function validateHeader($name, $value): void + { + $this->validateHeaderName($name); + $this->validateHeaderValue($value); + } + + /** + * @param mixed $name + * + * @throws InvalidArgumentException + */ + protected function validateHeaderName($name): void + { + if (!is_string($name) || preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $name) !== 1) { + throw new InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + } + + /** + * @param mixed $value + * + * @throws InvalidArgumentException + */ + protected function validateHeaderValue($value): void + { + $items = is_array($value) ? $value : [$value]; + + if (empty($items)) { + throw new InvalidArgumentException( + 'Header values must be a string or an array of strings, empty array given.' + ); + } + + $pattern = "@^[ \t\x21-\x7E\x80-\xFF]*$@"; + foreach ($items as $item) { + $hasInvalidType = !is_numeric($item) && !is_string($item); + $rejected = $hasInvalidType || preg_match($pattern, (string) $item) !== 1; + if ($rejected) { + throw new InvalidArgumentException( + 'Header values must be RFC 7230 compatible strings.' + ); + } + } + } + + /** + * @return static + */ + public static function createFromGlobals() + { + $headers = null; + + if (function_exists('getallheaders')) { + $headers = getallheaders(); + } + + if (!is_array($headers)) { + $headers = []; + } + + return new static($headers); + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Interfaces/HeadersInterface.php b/Sources/API/vendor/slim/psr7/src/Interfaces/HeadersInterface.php new file mode 100644 index 0000000..3f486f1 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Interfaces/HeadersInterface.php @@ -0,0 +1,90 @@ + true, + '1.1' => true, + '2.0' => true, + '2' => true, + ]; + + /** + * @var HeadersInterface + */ + protected $headers; + + /** + * @var StreamInterface + */ + protected $body; + + /** + * Disable magic setter to ensure immutability + * + * @param string $name The property name + * @param mixed $value The property value + * + * @return void + */ + public function __set($name, $value): void + { + // Do nothing + } + + /** + * {@inheritdoc} + */ + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + /** + * @return static + * {@inheritdoc} + */ + public function withProtocolVersion($version) + { + if (!isset(self::$validProtocolVersions[$version])) { + throw new InvalidArgumentException( + 'Invalid HTTP version. Must be one of: ' + . implode(', ', array_keys(self::$validProtocolVersions)) + ); + } + + $clone = clone $this; + $clone->protocolVersion = $version; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getHeaders(): array + { + return $this->headers->getHeaders(true); + } + + /** + * {@inheritdoc} + */ + public function hasHeader($name): bool + { + return $this->headers->hasHeader($name); + } + + /** + * {@inheritdoc} + */ + public function getHeader($name): array + { + return $this->headers->getHeader($name); + } + + /** + * {@inheritdoc} + */ + public function getHeaderLine($name): string + { + $values = $this->headers->getHeader($name); + return implode(',', $values); + } + + /** + * @return static + * {@inheritdoc} + */ + public function withHeader($name, $value) + { + $clone = clone $this; + $clone->headers->setHeader($name, $value); + + if ($this instanceof Response && $this->body instanceof NonBufferedBody) { + header(sprintf('%s: %s', $name, $clone->getHeaderLine($name))); + } + + return $clone; + } + + /** + * @return static + * {@inheritdoc} + */ + public function withAddedHeader($name, $value) + { + $clone = clone $this; + $clone->headers->addHeader($name, $value); + + if ($this instanceof Response && $this->body instanceof NonBufferedBody) { + header(sprintf('%s: %s', $name, $clone->getHeaderLine($name))); + } + + return $clone; + } + + /** + * @return static + * {@inheritdoc} + */ + public function withoutHeader($name) + { + $clone = clone $this; + $clone->headers->removeHeader($name); + + if ($this instanceof Response && $this->body instanceof NonBufferedBody) { + header_remove($name); + } + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getBody(): StreamInterface + { + return $this->body; + } + + /** + * @return static + * {@inheritdoc} + */ + public function withBody(StreamInterface $body) + { + $clone = clone $this; + $clone->body = $body; + + return $clone; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/NonBufferedBody.php b/Sources/API/vendor/slim/psr7/src/NonBufferedBody.php new file mode 100644 index 0000000..ad22c2d --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/NonBufferedBody.php @@ -0,0 +1,153 @@ +method = $this->filterMethod($method); + $this->uri = $uri; + $this->headers = $headers; + $this->cookies = $cookies; + $this->serverParams = $serverParams; + $this->attributes = []; + $this->body = $body; + $this->uploadedFiles = $uploadedFiles; + + if (isset($serverParams['SERVER_PROTOCOL'])) { + $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']); + } + + if (!$this->headers->hasHeader('Host') || $this->uri->getHost() !== '') { + $this->headers->setHeader('Host', $this->uri->getHost()); + } + } + + /** + * This method is applied to the cloned object after PHP performs an initial shallow-copy. + * This method completes a deep-copy by creating new objects for the cloned object's internal reference pointers. + */ + public function __clone() + { + $this->headers = clone $this->headers; + $this->body = clone $this->body; + } + + /** + * {@inheritdoc} + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withMethod($method) + { + $method = $this->filterMethod($method); + $clone = clone $this; + $clone->method = $method; + + return $clone; + } + + /** + * Validate the HTTP method + * + * @param string $method + * + * @return string + * + * @throws InvalidArgumentException on invalid HTTP method. + */ + protected function filterMethod($method): string + { + /** @var mixed $method */ + if (!is_string($method)) { + throw new InvalidArgumentException(sprintf( + 'Unsupported HTTP method; must be a string, received %s', + (is_object($method) ? get_class($method) : gettype($method)) + )); + } + + if (preg_match("/^[!#$%&'*+.^_`|~0-9a-z-]+$/i", $method) !== 1) { + throw new InvalidArgumentException(sprintf( + 'Unsupported HTTP method "%s" provided', + $method + )); + } + + return $method; + } + + /** + * {@inheritdoc} + */ + public function getRequestTarget(): string + { + if ($this->requestTarget) { + return $this->requestTarget; + } + + if ($this->uri === null) { + return '/'; + } + + $path = $this->uri->getPath(); + $path = '/' . ltrim($path, '/'); + + $query = $this->uri->getQuery(); + if ($query) { + $path .= '?' . $query; + } + + return $path; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withRequestTarget($requestTarget) + { + if (!is_string($requestTarget) || preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; must be a string and cannot contain whitespace' + ); + } + + $clone = clone $this; + $clone->requestTarget = $requestTarget; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getUri(): UriInterface + { + return $this->uri; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + $clone = clone $this; + $clone->uri = $uri; + + if (!$preserveHost && $uri->getHost() !== '') { + $clone->headers->setHeader('Host', $uri->getHost()); + return $clone; + } + + if (($uri->getHost() !== '' && !$this->hasHeader('Host') || $this->getHeaderLine('Host') === '')) { + $clone->headers->setHeader('Host', $uri->getHost()); + return $clone; + } + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams(): array + { + return $this->cookies; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withCookieParams(array $cookies) + { + $clone = clone $this; + $clone->cookies = $cookies; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams(): array + { + if (is_array($this->queryParams)) { + return $this->queryParams; + } + + if ($this->uri === null) { + return []; + } + + parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data + assert(is_array($this->queryParams)); + + return $this->queryParams; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withQueryParams(array $query) + { + $clone = clone $this; + $clone->queryParams = $query; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withUploadedFiles(array $uploadedFiles) + { + $clone = clone $this; + $clone->uploadedFiles = $uploadedFiles; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getServerParams(): array + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return $this->attributes[$name] ?? $default; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withAttribute($name, $value) + { + $clone = clone $this; + $clone->attributes[$name] = $value; + + return $clone; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withoutAttribute($name) + { + $clone = clone $this; + + unset($clone->attributes[$name]); + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withParsedBody($data) + { + /** @var mixed $data */ + if (!is_null($data) && !is_object($data) && !is_array($data)) { + throw new InvalidArgumentException('Parsed body value must be an array, an object, or null'); + } + + $clone = clone $this; + $clone->parsedBody = $data; + + return $clone; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Response.php b/Sources/API/vendor/slim/psr7/src/Response.php new file mode 100644 index 0000000..c6d4c6e --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Response.php @@ -0,0 +1,216 @@ + 'Continue', + StatusCodeInterface::STATUS_SWITCHING_PROTOCOLS => 'Switching Protocols', + StatusCodeInterface::STATUS_PROCESSING => 'Processing', + + // Successful 2xx + StatusCodeInterface::STATUS_OK => 'OK', + StatusCodeInterface::STATUS_CREATED => 'Created', + StatusCodeInterface::STATUS_ACCEPTED => 'Accepted', + StatusCodeInterface::STATUS_NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information', + StatusCodeInterface::STATUS_NO_CONTENT => 'No Content', + StatusCodeInterface::STATUS_RESET_CONTENT => 'Reset Content', + StatusCodeInterface::STATUS_PARTIAL_CONTENT => 'Partial Content', + StatusCodeInterface::STATUS_MULTI_STATUS => 'Multi-Status', + StatusCodeInterface::STATUS_ALREADY_REPORTED => 'Already Reported', + StatusCodeInterface::STATUS_IM_USED => 'IM Used', + + // Redirection 3xx + StatusCodeInterface::STATUS_MULTIPLE_CHOICES => 'Multiple Choices', + StatusCodeInterface::STATUS_MOVED_PERMANENTLY => 'Moved Permanently', + StatusCodeInterface::STATUS_FOUND => 'Found', + StatusCodeInterface::STATUS_SEE_OTHER => 'See Other', + StatusCodeInterface::STATUS_NOT_MODIFIED => 'Not Modified', + StatusCodeInterface::STATUS_USE_PROXY => 'Use Proxy', + StatusCodeInterface::STATUS_RESERVED => '(Unused)', + StatusCodeInterface::STATUS_TEMPORARY_REDIRECT => 'Temporary Redirect', + StatusCodeInterface::STATUS_PERMANENT_REDIRECT => 'Permanent Redirect', + + // Client Error 4xx + StatusCodeInterface::STATUS_BAD_REQUEST => 'Bad Request', + StatusCodeInterface::STATUS_UNAUTHORIZED => 'Unauthorized', + StatusCodeInterface::STATUS_PAYMENT_REQUIRED => 'Payment Required', + StatusCodeInterface::STATUS_FORBIDDEN => 'Forbidden', + StatusCodeInterface::STATUS_NOT_FOUND => 'Not Found', + StatusCodeInterface::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', + StatusCodeInterface::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', + StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + StatusCodeInterface::STATUS_REQUEST_TIMEOUT => 'Request Timeout', + StatusCodeInterface::STATUS_CONFLICT => 'Conflict', + StatusCodeInterface::STATUS_GONE => 'Gone', + StatusCodeInterface::STATUS_LENGTH_REQUIRED => 'Length Required', + StatusCodeInterface::STATUS_PRECONDITION_FAILED => 'Precondition Failed', + StatusCodeInterface::STATUS_PAYLOAD_TOO_LARGE => 'Request Entity Too Large', + StatusCodeInterface::STATUS_URI_TOO_LONG => 'Request-URI Too Long', + StatusCodeInterface::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + StatusCodeInterface::STATUS_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', + StatusCodeInterface::STATUS_EXPECTATION_FAILED => 'Expectation Failed', + StatusCodeInterface::STATUS_IM_A_TEAPOT => 'I\'m a teapot', + StatusCodeInterface::STATUS_MISDIRECTED_REQUEST => 'Misdirected Request', + StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', + StatusCodeInterface::STATUS_LOCKED => 'Locked', + StatusCodeInterface::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', + StatusCodeInterface::STATUS_UPGRADE_REQUIRED => 'Upgrade Required', + StatusCodeInterface::STATUS_PRECONDITION_REQUIRED => 'Precondition Required', + StatusCodeInterface::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', + StatusCodeInterface::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', + 444 => 'Connection Closed Without Response', + StatusCodeInterface::STATUS_UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons', + 499 => 'Client Closed Request', + + // Server Error 5xx + StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error', + StatusCodeInterface::STATUS_NOT_IMPLEMENTED => 'Not Implemented', + StatusCodeInterface::STATUS_BAD_GATEWAY => 'Bad Gateway', + StatusCodeInterface::STATUS_SERVICE_UNAVAILABLE => 'Service Unavailable', + StatusCodeInterface::STATUS_GATEWAY_TIMEOUT => 'Gateway Timeout', + StatusCodeInterface::STATUS_VERSION_NOT_SUPPORTED => 'HTTP Version Not Supported', + StatusCodeInterface::STATUS_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + StatusCodeInterface::STATUS_INSUFFICIENT_STORAGE => 'Insufficient Storage', + StatusCodeInterface::STATUS_LOOP_DETECTED => 'Loop Detected', + StatusCodeInterface::STATUS_NOT_EXTENDED => 'Not Extended', + StatusCodeInterface::STATUS_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', + 599 => 'Network Connect Timeout Error', + ]; + + /** + * @param int $status The response status code. + * @param HeadersInterface|null $headers The response headers. + * @param StreamInterface|null $body The response body. + */ + public function __construct( + int $status = StatusCodeInterface::STATUS_OK, + ?HeadersInterface $headers = null, + ?StreamInterface $body = null + ) { + $this->status = $this->filterStatus($status); + $this->headers = $headers ?: new Headers([], []); + $this->body = $body ?: (new StreamFactory())->createStream(); + } + + /** + * This method is applied to the cloned object after PHP performs an initial shallow-copy. + * This method completes a deep-copy by creating new objects for the cloned object's internal reference pointers. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * {@inheritdoc} + */ + public function getStatusCode(): int + { + return $this->status; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withStatus($code, $reasonPhrase = '') + { + $code = $this->filterStatus($code); + $reasonPhrase = $this->filterReasonPhrase($reasonPhrase); + + $clone = clone $this; + $clone->status = $code; + $clone->reasonPhrase = $reasonPhrase; + + return $clone; + } + + /** + * {@inheritdoc} + */ + public function getReasonPhrase(): string + { + if ($this->reasonPhrase !== '') { + return $this->reasonPhrase; + } + + if (isset(static::$messages[$this->status])) { + return static::$messages[$this->status]; + } + + return ''; + } + + /** + * Filter HTTP status code. + * + * @param int $status HTTP status code. + * + * @return int + * + * @throws InvalidArgumentException If an invalid HTTP status code is provided. + */ + protected function filterStatus($status): int + { + if (!is_integer($status) || $status < StatusCodeInterface::STATUS_CONTINUE || $status > 599) { + throw new InvalidArgumentException('Invalid HTTP status code.'); + } + + return $status; + } + + /** + * Filter Reason Phrase + * + * @param mixed $reasonPhrase + * + * @return string + * + * @throws InvalidArgumentException + */ + protected function filterReasonPhrase($reasonPhrase = ''): string + { + if (is_object($reasonPhrase) && method_exists($reasonPhrase, '__toString')) { + $reasonPhrase = (string) $reasonPhrase; + } + + if (!is_string($reasonPhrase)) { + throw new InvalidArgumentException('Response reason phrase must be a string.'); + } + + if (strpos($reasonPhrase, "\r") !== false || strpos($reasonPhrase, "\n") !== false) { + throw new InvalidArgumentException( + 'Reason phrase contains one of the following prohibited characters: \r \n' + ); + } + + return $reasonPhrase; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Stream.php b/Sources/API/vendor/slim/psr7/src/Stream.php new file mode 100644 index 0000000..9de6685 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Stream.php @@ -0,0 +1,396 @@ +attach($stream); + + if ($cache && (!$cache->isSeekable() || !$cache->isWritable())) { + throw new RuntimeException('Cache stream must be seekable and writable'); + } + $this->cache = $cache; + } + + /** + * {@inheritdoc} + * @return array|mixed + */ + public function getMetadata($key = null) + { + if (!$this->stream) { + return null; + } + + $this->meta = stream_get_meta_data($this->stream); + + if (!$key) { + return $this->meta; + } + + return $this->meta[$key] ?? null; + } + + /** + * Attach new resource to this object. + * + * @internal This method is not part of the PSR-7 standard. + * + * @param resource $stream A PHP resource handle. + * + * @throws InvalidArgumentException If argument is not a valid PHP resource. + * + * @return void + */ + protected function attach($stream): void + { + if (!is_resource($stream)) { + throw new InvalidArgumentException(__METHOD__ . ' argument must be a valid PHP resource'); + } + + if ($this->stream) { + $this->detach(); + } + + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function detach() + { + $oldResource = $this->stream; + $this->stream = null; + $this->meta = null; + $this->readable = null; + $this->writable = null; + $this->seekable = null; + $this->size = null; + $this->isPipe = null; + + $this->cache = null; + $this->finished = false; + + return $oldResource; + } + + /** + * {@inheritdoc} + */ + public function __toString(): string + { + if (!$this->stream) { + return ''; + } + if ($this->cache && $this->finished) { + $this->cache->rewind(); + return $this->cache->getContents(); + } + if ($this->isSeekable()) { + $this->rewind(); + } + return $this->getContents(); + } + + /** + * {@inheritdoc} + */ + public function close(): void + { + if ($this->stream) { + if ($this->isPipe()) { + pclose($this->stream); + } else { + fclose($this->stream); + } + } + + $this->detach(); + } + + /** + * {@inheritdoc} + */ + public function getSize(): ?int + { + if ($this->stream && !$this->size) { + $stats = fstat($this->stream); + + if ($stats) { + $this->size = !$this->isPipe() ? $stats['size'] : null; + } + } + + return $this->size; + } + + /** + * {@inheritdoc} + */ + public function tell(): int + { + $position = false; + + if ($this->stream) { + $position = ftell($this->stream); + } + + if ($position === false || $this->isPipe()) { + throw new RuntimeException('Could not get the position of the pointer in stream.'); + } + + return $position; + } + + /** + * {@inheritdoc} + */ + public function eof(): bool + { + return !$this->stream || feof($this->stream); + } + + /** + * {@inheritdoc} + */ + public function isReadable(): bool + { + if ($this->readable !== null) { + return $this->readable; + } + + $this->readable = false; + + if ($this->stream) { + $mode = $this->getMetadata('mode'); + + if (is_string($mode) && (strstr($mode, 'r') !== false || strstr($mode, '+') !== false)) { + $this->readable = true; + } + } + + return $this->readable; + } + + /** + * {@inheritdoc} + */ + public function isWritable(): bool + { + if ($this->writable === null) { + $this->writable = false; + + if ($this->stream) { + $mode = $this->getMetadata('mode'); + + if (is_string($mode) && (strstr($mode, 'w') !== false || strstr($mode, '+') !== false)) { + $this->writable = true; + } + } + } + + return $this->writable; + } + + /** + * {@inheritdoc} + */ + public function isSeekable(): bool + { + if ($this->seekable === null) { + $this->seekable = false; + + if ($this->stream) { + $this->seekable = !$this->isPipe() && $this->getMetadata('seekable'); + } + } + + return $this->seekable; + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->isSeekable() || $this->stream && fseek($this->stream, $offset, $whence) === -1) { + throw new RuntimeException('Could not seek in stream.'); + } + } + + /** + * {@inheritdoc} + */ + public function rewind(): void + { + if (!$this->isSeekable() || $this->stream && rewind($this->stream) === false) { + throw new RuntimeException('Could not rewind stream.'); + } + } + + /** + * {@inheritdoc} + */ + public function read($length): string + { + $data = false; + + if ($this->isReadable() && $this->stream && $length >= 0) { + $data = fread($this->stream, $length); + } + + if (is_string($data)) { + if ($this->cache) { + $this->cache->write($data); + } + if ($this->eof()) { + $this->finished = true; + } + return $data; + } + + throw new RuntimeException('Could not read from stream.'); + } + + /** + * {@inheritdoc} + * @return int + */ + public function write($string) + { + $written = false; + + if ($this->isWritable() && $this->stream) { + $written = fwrite($this->stream, $string); + } + + if ($written !== false) { + $this->size = null; + return $written; + } + + throw new RuntimeException('Could not write to stream.'); + } + + /** + * {@inheritdoc} + */ + public function getContents(): string + { + if ($this->cache && $this->finished) { + $this->cache->rewind(); + return $this->cache->getContents(); + } + + $contents = false; + + if ($this->stream) { + $contents = stream_get_contents($this->stream); + } + + if (is_string($contents)) { + if ($this->cache) { + $this->cache->write($contents); + } + if ($this->eof()) { + $this->finished = true; + } + return $contents; + } + + throw new RuntimeException('Could not get contents of stream.'); + } + + /** + * Returns whether or not the stream is a pipe. + * + * @internal This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPipe(): bool + { + if ($this->isPipe === null) { + $this->isPipe = false; + + if ($this->stream) { + $stats = fstat($this->stream); + + if (is_array($stats)) { + $this->isPipe = ($stats['mode'] & self::FSTAT_MODE_S_IFIFO) !== 0; + } + } + } + + return $this->isPipe; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/UploadedFile.php b/Sources/API/vendor/slim/psr7/src/UploadedFile.php new file mode 100644 index 0000000..b0c4df5 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/UploadedFile.php @@ -0,0 +1,279 @@ +getMetadata('uri'); + if (!is_string($file)) { + throw new InvalidArgumentException('No URI associated with the stream.'); + } + $this->file = $file; + $this->stream = $fileNameOrStream; + } elseif (is_string($fileNameOrStream)) { + $this->file = $fileNameOrStream; + } else { + throw new InvalidArgumentException( + 'Please provide a string (full path to the uploaded file) or an instance of StreamInterface.' + ); + } + $this->name = $name; + $this->type = $type; + $this->size = $size; + $this->error = $error; + $this->sapi = $sapi; + } + + /** + * {@inheritdoc} + * @return StreamInterface + */ + public function getStream() + { + if ($this->moved) { + throw new RuntimeException(sprintf('Uploaded file %s has already been moved', $this->name)); + } + + if (!$this->stream) { + $this->stream = (new StreamFactory())->createStreamFromFile($this->file); + } + + return $this->stream; + } + + /** + * {@inheritdoc} + */ + public function moveTo($targetPath): void + { + if ($this->moved) { + throw new RuntimeException('Uploaded file already moved'); + } + + $targetIsStream = strpos($targetPath, '://') > 0; + if (!$targetIsStream && !is_writable(dirname($targetPath))) { + throw new InvalidArgumentException('Upload target path is not writable'); + } + + if ($targetIsStream) { + if (!copy($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); + } + + if (!unlink($this->file)) { + throw new RuntimeException(sprintf('Error removing uploaded file %s', $this->name)); + } + } elseif ($this->sapi) { + if (!is_uploaded_file($this->file)) { + throw new RuntimeException(sprintf('%s is not a valid uploaded file', $this->file)); + } + + if (!move_uploaded_file($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); + } + } else { + if (!rename($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); + } + } + + $this->moved = true; + } + + /** + * {@inheritdoc} + */ + public function getError(): int + { + return $this->error; + } + + /** + * {@inheritdoc} + */ + public function getClientFilename(): ?string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType(): ?string + { + return $this->type; + } + + /** + * {@inheritdoc} + */ + public function getSize(): ?int + { + return $this->size; + } + + /** + * Returns the client-provided full path to the file + * + * @internal This method is not part of the PSR-7 standard + * + * @return string + */ + public function getFilePath(): string + { + return $this->file; + } + + /** + * Create a normalized tree of UploadedFile instances from the Environment. + * + * @internal This method is not part of the PSR-7 standard. + * + * @param array $globals The global server variables. + * + * @return array A normalized tree of UploadedFile instances or null if none are provided. + */ + public static function createFromGlobals(array $globals): array + { + if (isset($globals['slim.files']) && is_array($globals['slim.files'])) { + return $globals['slim.files']; + } + + if (!empty($_FILES)) { + return self::parseUploadedFiles($_FILES); + } + + return []; + } + + /** + * Parse a non-normalized, i.e. $_FILES superglobal, tree of uploaded file data. + * + * @internal This method is not part of the PSR-7 standard. + * + * @param array $uploadedFiles The non-normalized tree of uploaded file data. + * + * @return array A normalized tree of UploadedFile instances. + */ + private static function parseUploadedFiles(array $uploadedFiles): array + { + $parsed = []; + foreach ($uploadedFiles as $field => $uploadedFile) { + if (!isset($uploadedFile['error'])) { + if (is_array($uploadedFile)) { + $parsed[$field] = self::parseUploadedFiles($uploadedFile); + } + continue; + } + + $parsed[$field] = []; + if (!is_array($uploadedFile['error'])) { + $parsed[$field] = new static( + $uploadedFile['tmp_name'], + $uploadedFile['name'] ?? null, + $uploadedFile['type'] ?? null, + $uploadedFile['size'] ?? null, + $uploadedFile['error'], + true + ); + } else { + $subArray = []; + foreach ($uploadedFile['error'] as $fileIdx => $error) { + // Normalize sub array and re-parse to move the input's key name up a level + $subArray[$fileIdx]['name'] = $uploadedFile['name'][$fileIdx]; + $subArray[$fileIdx]['type'] = $uploadedFile['type'][$fileIdx]; + $subArray[$fileIdx]['tmp_name'] = $uploadedFile['tmp_name'][$fileIdx]; + $subArray[$fileIdx]['error'] = $uploadedFile['error'][$fileIdx]; + $subArray[$fileIdx]['size'] = $uploadedFile['size'][$fileIdx]; + + $parsed[$field] = self::parseUploadedFiles($subArray); + } + } + } + + return $parsed; + } +} diff --git a/Sources/API/vendor/slim/psr7/src/Uri.php b/Sources/API/vendor/slim/psr7/src/Uri.php new file mode 100644 index 0000000..b43aa54 --- /dev/null +++ b/Sources/API/vendor/slim/psr7/src/Uri.php @@ -0,0 +1,494 @@ + null, + 'http' => 80, + 'https' => 443 + ]; + + /** + * Uri scheme (without "://" suffix) + */ + protected string $scheme = ''; + + protected string $user = ''; + + protected string $password = ''; + + protected string $host = ''; + + protected ?int $port; + + protected string $path = ''; + + /** + * Uri query string (without "?" prefix) + */ + protected string $query = ''; + + /** + * Uri fragment string (without "#" prefix) + */ + protected string $fragment = ''; + + /** + * @param string $scheme Uri scheme. + * @param string $host Uri host. + * @param int|null $port Uri port number. + * @param string $path Uri path. + * @param string $query Uri query string. + * @param string $fragment Uri fragment. + * @param string $user Uri user. + * @param string $password Uri password. + */ + public function __construct( + string $scheme, + string $host, + ?int $port = null, + string $path = '/', + string $query = '', + string $fragment = '', + string $user = '', + string $password = '' + ) { + $this->scheme = $this->filterScheme($scheme); + $this->host = $this->filterHost($host); + $this->port = $this->filterPort($port); + $this->path = $this->filterPath($path); + $this->query = $this->filterQuery($query); + $this->fragment = $this->filterFragment($fragment); + $this->user = $this->filterUserInfo($user); + $this->password = $this->filterUserInfo($password); + } + + /** + * {@inheritdoc} + */ + public function getScheme(): string + { + return $this->scheme; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + $clone = clone $this; + $clone->scheme = $scheme; + + return $clone; + } + + /** + * Filter Uri scheme. + * + * @param mixed $scheme Raw Uri scheme. + * + * @return string + * + * @throws InvalidArgumentException If the Uri scheme is not a string. + * @throws InvalidArgumentException If Uri scheme is not exists in SUPPORTED_SCHEMES + */ + protected function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new InvalidArgumentException('Uri scheme must be a string.'); + } + + $scheme = str_replace('://', '', strtolower($scheme)); + if (!key_exists($scheme, static::SUPPORTED_SCHEMES)) { + throw new InvalidArgumentException( + 'Uri scheme must be one of: "' . implode('", "', array_keys(static::SUPPORTED_SCHEMES)) . '"' + ); + } + + return $scheme; + } + + /** + * {@inheritdoc} + */ + public function getAuthority(): string + { + $userInfo = $this->getUserInfo(); + $host = $this->getHost(); + $port = $this->getPort(); + + return ($userInfo !== '' ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : ''); + } + + /** + * {@inheritdoc} + */ + public function getUserInfo(): string + { + $info = $this->user; + + if ($this->password !== '') { + $info .= ':' . $this->password; + } + + return $info; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withUserInfo($user, $password = null) + { + $clone = clone $this; + $clone->user = $this->filterUserInfo($user); + + if ($clone->user !== '') { + $clone->password = $this->filterUserInfo($password); + } else { + $clone->password = ''; + } + + return $clone; + } + + /** + * Filters the user info string. + * + * Returns the percent-encoded query string. + * + * @param string|null $info The raw uri query string. + * + * @return string + */ + protected function filterUserInfo(?string $info = null): string + { + if (!is_string($info)) { + return ''; + } + + $match = preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u', + function ($match) { + return rawurlencode($match[0]); + }, + $info + ); + + return is_string($match) ? $match : ''; + } + + /** + * {@inheritdoc} + */ + public function getHost(): string + { + return $this->host; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withHost($host) + { + $clone = clone $this; + $clone->host = $this->filterHost($host); + + return $clone; + } + + /** + * Filter Uri host. + * + * If the supplied host is an IPv6 address, then it is converted to a reference + * as per RFC 2373. + * + * @param mixed $host The host to filter. + * + * @return string + * + * @throws InvalidArgumentException for invalid host names. + */ + protected function filterHost($host): string + { + if (is_object($host) && method_exists($host, '__toString')) { + $host = (string) $host; + } + + if (!is_string($host)) { + throw new InvalidArgumentException('Uri host must be a string'); + } + + if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $host = '[' . $host . ']'; + } + + return strtolower($host); + } + + /** + * {@inheritdoc} + */ + public function getPort(): ?int + { + return $this->port && !$this->hasStandardPort() ? $this->port : null; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withPort($port) + { + $port = $this->filterPort($port); + $clone = clone $this; + $clone->port = $port; + + return $clone; + } + + /** + * Does this Uri use a standard port? + * + * @return bool + */ + protected function hasStandardPort(): bool + { + return static::SUPPORTED_SCHEMES[$this->scheme] === $this->port; + } + + /** + * Filter Uri port. + * + * @param int|null $port The Uri port number. + * + * @return int|null + * + * @throws InvalidArgumentException If the port is invalid. + */ + protected function filterPort($port): ?int + { + if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) { + return $port; + } + + throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)'); + } + + /** + * {@inheritdoc} + */ + public function getPath(): string + { + return $this->path; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withPath($path) + { + if (!is_string($path)) { + throw new InvalidArgumentException('Uri path must be a string'); + } + + $clone = clone $this; + $clone->path = $this->filterPath($path); + + return $clone; + } + + /** + * Filter Uri path. + * + * This method percent-encodes all reserved characters in the provided path string. + * This method will NOT double-encode characters that are already percent-encoded. + * + * @param string $path The raw uri path. + * + * @return string The RFC 3986 percent-encoded uri path. + * + * @link http://www.faqs.org/rfcs/rfc3986.html + */ + protected function filterPath($path): string + { + $match = preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', + function ($match) { + return rawurlencode($match[0]); + }, + $path + ); + + return is_string($match) ? $match : ''; + } + + /** + * {@inheritdoc} + */ + public function getQuery(): string + { + return $this->query; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withQuery($query) + { + $query = ltrim($this->filterQuery($query), '?'); + $clone = clone $this; + $clone->query = $query; + + return $clone; + } + + /** + * Filters the query string of a URI. + * + * Returns the percent-encoded query string. + * + * @param mixed $query The raw uri query string. + * + * @return string + */ + protected function filterQuery($query): string + { + if (is_object($query) && method_exists($query, '__toString')) { + $query = (string) $query; + } + + if (!is_string($query)) { + throw new InvalidArgumentException('Uri query must be a string.'); + } + + $match = preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', + function ($match) { + return rawurlencode($match[0]); + }, + $query + ); + + return is_string($match) ? $match : ''; + } + + /** + * {@inheritdoc} + */ + public function getFragment(): string + { + return $this->fragment; + } + + /** + * {@inheritdoc} + * @return static + */ + public function withFragment($fragment) + { + $fragment = $this->filterFragment($fragment); + $clone = clone $this; + $clone->fragment = $fragment; + + return $clone; + } + + /** + * Filters fragment of a URI. + * + * Returns the percent-encoded fragment. + * + * @param mixed $fragment The raw uri query string. + * + * @return string + */ + protected function filterFragment($fragment): string + { + if (is_object($fragment) && method_exists($fragment, '__toString')) { + $fragment = (string) $fragment; + } + + if (!is_string($fragment)) { + throw new InvalidArgumentException('Uri fragment must be a string.'); + } + + $fragment = ltrim($fragment, '#'); + + $match = preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', + function ($match) { + return rawurlencode($match[0]); + }, + $fragment + ); + + return is_string($match) ? $match : ''; + } + + /** + * {@inheritdoc} + */ + public function __toString(): string + { + $scheme = $this->getScheme(); + $authority = $this->getAuthority(); + $path = $this->getPath(); + $query = $this->getQuery(); + $fragment = $this->getFragment(); + + if ($path !== '') { + if ($path[0] !== '/') { + if ($authority !== '') { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/". + $path = '/' . $path; + } + } elseif (isset($path[1]) && $path[1] === '/') { + if ($authority === '') { + // If the path is starting with more than one "/" and no authority is present, + // the starting slashes MUST be reduced to one. + $path = '/' . ltrim($path, '/'); + } + } + } + + return ($scheme !== '' ? $scheme . ':' : '') + . ($authority !== '' ? '//' . $authority : '') + . $path + . ($query !== '' ? '?' . $query : '') + . ($fragment !== '' ? '#' . $fragment : ''); + } +} diff --git a/Sources/API/vendor/slim/slim/CHANGELOG.md b/Sources/API/vendor/slim/slim/CHANGELOG.md new file mode 100644 index 0000000..4953c3e --- /dev/null +++ b/Sources/API/vendor/slim/slim/CHANGELOG.md @@ -0,0 +1,208 @@ +# Changelog + +# 4.10.0 - 2022-03-14 +- [3120: Add a new PSR-17 factory to Psr17FactoryProvider](https://github.com/slimphp/Slim/pull/3120) thanks to @solventt +- [3123: Replace deprecated setMethods() in tests](https://github.com/slimphp/Slim/pull/3123) thanks to @solventt +- [3126: Update guzzlehttp/psr7 requirement from ^2.0 to ^2.1](https://github.com/slimphp/Slim/pull/3126) thanks to @dependabot[bot] +- [3127: PHPStan v1.0](https://github.com/slimphp/Slim/pull/3127) thanks to @t0mmy742 +- [3128: Update phpstan/phpstan requirement from ^1.0 to ^1.2](https://github.com/slimphp/Slim/pull/3128) thanks to @dependabot[bot] +- [3129: Deprecate PHP 7.3](https://github.com/slimphp/Slim/pull/3129) thanks to @l0gicgate +- [3130: Removed double defined PHP 7.4](https://github.com/slimphp/Slim/pull/3130) thanks to @flangofas +- [3132: Add new `RequestResponseNamedArgs` route strategy](https://github.com/slimphp/Slim/pull/3132) thanks to @adoy +- [3133: Improve typehinting for `RouteParserInterface`](https://github.com/slimphp/Slim/pull/3133) thanks to @jerowork +- [3135: Update phpstan/phpstan requirement from ^1.2 to ^1.3](https://github.com/slimphp/Slim/pull/3135) thanks to @dependabot[bot] +- [3137: Update phpspec/prophecy requirement from ^1.14 to ^1.15](https://github.com/slimphp/Slim/pull/3137) thanks to @dependabot[bot] +- [3138: Update license year](https://github.com/slimphp/Slim/pull/3138) thanks to @Awilum +- [3139: Fixed #1730 (reintroduced in 4.x)](https://github.com/slimphp/Slim/pull/3139) thanks to @adoy +- [3145: Update phpstan/phpstan requirement from ^1.3 to ^1.4](https://github.com/slimphp/Slim/pull/3145) thanks to @dependabot[bot] +- [3146: Inherit HttpException from RuntimeException](https://github.com/slimphp/Slim/pull/3146) thanks to @nbayramberdiyev +- [3148: Upgrade to HTML5](https://github.com/slimphp/Slim/pull/3148) thanks to @nbayramberdiyev +- [3172: Update nyholm/psr7 requirement from ^1.4 to ^1.5](https://github.com/slimphp/Slim/pull/3172) thanks to @dependabot[bot] + +# 4.9.0 - 2021-10-05 +- [3058: Implement exception class for Gone Http error](https://github.com/slimphp/Slim/pull/3058) thanks to @TheKernelPanic +- [3086: Update slim/psr7 requirement from ^1.3 to ^1.4](https://github.com/slimphp/Slim/pull/3086) thanks to @dependabot[bot] +- [3087: Update nyholm/psr7-server requirement from ^1.0.1 to ^1.0.2](https://github.com/slimphp/Slim/pull/3087) thanks to @dependabot[bot] +- [3093: Update phpstan/phpstan requirement from ^0.12.85 to ^0.12.90](https://github.com/slimphp/Slim/pull/3093) thanks to @dependabot[bot] +- [3099: Allow updated psr log](https://github.com/slimphp/Slim/pull/3099) thanks to @t0mmy742 +- [3104: Drop php7.2](https://github.com/slimphp/Slim/pull/3104) thanks to @t0mmy742 +- [3106: Use PSR-17 factory from Guzzle/psr7 2.0](https://github.com/slimphp/Slim/pull/3106) thanks to @t0mmy742 +- [3108: Update README file](https://github.com/slimphp/Slim/pull/3108) thanks to @t0mmy742 +- [3112: Update laminas/laminas-diactoros requirement from ^2.6 to ^2.8](https://github.com/slimphp/Slim/pull/3112) thanks to @dependabot[bot] +- [3114: Update slim/psr7 requirement from ^1.4 to ^1.5](https://github.com/slimphp/Slim/pull/3114) thanks to @dependabot[bot] +- [3115: Update phpstan/phpstan requirement from ^0.12.96 to ^0.12.99](https://github.com/slimphp/Slim/pull/3115) thanks to @dependabot[bot] +- [3116: Remove Zend Diactoros references](https://github.com/slimphp/Slim/pull/3116) thanks to @l0gicgate + +# 4.8.0 - 2021-05-19 +- [3034: Fix phpunit dependency version](https://github.com/slimphp/Slim/pull/3034) thanks to @l0gicgate +- [3037: Replace Travis by GitHub Actions](https://github.com/slimphp/Slim/pull/3037) thanks to @t0mmy742 +- [3043: Cover App creation from AppFactory with empty Container](https://github.com/slimphp/Slim/pull/3043) thanks to @t0mmy742 +- [3045: Update phpstan/phpstan requirement from ^0.12.58 to ^0.12.64](https://github.com/slimphp/Slim/pull/3045) thanks to @dependabot-preview[bot] +- [3047: documentation: min php 7.2 required](https://github.com/slimphp/Slim/pull/3047) thanks to @Rotzbua +- [3054: Update phpstan/phpstan requirement from ^0.12.64 to ^0.12.70](https://github.com/slimphp/Slim/pull/3054) thanks to @dependabot-preview[bot] +- [3056: Fix docblock in ErrorMiddleware](https://github.com/slimphp/Slim/pull/3056) thanks to @piotr-cz +- [3060: Update phpstan/phpstan requirement from ^0.12.70 to ^0.12.80](https://github.com/slimphp/Slim/pull/3060) thanks to @dependabot-preview[bot] +- [3061: Update nyholm/psr7 requirement from ^1.3 to ^1.4](https://github.com/slimphp/Slim/pull/3061) thanks to @dependabot-preview[bot] +- [3063: Allow ^1.0 || ^2.0 in psr/container](https://github.com/slimphp/Slim/pull/3063) thanks to @Ayesh +- [3069: Classname/Method Callable Arrays](https://github.com/slimphp/Slim/pull/3069) thanks to @ddrv +- [3078: Update squizlabs/php_codesniffer requirement from ^3.5 to ^3.6](https://github.com/slimphp/Slim/pull/3078) thanks to @dependabot[bot] +- [3079: Update phpspec/prophecy requirement from ^1.12 to ^1.13](https://github.com/slimphp/Slim/pull/3079) thanks to @dependabot[bot] +- [3080: Update guzzlehttp/psr7 requirement from ^1.7 to ^1.8](https://github.com/slimphp/Slim/pull/3080) thanks to @dependabot[bot] +- [3082: Update phpstan/phpstan requirement from ^0.12.80 to ^0.12.85](https://github.com/slimphp/Slim/pull/3082) thanks to @dependabot[bot] + +# 4.7.0 - 2020-11-30 + +### Fixed +- [3027: Fix: FastRoute dispatcher and data generator should match](https://github.com/slimphp/Slim/pull/3027) thanks to @edudobay + +### Added +- [3015: PHP 8 support](https://github.com/slimphp/Slim/pull/3015) thanks to @edudobay + +### Optimizations +- [3024: Randomize tests](https://github.com/slimphp/Slim/pull/3024) thanks to @pawel-slowik + +## 4.6.0 - 2020-11-15 + +### Fixed +- [2942: Fix PHPdoc for error handlers in ErrorMiddleware ](https://github.com/slimphp/Slim/pull/2942) thanks to @TiMESPLiNTER +- [2944: Remove unused function in ErrorHandler](https://github.com/slimphp/Slim/pull/2944) thanks to @l0gicgate +- [2960: Fix phpstan 0.12 errors](https://github.com/slimphp/Slim/pull/2960) thanks to @adriansuter +- [2982: Removing cloning statements in tests](https://github.com/slimphp/Slim/pull/2982) thanks to @l0gicgate +- [3017: Fix request creator factory test](https://github.com/slimphp/Slim/pull/3017) thanks to @pawel-slowik +- [3022: Ensure RouteParser Always Present After Routing](https://github.com/slimphp/Slim/pull/3022) thanks to @l0gicgate + +### Added +- [2949: Add the support in composer.json](https://github.com/slimphp/Slim/pull/2949) thanks to @ddrv +- [2958: Strict empty string content type checking in BodyParsingMiddleware::getMediaType](https://github.com/slimphp/Slim/pull/2958) thanks to @Ayesh +- [2997: Add hints to methods](https://github.com/slimphp/Slim/pull/2997) thanks to @evgsavosin - [3000: Fix route controller test](https://github.com/slimphp/Slim/pull/3000) thanks to @pawel-slowik +- [3001: Add missing `$strategy` parameter in a Route test](https://github.com/slimphp/Slim/pull/3001) thanks to @pawel-slowik + +### Optimizations +- [2951: Minor optimizations in if() blocks](https://github.com/slimphp/Slim/pull/2951) thanks to @Ayesh +- [2959: Micro optimization: Declare closures in BodyParsingMiddleware as static](https://github.com/slimphp/Slim/pull/2959) thanks to @Ayesh +- [2978: Split the routing results to its own function.](https://github.com/slimphp/Slim/pull/2978) thanks to @dlundgren + +### Dependencies Updated +- [2953: Update nyholm/psr7-server requirement from ^0.4.1](https://github.com/slimphp/Slim/pull/2953) thanks to @dependabot-preview[bot] +- [2954: Update laminas/laminas-diactoros requirement from ^2.1 to ^2.3](https://github.com/slimphp/Slim/pull/2954) thanks to @dependabot-preview[bot] +- [2955: Update guzzlehttp/psr7 requirement from ^1.5 to ^1.6](https://github.com/slimphp/Slim/pull/2955) thanks to @dependabot-preview[bot] +- [2956: Update slim/psr7 requirement from ^1.0 to ^1.1](https://github.com/slimphp/Slim/pull/2956) thanks to @dependabot-preview[bot] +- [2957: Update nyholm/psr7 requirement from ^1.1 to ^1.2](https://github.com/slimphp/Slim/pull/2957) thanks to @dependabot-preview[bot] +- [2963: Update phpstan/phpstan requirement from ^0.12.23 to ^0.12.25](https://github.com/slimphp/Slim/pull/2963) thanks to @dependabot-preview[bot] +- [2965: Update adriansuter/php-autoload-override requirement from ^1.0 to ^1.1](https://github.com/slimphp/Slim/pull/2965) thanks to @dependabot-preview[bot] +- [2967: Update nyholm/psr7 requirement from ^1.2 to ^1.3](https://github.com/slimphp/Slim/pull/2967) thanks to @dependabot-preview[bot] +- [2969: Update nyholm/psr7-server requirement from ^0.4.1 to ^1.0.0](https://github.com/slimphp/Slim/pull/2969) thanks to @dependabot-preview[bot] +- [2970: Update phpstan/phpstan requirement from ^0.12.25 to ^0.12.26](https://github.com/slimphp/Slim/pull/2970) thanks to @dependabot-preview[bot] +- [2971: Update phpstan/phpstan requirement from ^0.12.26 to ^0.12.27](https://github.com/slimphp/Slim/pull/2971) thanks to @dependabot-preview[bot] +- [2972: Update phpstan/phpstan requirement from ^0.12.27 to ^0.12.28](https://github.com/slimphp/Slim/pull/2972) thanks to @dependabot-preview[bot] +- [2973: Update phpstan/phpstan requirement from ^0.12.28 to ^0.12.29](https://github.com/slimphp/Slim/pull/2973) thanks to @dependabot-preview[bot] +- [2975: Update phpstan/phpstan requirement from ^0.12.29 to ^0.12.30](https://github.com/slimphp/Slim/pull/2975) thanks to @dependabot-preview[bot] +- [2976: Update phpstan/phpstan requirement from ^0.12.30 to ^0.12.31](https://github.com/slimphp/Slim/pull/2976) thanks to @dependabot-preview[bot] +- [2980: Update phpstan/phpstan requirement from ^0.12.31 to ^0.12.32](https://github.com/slimphp/Slim/pull/2980) thanks to @dependabot-preview[bot] +- [2981: Update phpspec/prophecy requirement from ^1.10 to ^1.11](https://github.com/slimphp/Slim/pull/2981) thanks to @dependabot-preview[bot] +- [2986: Update phpstan/phpstan requirement from ^0.12.32 to ^0.12.33](https://github.com/slimphp/Slim/pull/2986) thanks to @dependabot-preview[bot] +- [2990: Update phpstan/phpstan requirement from ^0.12.33 to ^0.12.34](https://github.com/slimphp/Slim/pull/2990) thanks to @dependabot-preview[bot] +- [2991: Update phpstan/phpstan requirement from ^0.12.34 to ^0.12.35](https://github.com/slimphp/Slim/pull/2991) thanks to @dependabot-preview[bot] +- [2993: Update phpstan/phpstan requirement from ^0.12.35 to ^0.12.36](https://github.com/slimphp/Slim/pull/2993) thanks to @dependabot-preview[bot] +- [2995: Update phpstan/phpstan requirement from ^0.12.36 to ^0.12.37](https://github.com/slimphp/Slim/pull/2995) thanks to @dependabot-preview[bot] +- [3010: Update guzzlehttp/psr7 requirement from ^1.6 to ^1.7](https://github.com/slimphp/Slim/pull/3010) thanks to @dependabot-preview[bot] +- [3011: Update phpspec/prophecy requirement from ^1.11 to ^1.12](https://github.com/slimphp/Slim/pull/3011) thanks to @dependabot-preview[bot] +- [3012: Update slim/http requirement from ^1.0 to ^1.1](https://github.com/slimphp/Slim/pull/3012) thanks to @dependabot-preview[bot] +- [3013: Update slim/psr7 requirement from ^1.1 to ^1.2](https://github.com/slimphp/Slim/pull/3013) thanks to @dependabot-preview[bot] +- [3014: Update laminas/laminas-diactoros requirement from ^2.3 to ^2.4](https://github.com/slimphp/Slim/pull/3014) thanks to @dependabot-preview[bot] +- [3018: Update phpstan/phpstan requirement from ^0.12.37 to ^0.12.54](https://github.com/slimphp/Slim/pull/3018) thanks to @dependabot-preview[bot] + +## 4.5.0 - 2020-04-14 + +### Added +- [2928](https://github.com/slimphp/Slim/pull/2928) Test against PHP 7.4 +- [2937](https://github.com/slimphp/Slim/pull/2937) Add support for PSR-3 + +### Fixed +- [2916](https://github.com/slimphp/Slim/pull/2916) Rename phpcs.xml to phpcs.xml.dist +- [2917](https://github.com/slimphp/Slim/pull/2917) Update .editorconfig +- [2925](https://github.com/slimphp/Slim/pull/2925) ResponseEmitter: Don't remove Content-Type and Content-Length when body is empt +- [2932](https://github.com/slimphp/Slim/pull/2932) Update the Tidelift enterprise language +- [2938](https://github.com/slimphp/Slim/pull/2938) Modify usage of deprecated expectExceptionMessageRegExp() method + +## 4.4.0 - 2020-01-04 + +### Added +- [2862](https://github.com/slimphp/Slim/pull/2862) Optionally handle subclasses of exceptions in custom error handler +- [2869](https://github.com/slimphp/Slim/pull/2869) php-di/php-di added in composer suggestion +- [2874](https://github.com/slimphp/Slim/pull/2874) Add `null` to param type-hints +- [2889](https://github.com/slimphp/Slim/pull/2889) Make `RouteContext` attributes customizable and change default to use private names +- [2907](https://github.com/slimphp/Slim/pull/2907) Migrate to PSR-12 convention +- [2910](https://github.com/slimphp/Slim/pull/2910) Migrate Zend to Laminas +- [2912](https://github.com/slimphp/Slim/pull/2912) Add Laminas PSR17 Factory +- [2913](https://github.com/slimphp/Slim/pull/2913) Update php-autoload-override version +- [2914](https://github.com/slimphp/Slim/pull/2914) Added ability to add handled exceptions as an array + +### Fixed +- [2864](https://github.com/slimphp/Slim/pull/2864) Optimize error message in error handling if displayErrorDetails was not set +- [2876](https://github.com/slimphp/Slim/pull/2876) Update links from http to https +- [2877](https://github.com/slimphp/Slim/pull/2877) Fix docblock for `Slim\Routing\RouteCollector::cacheFile` +- [2878](https://github.com/slimphp/Slim/pull/2878) check body is writable only on ouput buffering append +- [2896](https://github.com/slimphp/Slim/pull/2896) Render errors uniformly +- [2902](https://github.com/slimphp/Slim/pull/2902) Fix prophecies +- [2908](https://github.com/slimphp/Slim/pull/2908) Use autoload-dev for `Slim\Tests` namespace + +### Removed +- [2871](https://github.com/slimphp/Slim/pull/2871) Remove explicit type-hint +- [2872](https://github.com/slimphp/Slim/pull/2872) Remove type-hint + +## 4.3.0 - 2019-10-05 + +### Added +- [2819](https://github.com/slimphp/Slim/pull/2819) Added description to addRoutingMiddleware() +- [2820](https://github.com/slimphp/Slim/pull/2820) Update link to homepage in composer.json +- [2828](https://github.com/slimphp/Slim/pull/2828) Allow URIs with leading multi-slashes +- [2832](https://github.com/slimphp/Slim/pull/2832) Refactor `FastRouteDispatcher` +- [2835](https://github.com/slimphp/Slim/pull/2835) Rename `pathFor` to `urlFor` in docblock +- [2846](https://github.com/slimphp/Slim/pull/2846) Correcting the branch name as per issue-2843 +- [2849](https://github.com/slimphp/Slim/pull/2849) Create class alias for FastRoute\RouteCollector +- [2855](https://github.com/slimphp/Slim/pull/2855) Add list of allowed methods to HttpMethodNotAllowedException +- [2860](https://github.com/slimphp/Slim/pull/2860) Add base path to `$request` and use `RouteContext` to read + +### Fixed +- [2839](https://github.com/slimphp/Slim/pull/2839) Fix description for handler signature in phpdocs +- [2844](https://github.com/slimphp/Slim/pull/2844) Handle base path by routeCollector instead of RouteCollectorProxy +- [2845](https://github.com/slimphp/Slim/pull/2845) Fix composer scripts +- [2851](https://github.com/slimphp/Slim/pull/2851) Fix example of 'Hello World' app +- [2854](https://github.com/slimphp/Slim/pull/2854) Fix undefined property in tests + +### Removed +- [2853](https://github.com/slimphp/Slim/pull/2853) Remove unused classes + +## 4.2.0 - 2019-08-20 + +### Added +- [2787](https://github.com/slimphp/Slim/pull/2787) Add an advanced callable resolver +- [2791](https://github.com/slimphp/Slim/pull/2791) Add `inferPrivatePropertyTypeFromConstructor` to phpstan +- [2793](https://github.com/slimphp/Slim/pull/2793) Add ability to configure application via a container in `AppFactory` +- [2798](https://github.com/slimphp/Slim/pull/2798) Add PSR-7 Agnostic Body Parsing Middleware +- [2801](https://github.com/slimphp/Slim/pull/2801) Add `setLogErrorRenderer()` method to `ErrorHandler` +- [2807](https://github.com/slimphp/Slim/pull/2807) Add check for Slim callable notation if no resolver given +- [2803](https://github.com/slimphp/Slim/pull/2803) Add ability to emit non seekable streams in `ResponseEmitter` +- [2817](https://github.com/slimphp/Slim/pull/2817) Add the ability to pass in a custom `MiddlewareDispatcherInterface` to the `App` + +### Fixed +- [2789](https://github.com/slimphp/Slim/pull/2789) Fix Cookie header detection in `ResponseEmitter` +- [2796](https://github.com/slimphp/Slim/pull/2796) Fix http message format +- [2800](https://github.com/slimphp/Slim/pull/2800) Fix null comparisons more clear in `ErrorHandler` +- [2802](https://github.com/slimphp/Slim/pull/2802) Fix incorrect search of a header in stack +- [2806](https://github.com/slimphp/Slim/pull/2806) Simplify `Route::prepare()` method argument preparation +- [2809](https://github.com/slimphp/Slim/pull/2809) Eliminate a duplicate code via HOF in `MiddlewareDispatcher` +- [2816](https://github.com/slimphp/Slim/pull/2816) Fix RouteCollectorProxy::redirect() bug + +### Removed +- [2811](https://github.com/slimphp/Slim/pull/2811) Remove `DeferredCallable` + +## 4.1.0 - 2019-08-06 + +### Added +- [#2779](https://github.com/slimphp/Slim/pull/2774) Add support for Slim callables `Class:method` resolution & Container Closure auto-binding in `MiddlewareDispatcher` +- [#2774](https://github.com/slimphp/Slim/pull/2774) Add possibility for custom `RequestHandler` invocation strategies + +### Fixed +- [#2776](https://github.com/slimphp/Slim/pull/2774) Fix group middleware on multiple nested groups diff --git a/Sources/API/vendor/slim/slim/LICENSE.md b/Sources/API/vendor/slim/slim/LICENSE.md new file mode 100644 index 0000000..d6fd559 --- /dev/null +++ b/Sources/API/vendor/slim/slim/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2011-2022 Josh Lockhart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/slim/slim/MAINTAINERS.md b/Sources/API/vendor/slim/slim/MAINTAINERS.md new file mode 100644 index 0000000..2b8ad04 --- /dev/null +++ b/Sources/API/vendor/slim/slim/MAINTAINERS.md @@ -0,0 +1,17 @@ +# Maintainers + +There aren't many rules for maintainers of Slim to remember; what we have is listed here. + +## We don't merge our own PRs + +Our code is better if more than one set of eyes looks at it. Therefore we do not merge our own pull requests unless there is an exceptional circumstance. This helps to spot errors in the patch and also enables us to share information about the project around the maintainer team. + +## PRs tagged `WIP` are not ready to be merged + +Sometimes it's helpful to collaborate on a patch before it's ready to be merged. We use the text `WIP` (for _Work in Progress_) in the title to mark these PRs. + +If a PR has `WIP` in its title, then it is not to be merged. The person who raised the PR will remove the `WIP` text when they are ready for a full review and merge. + +## Assign a merged PR to a milestone + +By ensuring that all merged PRs are assigned to a milestone, we can easily find which PRs were in which release. diff --git a/Sources/API/vendor/slim/slim/SECURITY.md b/Sources/API/vendor/slim/slim/SECURITY.md new file mode 100644 index 0000000..a5b6df0 --- /dev/null +++ b/Sources/API/vendor/slim/slim/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +### Supported Versions + + +| Version | Supported | +| ------- | ------------------ | +| 3.x.x | :white_check_mark: | +| 4.x.x | :white_check_mark: | + + +### Reporting a Vulnerability + +To report a vulnerability please send an email to security@slimframework.com diff --git a/Sources/API/vendor/slim/slim/Slim/App.php b/Sources/API/vendor/slim/slim/Slim/App.php new file mode 100644 index 0000000..1374d6f --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/App.php @@ -0,0 +1,216 @@ +routeResolver = $routeResolver ?? new RouteResolver($this->routeCollector); + $routeRunner = new RouteRunner($this->routeResolver, $this->routeCollector->getRouteParser(), $this); + + if (!$middlewareDispatcher) { + $middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $this->callableResolver, $container); + } else { + $middlewareDispatcher->seedMiddlewareStack($routeRunner); + } + + $this->middlewareDispatcher = $middlewareDispatcher; + } + + /** + * @return RouteResolverInterface + */ + public function getRouteResolver(): RouteResolverInterface + { + return $this->routeResolver; + } + + /** + * @return MiddlewareDispatcherInterface + */ + public function getMiddlewareDispatcher(): MiddlewareDispatcherInterface + { + return $this->middlewareDispatcher; + } + + /** + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): self + { + $this->middlewareDispatcher->add($middleware); + return $this; + } + + /** + * @param MiddlewareInterface $middleware + */ + public function addMiddleware(MiddlewareInterface $middleware): self + { + $this->middlewareDispatcher->addMiddleware($middleware); + return $this; + } + + /** + * Add the Slim built-in routing middleware to the app middleware stack + * + * This method can be used to control middleware order and is not required for default routing operation. + * + * @return RoutingMiddleware + */ + public function addRoutingMiddleware(): RoutingMiddleware + { + $routingMiddleware = new RoutingMiddleware( + $this->getRouteResolver(), + $this->getRouteCollector()->getRouteParser() + ); + $this->add($routingMiddleware); + return $routingMiddleware; + } + + /** + * Add the Slim built-in error middleware to the app middleware stack + * + * @param bool $displayErrorDetails + * @param bool $logErrors + * @param bool $logErrorDetails + * @param LoggerInterface|null $logger + * + * @return ErrorMiddleware + */ + public function addErrorMiddleware( + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails, + ?LoggerInterface $logger = null + ): ErrorMiddleware { + $errorMiddleware = new ErrorMiddleware( + $this->getCallableResolver(), + $this->getResponseFactory(), + $displayErrorDetails, + $logErrors, + $logErrorDetails, + $logger + ); + $this->add($errorMiddleware); + return $errorMiddleware; + } + + /** + * Add the Slim body parsing middleware to the app middleware stack + * + * @param callable[] $bodyParsers + * + * @return BodyParsingMiddleware + */ + public function addBodyParsingMiddleware(array $bodyParsers = []): BodyParsingMiddleware + { + $bodyParsingMiddleware = new BodyParsingMiddleware($bodyParsers); + $this->add($bodyParsingMiddleware); + return $bodyParsingMiddleware; + } + + /** + * Run application + * + * This method traverses the application middleware stack and then sends the + * resultant Response object to the HTTP client. + * + * @param ServerRequestInterface|null $request + * @return void + */ + public function run(?ServerRequestInterface $request = null): void + { + if (!$request) { + $serverRequestCreator = ServerRequestCreatorFactory::create(); + $request = $serverRequestCreator->createServerRequestFromGlobals(); + } + + $response = $this->handle($request); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + } + + /** + * Handle a request + * + * This method traverses the application middleware stack and then returns the + * resultant Response object. + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $response = $this->middlewareDispatcher->handle($request); + + /** + * This is to be in compliance with RFC 2616, Section 9. + * If the incoming request method is HEAD, we need to ensure that the response body + * is empty as the request may fall back on a GET route handler due to FastRoute's + * routing logic which could potentially append content to the response body + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 + */ + $method = strtoupper($request->getMethod()); + if ($method === 'HEAD') { + $emptyBody = $this->responseFactory->createResponse()->getBody(); + return $response->withBody($emptyBody); + } + + return $response; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/CallableResolver.php b/Sources/API/vendor/slim/slim/Slim/CallableResolver.php new file mode 100644 index 0000000..66f225d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/CallableResolver.php @@ -0,0 +1,193 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function resolve($toResolve): callable + { + $toResolve = $this->prepareToResolve($toResolve); + if (is_callable($toResolve)) { + return $this->bindToContainer($toResolve); + } + $resolved = $toResolve; + if (is_string($toResolve)) { + $resolved = $this->resolveSlimNotation($toResolve); + $resolved[1] ??= '__invoke'; + } + $callable = $this->assertCallable($resolved, $toResolve); + return $this->bindToContainer($callable); + } + + /** + * {@inheritdoc} + */ + public function resolveRoute($toResolve): callable + { + return $this->resolveByPredicate($toResolve, [$this, 'isRoute'], 'handle'); + } + + /** + * {@inheritdoc} + */ + public function resolveMiddleware($toResolve): callable + { + return $this->resolveByPredicate($toResolve, [$this, 'isMiddleware'], 'process'); + } + + /** + * @param string|callable $toResolve + * + * @throws RuntimeException + */ + private function resolveByPredicate($toResolve, callable $predicate, string $defaultMethod): callable + { + $toResolve = $this->prepareToResolve($toResolve); + if (is_callable($toResolve)) { + return $this->bindToContainer($toResolve); + } + $resolved = $toResolve; + if ($predicate($toResolve)) { + $resolved = [$toResolve, $defaultMethod]; + } + if (is_string($toResolve)) { + [$instance, $method] = $this->resolveSlimNotation($toResolve); + if ($method === null && $predicate($instance)) { + $method = $defaultMethod; + } + $resolved = [$instance, $method ?? '__invoke']; + } + $callable = $this->assertCallable($resolved, $toResolve); + return $this->bindToContainer($callable); + } + + /** + * @param mixed $toResolve + */ + private function isRoute($toResolve): bool + { + return $toResolve instanceof RequestHandlerInterface; + } + + /** + * @param mixed $toResolve + */ + private function isMiddleware($toResolve): bool + { + return $toResolve instanceof MiddlewareInterface; + } + + /** + * @throws RuntimeException + * + * @return array{object, string|null} [Instance, Method Name] + */ + private function resolveSlimNotation(string $toResolve): array + { + preg_match(CallableResolver::$callablePattern, $toResolve, $matches); + [$class, $method] = $matches ? [$matches[1], $matches[2]] : [$toResolve, null]; + + /** @var string $class */ + /** @var string|null $method */ + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + if (!is_object($instance)) { + throw new RuntimeException(sprintf('%s container entry is not an object', $class)); + } + } else { + if (!class_exists($class)) { + if ($method) { + $class .= '::' . $method . '()'; + } + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + return [$instance, $method]; + } + + /** + * @param mixed $resolved + * @param mixed $toResolve + * + * @throws RuntimeException + */ + private function assertCallable($resolved, $toResolve): callable + { + if (!is_callable($resolved)) { + if (is_callable($toResolve) || is_object($toResolve) || is_array($toResolve)) { + $formatedToResolve = ($toResolveJson = json_encode($toResolve)) !== false ? $toResolveJson : ''; + } else { + $formatedToResolve = is_string($toResolve) ? $toResolve : ''; + } + throw new RuntimeException(sprintf('%s is not resolvable', $formatedToResolve)); + } + return $resolved; + } + + private function bindToContainer(callable $callable): callable + { + if (is_array($callable) && $callable[0] instanceof Closure) { + $callable = $callable[0]; + } + if ($this->container && $callable instanceof Closure) { + /** @var Closure $callable */ + $callable = $callable->bindTo($this->container); + } + return $callable; + } + + /** + * @param string|callable $toResolve + * @return string|callable + */ + private function prepareToResolve($toResolve) + { + if (!is_array($toResolve)) { + return $toResolve; + } + $candidate = $toResolve; + $class = array_shift($candidate); + $method = array_shift($candidate); + if (is_string($class) && is_string($method)) { + return $class . ':' . $method; + } + return $toResolve; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Error/AbstractErrorRenderer.php b/Sources/API/vendor/slim/slim/Slim/Error/AbstractErrorRenderer.php new file mode 100644 index 0000000..90b290d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Error/AbstractErrorRenderer.php @@ -0,0 +1,46 @@ +getTitle(); + } + + return $this->defaultErrorTitle; + } + + protected function getErrorDescription(Throwable $exception): string + { + if ($exception instanceof HttpException) { + return $exception->getDescription(); + } + + return $this->defaultErrorDescription; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php new file mode 100644 index 0000000..e030522 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php @@ -0,0 +1,84 @@ +The application could not run because of the following error:

'; + $html .= '

Details

'; + $html .= $this->renderExceptionFragment($exception); + } else { + $html = "

{$this->getErrorDescription($exception)}

"; + } + + return $this->renderHtmlBody($this->getErrorTitle($exception), $html); + } + + private function renderExceptionFragment(Throwable $exception): string + { + $html = sprintf('
Type: %s
', get_class($exception)); + + /** @var int|string $code */ + $code = $exception->getCode(); + $html .= sprintf('
Code: %s
', $code); + + $html .= sprintf('
Message: %s
', htmlentities($exception->getMessage())); + + $html .= sprintf('
File: %s
', $exception->getFile()); + + $html .= sprintf('
Line: %s
', $exception->getLine()); + + $html .= '

Trace

'; + $html .= sprintf('
%s
', htmlentities($exception->getTraceAsString())); + + return $html; + } + + public function renderHtmlBody(string $title = '', string $html = ''): string + { + return sprintf( + '' . + '' . + ' ' . + ' ' . + ' ' . + ' %s' . + ' ' . + ' ' . + ' ' . + '

%s

' . + '
%s
' . + ' Go Back' . + ' ' . + '', + $title, + $title, + $html + ); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php new file mode 100644 index 0000000..63d905b --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php @@ -0,0 +1,56 @@ + $this->getErrorTitle($exception)]; + + if ($displayErrorDetails) { + $error['exception'] = []; + do { + $error['exception'][] = $this->formatExceptionFragment($exception); + } while ($exception = $exception->getPrevious()); + } + + return (string) json_encode($error, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * @return array + */ + private function formatExceptionFragment(Throwable $exception): array + { + /** @var int|string $code */ + $code = $exception->getCode(); + return [ + 'type' => get_class($exception), + 'code' => $code, + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ]; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php new file mode 100644 index 0000000..3d80c74 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php @@ -0,0 +1,59 @@ +getErrorTitle($exception)}\n"; + + if ($displayErrorDetails) { + $text .= $this->formatExceptionFragment($exception); + + while ($exception = $exception->getPrevious()) { + $text .= "\nPrevious Error:\n"; + $text .= $this->formatExceptionFragment($exception); + } + } + + return $text; + } + + private function formatExceptionFragment(Throwable $exception): string + { + $text = sprintf("Type: %s\n", get_class($exception)); + + $code = $exception->getCode(); + /** @var int|string $code */ + $text .= sprintf("Code: %s\n", $code); + + $text .= sprintf("Message: %s\n", htmlentities($exception->getMessage())); + + $text .= sprintf("File: %s\n", $exception->getFile()); + + $text .= sprintf("Line: %s\n", $exception->getLine()); + + $text .= sprintf('Trace: %s', $exception->getTraceAsString()); + + return $text; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php new file mode 100644 index 0000000..1171b79 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php @@ -0,0 +1,54 @@ +\n"; + $xml .= "\n " . $this->createCdataSection($this->getErrorTitle($exception)) . "\n"; + + if ($displayErrorDetails) { + do { + $xml .= " \n"; + $xml .= ' ' . get_class($exception) . "\n"; + $xml .= ' ' . $exception->getCode() . "\n"; + $xml .= ' ' . $this->createCdataSection($exception->getMessage()) . "\n"; + $xml .= ' ' . $exception->getFile() . "\n"; + $xml .= ' ' . $exception->getLine() . "\n"; + $xml .= " \n"; + } while ($exception = $exception->getPrevious()); + } + + $xml .= ''; + + return $xml; + } + + /** + * Returns a CDATA section with the given content. + */ + private function createCdataSection(string $content): string + { + return sprintf('', str_replace(']]>', ']]]]>', $content)); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Exception/HttpBadRequestException.php b/Sources/API/vendor/slim/slim/Slim/Exception/HttpBadRequestException.php new file mode 100644 index 0000000..caea20f --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Exception/HttpBadRequestException.php @@ -0,0 +1,28 @@ +request = $request; + } + + public function getRequest(): ServerRequestInterface + { + return $this->request; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + return $this; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Exception/HttpForbiddenException.php b/Sources/API/vendor/slim/slim/Slim/Exception/HttpForbiddenException.php new file mode 100644 index 0000000..dd3bb23 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Exception/HttpForbiddenException.php @@ -0,0 +1,27 @@ +allowedMethods; + } + + /** + * @param string[] $methods + */ + public function setAllowedMethods(array $methods): self + { + $this->allowedMethods = $methods; + $this->message = 'Method not allowed. Must be one of: ' . implode(', ', $methods); + return $this; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Exception/HttpNotFoundException.php b/Sources/API/vendor/slim/slim/Slim/Exception/HttpNotFoundException.php new file mode 100644 index 0000000..865146d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Exception/HttpNotFoundException.php @@ -0,0 +1,27 @@ +message = $message; + } + + parent::__construct($request, $this->message, $this->code, $previous); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Exception/HttpUnauthorizedException.php b/Sources/API/vendor/slim/slim/Slim/Exception/HttpUnauthorizedException.php new file mode 100644 index 0000000..07bd70d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Exception/HttpUnauthorizedException.php @@ -0,0 +1,27 @@ +has(ResponseFactoryInterface::class) + && ( + $responseFactoryFromContainer = $container->get(ResponseFactoryInterface::class) + ) instanceof ResponseFactoryInterface + ? $responseFactoryFromContainer + : self::determineResponseFactory(); + + $callableResolver = $container->has(CallableResolverInterface::class) + && ( + $callableResolverFromContainer = $container->get(CallableResolverInterface::class) + ) instanceof CallableResolverInterface + ? $callableResolverFromContainer + : null; + + $routeCollector = $container->has(RouteCollectorInterface::class) + && ( + $routeCollectorFromContainer = $container->get(RouteCollectorInterface::class) + ) instanceof RouteCollectorInterface + ? $routeCollectorFromContainer + : null; + + $routeResolver = $container->has(RouteResolverInterface::class) + && ( + $routeResolverFromContainer = $container->get(RouteResolverInterface::class) + ) instanceof RouteResolverInterface + ? $routeResolverFromContainer + : null; + + $middlewareDispatcher = $container->has(MiddlewareDispatcherInterface::class) + && ( + $middlewareDispatcherFromContainer = $container->get(MiddlewareDispatcherInterface::class) + ) instanceof MiddlewareDispatcherInterface + ? $middlewareDispatcherFromContainer + : null; + + return new App( + $responseFactory, + $container, + $callableResolver, + $routeCollector, + $routeResolver, + $middlewareDispatcher + ); + } + + /** + * @throws RuntimeException + */ + public static function determineResponseFactory(): ResponseFactoryInterface + { + if (static::$responseFactory) { + if (static::$streamFactory) { + return static::attemptResponseFactoryDecoration(static::$responseFactory, static::$streamFactory); + } + return static::$responseFactory; + } + + $psr17FactoryProvider = static::$psr17FactoryProvider ?? new Psr17FactoryProvider(); + + /** @var Psr17Factory $psr17factory */ + foreach ($psr17FactoryProvider->getFactories() as $psr17factory) { + if ($psr17factory::isResponseFactoryAvailable()) { + $responseFactory = $psr17factory::getResponseFactory(); + + if (static::$streamFactory || $psr17factory::isStreamFactoryAvailable()) { + $streamFactory = static::$streamFactory ?? $psr17factory::getStreamFactory(); + return static::attemptResponseFactoryDecoration($responseFactory, $streamFactory); + } + + return $responseFactory; + } + } + + throw new RuntimeException( + "Could not detect any PSR-17 ResponseFactory implementations. " . + "Please install a supported implementation in order to use `AppFactory::create()`. " . + "See https://github.com/slimphp/Slim/blob/4.x/README.md for a list of supported implementations." + ); + } + + protected static function attemptResponseFactoryDecoration( + ResponseFactoryInterface $responseFactory, + StreamFactoryInterface $streamFactory + ): ResponseFactoryInterface { + if ( + static::$slimHttpDecoratorsAutomaticDetectionEnabled + && SlimHttpPsr17Factory::isResponseFactoryAvailable() + ) { + return SlimHttpPsr17Factory::createDecoratedResponseFactory($responseFactory, $streamFactory); + } + + return $responseFactory; + } + + public static function setPsr17FactoryProvider(Psr17FactoryProviderInterface $psr17FactoryProvider): void + { + static::$psr17FactoryProvider = $psr17FactoryProvider; + } + + public static function setResponseFactory(ResponseFactoryInterface $responseFactory): void + { + static::$responseFactory = $responseFactory; + } + + public static function setStreamFactory(StreamFactoryInterface $streamFactory): void + { + static::$streamFactory = $streamFactory; + } + + public static function setContainer(ContainerInterface $container): void + { + static::$container = $container; + } + + public static function setCallableResolver(CallableResolverInterface $callableResolver): void + { + static::$callableResolver = $callableResolver; + } + + public static function setRouteCollector(RouteCollectorInterface $routeCollector): void + { + static::$routeCollector = $routeCollector; + } + + public static function setRouteResolver(RouteResolverInterface $routeResolver): void + { + static::$routeResolver = $routeResolver; + } + + public static function setMiddlewareDispatcher(MiddlewareDispatcherInterface $middlewareDispatcher): void + { + static::$middlewareDispatcher = $middlewareDispatcher; + } + + public static function setSlimHttpDecoratorsAutomaticDetection(bool $enabled): void + { + static::$slimHttpDecoratorsAutomaticDetectionEnabled = $enabled; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php new file mode 100644 index 0000000..32a548a --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php @@ -0,0 +1,19 @@ +serverRequestCreator = $serverRequestCreator; + $this->serverRequestCreatorMethod = $serverRequestCreatorMethod; + } + + /** + * {@inheritdoc} + */ + public function createServerRequestFromGlobals(): ServerRequestInterface + { + /** @var callable $callable */ + $callable = [$this->serverRequestCreator, $this->serverRequestCreatorMethod]; + return (Closure::fromCallable($callable))(); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php new file mode 100644 index 0000000..5d63631 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php @@ -0,0 +1,39 @@ +serverRequestCreator = $serverRequestCreator; + } + + /** + * {@inheritdoc} + */ + public function createServerRequestFromGlobals(): ServerRequestInterface + { + if (!static::isServerRequestDecoratorAvailable()) { + throw new RuntimeException('The Slim-Http ServerRequest decorator is not available.'); + } + + $request = $this->serverRequestCreator->createServerRequestFromGlobals(); + + if ( + !(( + $decoratedServerRequest = new static::$serverRequestDecoratorClass($request) + ) instanceof ServerRequestInterface) + ) { + throw new RuntimeException(get_called_class() . ' could not instantiate a decorated server request.'); + } + + return $decoratedServerRequest; + } + + public static function isServerRequestDecoratorAvailable(): bool + { + return class_exists(static::$serverRequestDecoratorClass); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php new file mode 100644 index 0000000..46c46f9 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php @@ -0,0 +1,19 @@ +getFactories() as $psr17Factory) { + if ($psr17Factory::isServerRequestCreatorAvailable()) { + $serverRequestCreator = $psr17Factory::getServerRequestCreator(); + return static::attemptServerRequestCreatorDecoration($serverRequestCreator); + } + } + + throw new RuntimeException( + "Could not detect any ServerRequest creator implementations. " . + "Please install a supported implementation in order to use `App::run()` " . + "without having to pass in a `ServerRequest` object. " . + "See https://github.com/slimphp/Slim/blob/4.x/README.md for a list of supported implementations." + ); + } + + protected static function attemptServerRequestCreatorDecoration( + ServerRequestCreatorInterface $serverRequestCreator + ): ServerRequestCreatorInterface { + if ( + static::$slimHttpDecoratorsAutomaticDetectionEnabled + && SlimHttpServerRequestCreator::isServerRequestDecoratorAvailable() + ) { + return new SlimHttpServerRequestCreator($serverRequestCreator); + } + + return $serverRequestCreator; + } + + public static function setPsr17FactoryProvider(Psr17FactoryProviderInterface $psr17FactoryProvider): void + { + static::$psr17FactoryProvider = $psr17FactoryProvider; + } + + public static function setServerRequestCreator(ServerRequestCreatorInterface $serverRequestCreator): void + { + self::$serverRequestCreator = $serverRequestCreator; + } + + public static function setSlimHttpDecoratorsAutomaticDetection(bool $enabled): void + { + static::$slimHttpDecoratorsAutomaticDetectionEnabled = $enabled; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Handlers/ErrorHandler.php b/Sources/API/vendor/slim/slim/Slim/Handlers/ErrorHandler.php new file mode 100644 index 0000000..f9606e3 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Handlers/ErrorHandler.php @@ -0,0 +1,308 @@ + + */ + protected array $errorRenderers = [ + 'application/json' => JsonErrorRenderer::class, + 'application/xml' => XmlErrorRenderer::class, + 'text/xml' => XmlErrorRenderer::class, + 'text/html' => HtmlErrorRenderer::class, + 'text/plain' => PlainTextErrorRenderer::class, + ]; + + protected bool $displayErrorDetails = false; + + protected bool $logErrors; + + protected bool $logErrorDetails = false; + + protected ?string $contentType = null; + + protected ?string $method = null; + + protected ServerRequestInterface $request; + + protected Throwable $exception; + + protected int $statusCode; + + protected CallableResolverInterface $callableResolver; + + protected ResponseFactoryInterface $responseFactory; + + protected LoggerInterface $logger; + + public function __construct( + CallableResolverInterface $callableResolver, + ResponseFactoryInterface $responseFactory, + ?LoggerInterface $logger = null + ) { + $this->callableResolver = $callableResolver; + $this->responseFactory = $responseFactory; + $this->logger = $logger ?: $this->getDefaultLogger(); + } + + /** + * Invoke error handler + * + * @param ServerRequestInterface $request The most recent Request object + * @param Throwable $exception The caught Exception object + * @param bool $displayErrorDetails Whether or not to display the error details + * @param bool $logErrors Whether or not to log errors + * @param bool $logErrorDetails Whether or not to log error details + */ + public function __invoke( + ServerRequestInterface $request, + Throwable $exception, + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails + ): ResponseInterface { + $this->displayErrorDetails = $displayErrorDetails; + $this->logErrors = $logErrors; + $this->logErrorDetails = $logErrorDetails; + $this->request = $request; + $this->exception = $exception; + $this->method = $request->getMethod(); + $this->statusCode = $this->determineStatusCode(); + if ($this->contentType === null) { + $this->contentType = $this->determineContentType($request); + } + + if ($logErrors) { + $this->writeToErrorLog(); + } + + return $this->respond(); + } + + /** + * Force the content type for all error handler responses. + * + * @param string|null $contentType The content type + */ + public function forceContentType(?string $contentType): void + { + $this->contentType = $contentType; + } + + protected function determineStatusCode(): int + { + if ($this->method === 'OPTIONS') { + return 200; + } + + if ($this->exception instanceof HttpException) { + return $this->exception->getCode(); + } + + return 500; + } + + /** + * Determine which content type we know about is wanted using Accept header + * + * Note: This method is a bare-bones implementation designed specifically for + * Slim's error handling requirements. Consider a fully-feature solution such + * as willdurand/negotiation for any other situation. + */ + protected function determineContentType(ServerRequestInterface $request): ?string + { + $acceptHeader = $request->getHeaderLine('Accept'); + $selectedContentTypes = array_intersect( + explode(',', $acceptHeader), + array_keys($this->errorRenderers) + ); + $count = count($selectedContentTypes); + + if ($count) { + $current = current($selectedContentTypes); + + /** + * Ensure other supported content types take precedence over text/plain + * when multiple content types are provided via Accept header. + */ + if ($current === 'text/plain' && $count > 1) { + $next = next($selectedContentTypes); + if (is_string($next)) { + return $next; + } + } + + if (is_string($current)) { + return $current; + } + } + + if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) { + $mediaType = 'application/' . $matches[1]; + if (array_key_exists($mediaType, $this->errorRenderers)) { + return $mediaType; + } + } + + return null; + } + + /** + * Determine which renderer to use based on content type + * + * @throws RuntimeException + */ + protected function determineRenderer(): callable + { + if ($this->contentType !== null && array_key_exists($this->contentType, $this->errorRenderers)) { + $renderer = $this->errorRenderers[$this->contentType]; + } else { + $renderer = $this->defaultErrorRenderer; + } + + return $this->callableResolver->resolve($renderer); + } + + /** + * Register an error renderer for a specific content-type + * + * @param string $contentType The content-type this renderer should be registered to + * @param ErrorRendererInterface|string|callable $errorRenderer The error renderer + */ + public function registerErrorRenderer(string $contentType, $errorRenderer): void + { + $this->errorRenderers[$contentType] = $errorRenderer; + } + + /** + * Set the default error renderer + * + * @param string $contentType The content type of the default error renderer + * @param ErrorRendererInterface|string|callable $errorRenderer The default error renderer + */ + public function setDefaultErrorRenderer(string $contentType, $errorRenderer): void + { + $this->defaultErrorRendererContentType = $contentType; + $this->defaultErrorRenderer = $errorRenderer; + } + + /** + * Set the renderer for the error logger + * + * @param ErrorRendererInterface|string|callable $logErrorRenderer + */ + public function setLogErrorRenderer($logErrorRenderer): void + { + $this->logErrorRenderer = $logErrorRenderer; + } + + /** + * Write to the error log if $logErrors has been set to true + */ + protected function writeToErrorLog(): void + { + $renderer = $this->callableResolver->resolve($this->logErrorRenderer); + $error = $renderer($this->exception, $this->logErrorDetails); + if (!$this->displayErrorDetails) { + $error .= "\nTips: To display error details in HTTP response "; + $error .= 'set "displayErrorDetails" to true in the ErrorHandler constructor.'; + } + $this->logError($error); + } + + /** + * Wraps the error_log function so that this can be easily tested + */ + protected function logError(string $error): void + { + $this->logger->error($error); + } + + /** + * Returns a default logger implementation. + */ + protected function getDefaultLogger(): LoggerInterface + { + return new Logger(); + } + + protected function respond(): ResponseInterface + { + $response = $this->responseFactory->createResponse($this->statusCode); + if ($this->contentType !== null && array_key_exists($this->contentType, $this->errorRenderers)) { + $response = $response->withHeader('Content-type', $this->contentType); + } else { + $response = $response->withHeader('Content-type', $this->defaultErrorRendererContentType); + } + + if ($this->exception instanceof HttpMethodNotAllowedException) { + $allowedMethods = implode(', ', $this->exception->getAllowedMethods()); + $response = $response->withHeader('Allow', $allowedMethods); + } + + $renderer = $this->determineRenderer(); + $body = call_user_func($renderer, $this->exception, $this->displayErrorDetails); + if ($body !== false) { + /** @var string $body */ + $response->getBody()->write($body); + } + + return $response; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestHandler.php b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestHandler.php new file mode 100644 index 0000000..ea88a5f --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestHandler.php @@ -0,0 +1,48 @@ +appendRouteArgumentsToRequestAttributes = $appendRouteArgumentsToRequestAttributes; + } + + /** + * Invoke a route callable that implements RequestHandlerInterface + * + * @param array $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + if ($this->appendRouteArgumentsToRequestAttributes) { + foreach ($routeArguments as $k => $v) { + $request = $request->withAttribute($k, $v); + } + } + + return $callable($request); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php new file mode 100644 index 0000000..45b2c05 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php @@ -0,0 +1,40 @@ + $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + foreach ($routeArguments as $k => $v) { + $request = $request->withAttribute($k, $v); + } + + return $callable($request, $response, $routeArguments); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php new file mode 100644 index 0000000..c4ab16d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php @@ -0,0 +1,38 @@ + $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + return $callable($request, $response, ...array_values($routeArguments)); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php new file mode 100644 index 0000000..651111d --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php @@ -0,0 +1,44 @@ += 8.0.0'); + } + } + + /** + * Invoke a route callable with request, response and all route parameters + * as individual arguments. + * + * @param array $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + return $callable($request, $response, ...$routeArguments); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php b/Sources/API/vendor/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php new file mode 100644 index 0000000..aa1d897 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php @@ -0,0 +1,28 @@ + $routeArguments The route's placeholder arguments + * + * @return ResponseInterface The response from the callable. + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface; +} diff --git a/Sources/API/vendor/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php b/Sources/API/vendor/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php new file mode 100644 index 0000000..aa7a26a --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php @@ -0,0 +1,42 @@ + + */ + public function getArguments(): array; + + /** + * Set a route argument + */ + public function setArgument(string $name, string $value): RouteInterface; + + /** + * Replace route arguments + * + * @param array $arguments + */ + public function setArguments(array $arguments): self; + + /** + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): self; + + public function addMiddleware(MiddlewareInterface $middleware): self; + + /** + * Prepare the route for use + * + * @param array $arguments + */ + public function prepare(array $arguments): self; + + /** + * Run route + * + * This method traverses the middleware stack, including the route's callable + * and captures the resultant HTTP response object. It then sends the response + * back to the Application. + */ + public function run(ServerRequestInterface $request): ResponseInterface; +} diff --git a/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteParserInterface.php b/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteParserInterface.php new file mode 100644 index 0000000..03d9326 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteParserInterface.php @@ -0,0 +1,52 @@ + $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function relativeUrlFor(string $routeName, array $data = [], array $queryParams = []): string; + + /** + * Build the path for a named route including the base path + * + * @param string $routeName Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function urlFor(string $routeName, array $data = [], array $queryParams = []): string; + + /** + * Get fully qualified URL for named route + * + * @param UriInterface $uri + * @param string $routeName Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + */ + public function fullUrlFor(UriInterface $uri, string $routeName, array $data = [], array $queryParams = []): string; +} diff --git a/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteResolverInterface.php b/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteResolverInterface.php new file mode 100644 index 0000000..256a359 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Interfaces/RouteResolverInterface.php @@ -0,0 +1,17 @@ +getPath() + */ + public function computeRoutingResults(string $uri, string $method): RoutingResults; + + public function resolveRoute(string $identifier): RouteInterface; +} diff --git a/Sources/API/vendor/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php b/Sources/API/vendor/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php new file mode 100644 index 0000000..54d231e --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php @@ -0,0 +1,18 @@ + $context + * + * @throws InvalidArgumentException + */ + public function log($level, $message, array $context = []): void + { + error_log((string) $message); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/BodyParsingMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/BodyParsingMiddleware.php new file mode 100644 index 0000000..9a90f30 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/BodyParsingMiddleware.php @@ -0,0 +1,196 @@ + callable + */ + public function __construct(array $bodyParsers = []) + { + $this->registerDefaultBodyParsers(); + + foreach ($bodyParsers as $mediaType => $parser) { + $this->registerBodyParser($mediaType, $parser); + } + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $parsedBody = $request->getParsedBody(); + + if (empty($parsedBody)) { + $parsedBody = $this->parseBody($request); + $request = $request->withParsedBody($parsedBody); + } + + return $handler->handle($request); + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + * @param callable $callable A callable that returns parsed contents for media type. + */ + public function registerBodyParser(string $mediaType, callable $callable): self + { + $this->bodyParsers[$mediaType] = $callable; + return $this; + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + */ + public function hasBodyParser(string $mediaType): bool + { + return isset($this->bodyParsers[$mediaType]); + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + * @throws RuntimeException + */ + public function getBodyParser(string $mediaType): callable + { + if (!isset($this->bodyParsers[$mediaType])) { + throw new RuntimeException('No parser for type ' . $mediaType); + } + return $this->bodyParsers[$mediaType]; + } + + protected function registerDefaultBodyParsers(): void + { + $this->registerBodyParser('application/json', static function ($input) { + $result = json_decode($input, true); + + if (!is_array($result)) { + return null; + } + + return $result; + }); + + $this->registerBodyParser('application/x-www-form-urlencoded', static function ($input) { + parse_str($input, $data); + return $data; + }); + + $xmlCallable = static function ($input) { + $backup = self::disableXmlEntityLoader(true); + $backup_errors = libxml_use_internal_errors(true); + $result = simplexml_load_string($input); + + self::disableXmlEntityLoader($backup); + libxml_clear_errors(); + libxml_use_internal_errors($backup_errors); + + if ($result === false) { + return null; + } + + return $result; + }; + + $this->registerBodyParser('application/xml', $xmlCallable); + $this->registerBodyParser('text/xml', $xmlCallable); + } + + /** + * @return null|array|object + */ + protected function parseBody(ServerRequestInterface $request) + { + $mediaType = $this->getMediaType($request); + if ($mediaType === null) { + return null; + } + + // Check if this specific media type has a parser registered first + if (!isset($this->bodyParsers[$mediaType])) { + // If not, look for a media type with a structured syntax suffix (RFC 6839) + $parts = explode('+', $mediaType); + if (count($parts) >= 2) { + $mediaType = 'application/' . $parts[count($parts) - 1]; + } + } + + if (isset($this->bodyParsers[$mediaType])) { + $body = (string)$request->getBody(); + $parsed = $this->bodyParsers[$mediaType]($body); + + if ($parsed !== null && !is_object($parsed) && !is_array($parsed)) { + throw new RuntimeException( + 'Request body media type parser return value must be an array, an object, or null' + ); + } + + return $parsed; + } + + return null; + } + + /** + * @return string|null The serverRequest media type, minus content-type params + */ + protected function getMediaType(ServerRequestInterface $request): ?string + { + $contentType = $request->getHeader('Content-Type')[0] ?? null; + + if (is_string($contentType) && trim($contentType) !== '') { + $contentTypeParts = explode(';', $contentType); + return strtolower(trim($contentTypeParts[0])); + } + + return null; + } + + protected static function disableXmlEntityLoader(bool $disable): bool + { + if (LIBXML_VERSION >= 20900) { + // libxml >= 2.9.0 disables entity loading by default, so it is + // safe to skip the real call (deprecated in PHP 8). + return true; + } + + // @codeCoverageIgnoreStart + return libxml_disable_entity_loader($disable); + // @codeCoverageIgnoreEnd + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/ContentLengthMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/ContentLengthMiddleware.php new file mode 100644 index 0000000..8fa13bc --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/ContentLengthMiddleware.php @@ -0,0 +1,32 @@ +handle($request); + + // Add Content-Length header if not already added + $size = $response->getBody()->getSize(); + if ($size !== null && !$response->hasHeader('Content-Length')) { + $response = $response->withHeader('Content-Length', (string) $size); + } + + return $response; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php new file mode 100644 index 0000000..2eb5cc9 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php @@ -0,0 +1,212 @@ +callableResolver = $callableResolver; + $this->responseFactory = $responseFactory; + $this->displayErrorDetails = $displayErrorDetails; + $this->logErrors = $logErrors; + $this->logErrorDetails = $logErrorDetails; + $this->logger = $logger; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + return $handler->handle($request); + } catch (Throwable $e) { + return $this->handleException($request, $e); + } + } + + public function handleException(ServerRequestInterface $request, Throwable $exception): ResponseInterface + { + if ($exception instanceof HttpException) { + $request = $exception->getRequest(); + } + + $exceptionType = get_class($exception); + $handler = $this->getErrorHandler($exceptionType); + + return $handler($request, $exception, $this->displayErrorDetails, $this->logErrors, $this->logErrorDetails); + } + + /** + * Get callable to handle scenarios where an error + * occurs when processing the current request. + * + * @param string $type Exception/Throwable name. ie: RuntimeException::class + * @return callable|ErrorHandler + */ + public function getErrorHandler(string $type) + { + if (isset($this->handlers[$type])) { + return $this->callableResolver->resolve($this->handlers[$type]); + } + + if (isset($this->subClassHandlers[$type])) { + return $this->callableResolver->resolve($this->subClassHandlers[$type]); + } + + foreach ($this->subClassHandlers as $class => $handler) { + if (is_subclass_of($type, $class)) { + return $this->callableResolver->resolve($handler); + } + } + + return $this->getDefaultErrorHandler(); + } + + /** + * Get default error handler + * + * @return ErrorHandler|callable + */ + public function getDefaultErrorHandler() + { + if ($this->defaultErrorHandler === null) { + $this->defaultErrorHandler = new ErrorHandler( + $this->callableResolver, + $this->responseFactory, + $this->logger + ); + } + + return $this->callableResolver->resolve($this->defaultErrorHandler); + } + + /** + * Set callable as the default Slim application error handler. + * + * The callable signature MUST match the ErrorHandlerInterface + * + * @see \Slim\Interfaces\ErrorHandlerInterface + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Throwable + * 3. Boolean $displayErrorDetails + * 4. Boolean $logErrors + * 5. Boolean $logErrorDetails + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param string|callable|ErrorHandler $handler + */ + public function setDefaultErrorHandler($handler): self + { + $this->defaultErrorHandler = $handler; + return $this; + } + + /** + * Set callable to handle scenarios where an error + * occurs when processing the current request. + * + * The callable signature MUST match the ErrorHandlerInterface + * + * Pass true to $handleSubclasses to make the handler handle all subclasses of + * the type as well. Pass an array of classes to make the same function handle multiple exceptions. + * + * @see \Slim\Interfaces\ErrorHandlerInterface + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Throwable + * 3. Boolean $displayErrorDetails + * 4. Boolean $logErrors + * 5. Boolean $logErrorDetails + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param string|string[] $typeOrTypes Exception/Throwable name. + * ie: RuntimeException::class or an array of classes + * ie: [HttpNotFoundException::class, HttpMethodNotAllowedException::class] + * @param string|callable|ErrorHandlerInterface $handler + */ + public function setErrorHandler($typeOrTypes, $handler, bool $handleSubclasses = false): self + { + if (is_array($typeOrTypes)) { + foreach ($typeOrTypes as $type) { + $this->addErrorHandler($type, $handler, $handleSubclasses); + } + } else { + $this->addErrorHandler($typeOrTypes, $handler, $handleSubclasses); + } + + return $this; + } + + /** + * Used internally to avoid code repetition when passing multiple exceptions to setErrorHandler(). + * @param string|callable|ErrorHandlerInterface $handler + */ + private function addErrorHandler(string $type, $handler, bool $handleSubclasses): void + { + if ($handleSubclasses) { + $this->subClassHandlers[$type] = $handler; + } else { + $this->handlers[$type] = $handler; + } + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php new file mode 100644 index 0000000..079a1f1 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php @@ -0,0 +1,43 @@ +getHeaderLine('X-Http-Method-Override'); + + if ($methodHeader) { + $request = $request->withMethod($methodHeader); + } elseif (strtoupper($request->getMethod()) === 'POST') { + $body = $request->getParsedBody(); + + if (is_array($body) && !empty($body['_METHOD'])) { + $request = $request->withMethod($body['_METHOD']); + } + + if ($request->getBody()->eof()) { + $request->getBody()->rewind(); + } + } + + return $handler->handle($request); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php new file mode 100644 index 0000000..69ee1f6 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php @@ -0,0 +1,74 @@ +streamFactory = $streamFactory; + $this->style = $style; + + if (!in_array($style, [static::APPEND, static::PREPEND], true)) { + throw new InvalidArgumentException("Invalid style `{$style}`. Must be `append` or `prepend`"); + } + } + + /** + * @throws Throwable + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + ob_start(); + $response = $handler->handle($request); + $output = ob_get_clean(); + } catch (Throwable $e) { + ob_end_clean(); + throw $e; + } + + if (!empty($output)) { + if ($this->style === static::PREPEND) { + $body = $this->streamFactory->createStream(); + $body->write($output . $response->getBody()); + $response = $response->withBody($body); + } elseif ($this->style === static::APPEND && $response->getBody()->isWritable()) { + $response->getBody()->write($output); + } + } + + return $response; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php b/Sources/API/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php new file mode 100644 index 0000000..a3d3085 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php @@ -0,0 +1,98 @@ +routeResolver = $routeResolver; + $this->routeParser = $routeParser; + } + + /** + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + * @throws RuntimeException + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $request = $this->performRouting($request); + return $handler->handle($request); + } + + /** + * Perform routing + * + * @param ServerRequestInterface $request PSR7 Server Request + * + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + * @throws RuntimeException + */ + public function performRouting(ServerRequestInterface $request): ServerRequestInterface + { + $request = $request->withAttribute(RouteContext::ROUTE_PARSER, $this->routeParser); + + $routingResults = $this->resolveRoutingResultsFromRequest($request); + $routeStatus = $routingResults->getRouteStatus(); + + $request = $request->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); + + switch ($routeStatus) { + case RoutingResults::FOUND: + $routeArguments = $routingResults->getRouteArguments(); + $routeIdentifier = $routingResults->getRouteIdentifier() ?? ''; + $route = $this->routeResolver + ->resolveRoute($routeIdentifier) + ->prepare($routeArguments); + return $request->withAttribute(RouteContext::ROUTE, $route); + + case RoutingResults::NOT_FOUND: + throw new HttpNotFoundException($request); + + case RoutingResults::METHOD_NOT_ALLOWED: + $exception = new HttpMethodNotAllowedException($request); + $exception->setAllowedMethods($routingResults->getAllowedMethods()); + throw $exception; + + default: + throw new RuntimeException('An unexpected error occurred while performing routing.'); + } + } + + /** + * Resolves the route from the given request + */ + protected function resolveRoutingResultsFromRequest(ServerRequestInterface $request): RoutingResults + { + return $this->routeResolver->computeRoutingResults( + $request->getUri()->getPath(), + $request->getMethod() + ); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/MiddlewareDispatcher.php b/Sources/API/vendor/slim/slim/Slim/MiddlewareDispatcher.php new file mode 100644 index 0000000..7e05644 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/MiddlewareDispatcher.php @@ -0,0 +1,275 @@ +seedMiddlewareStack($kernel); + $this->callableResolver = $callableResolver; + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function seedMiddlewareStack(RequestHandlerInterface $kernel): void + { + $this->tip = $kernel; + } + + /** + * Invoke the middleware stack + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->tip->handle($request); + } + + /** + * Add a new middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + * + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): MiddlewareDispatcherInterface + { + if ($middleware instanceof MiddlewareInterface) { + return $this->addMiddleware($middleware); + } + + if (is_string($middleware)) { + return $this->addDeferred($middleware); + } + + if (is_callable($middleware)) { + return $this->addCallable($middleware); + } + + /** @phpstan-ignore-next-line */ + throw new RuntimeException( + 'A middleware must be an object/class name referencing an implementation of ' . + 'MiddlewareInterface or a callable with a matching signature.' + ); + } + + /** + * Add a new middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addMiddleware(MiddlewareInterface $middleware): MiddlewareDispatcherInterface + { + $next = $this->tip; + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + private MiddlewareInterface $middleware; + + private RequestHandlerInterface $next; + + public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $next) + { + $this->middleware = $middleware; + $this->next = $next; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->next); + } + }; + + return $this; + } + + /** + * Add a new middleware by class name + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addDeferred(string $middleware): self + { + $next = $this->tip; + $this->tip = new class ( + $middleware, + $next, + $this->container, + $this->callableResolver + ) implements RequestHandlerInterface { + private string $middleware; + + private RequestHandlerInterface $next; + + private ?ContainerInterface $container; + + private ?CallableResolverInterface $callableResolver; + + public function __construct( + string $middleware, + RequestHandlerInterface $next, + ?ContainerInterface $container = null, + ?CallableResolverInterface $callableResolver = null + ) { + $this->middleware = $middleware; + $this->next = $next; + $this->container = $container; + $this->callableResolver = $callableResolver; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveMiddleware($this->middleware); + return $callable($request, $this->next); + } + + $callable = null; + + if ($this->callableResolver instanceof CallableResolverInterface) { + try { + $callable = $this->callableResolver->resolve($this->middleware); + } catch (RuntimeException $e) { + // Do Nothing + } + } + + if (!$callable) { + $resolved = $this->middleware; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { + $resolved = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($resolved)) { + $instance = $this->container->get($resolved); + if ($instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + } elseif (!function_exists($resolved)) { + if (!class_exists($resolved)) { + throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + } + $instance = new $resolved($this->container); + } + + if ($instance && $instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + + $callable = $instance ?? $resolved; + if ($instance && $method) { + $callable = [$instance, $method]; + } + + if ($this->container && $callable instanceof Closure) { + $callable = $callable->bindTo($this->container); + } + } + + if (!is_callable($callable)) { + throw new RuntimeException( + sprintf( + 'Middleware %s is not resolvable', + $this->middleware + ) + ); + } + + return $callable($request, $this->next); + } + }; + + return $this; + } + + /** + * Add a (non-standard) callable middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addCallable(callable $middleware): self + { + $next = $this->tip; + + if ($this->container && $middleware instanceof Closure) { + /** @var Closure $middleware */ + $middleware = $middleware->bindTo($this->container); + } + + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + /** + * @var callable + */ + private $middleware; + + /** + * @var RequestHandlerInterface + */ + private $next; + + public function __construct(callable $middleware, RequestHandlerInterface $next) + { + $this->middleware = $middleware; + $this->next = $next; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return ($this->middleware)($request, $this->next); + } + }; + + return $this; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/ResponseEmitter.php b/Sources/API/vendor/slim/slim/Slim/ResponseEmitter.php new file mode 100644 index 0000000..fac36e9 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/ResponseEmitter.php @@ -0,0 +1,136 @@ +responseChunkSize = $responseChunkSize; + } + + /** + * Send the response the client + */ + public function emit(ResponseInterface $response): void + { + $isEmpty = $this->isResponseEmpty($response); + if (headers_sent() === false) { + $this->emitHeaders($response); + + // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers. + // See https://github.com/slimphp/Slim/issues/1730 + + $this->emitStatusLine($response); + } + + if (!$isEmpty) { + $this->emitBody($response); + } + } + + /** + * Emit Response Headers + */ + private function emitHeaders(ResponseInterface $response): void + { + foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + } + + /** + * Emit Status Line + */ + private function emitStatusLine(ResponseInterface $response): void + { + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + } + + /** + * Emit Body + */ + private function emitBody(ResponseInterface $response): void + { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + $amountToRead = (int) $response->getHeaderLine('Content-Length'); + if (!$amountToRead) { + $amountToRead = $body->getSize(); + } + + if ($amountToRead) { + while ($amountToRead > 0 && !$body->eof()) { + $length = min($this->responseChunkSize, $amountToRead); + $data = $body->read($length); + echo $data; + + $amountToRead -= strlen($data); + + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } else { + while (!$body->eof()) { + echo $body->read($this->responseChunkSize); + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } + } + + /** + * Asserts response body is empty or status code is 204, 205 or 304 + */ + public function isResponseEmpty(ResponseInterface $response): bool + { + if (in_array($response->getStatusCode(), [204, 205, 304], true)) { + return true; + } + $stream = $response->getBody(); + $seekable = $stream->isSeekable(); + if ($seekable) { + $stream->rewind(); + } + return $seekable ? $stream->read(1) === '' : $stream->eof(); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/Dispatcher.php b/Sources/API/vendor/slim/slim/Slim/Routing/Dispatcher.php new file mode 100644 index 0000000..e33eac3 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/Dispatcher.php @@ -0,0 +1,78 @@ +routeCollector = $routeCollector; + } + + protected function createDispatcher(): FastRouteDispatcher + { + if ($this->dispatcher) { + return $this->dispatcher; + } + + $routeDefinitionCallback = function (FastRouteCollector $r): void { + $basePath = $this->routeCollector->getBasePath(); + + foreach ($this->routeCollector->getRoutes() as $route) { + $r->addRoute($route->getMethods(), $basePath . $route->getPattern(), $route->getIdentifier()); + } + }; + + $cacheFile = $this->routeCollector->getCacheFile(); + if ($cacheFile) { + /** @var FastRouteDispatcher $dispatcher */ + $dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [ + 'dataGenerator' => GroupCountBased::class, + 'dispatcher' => FastRouteDispatcher::class, + 'routeParser' => new Std(), + 'cacheFile' => $cacheFile, + ]); + } else { + /** @var FastRouteDispatcher $dispatcher */ + $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [ + 'dataGenerator' => GroupCountBased::class, + 'dispatcher' => FastRouteDispatcher::class, + 'routeParser' => new Std(), + ]); + } + + $this->dispatcher = $dispatcher; + return $this->dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch(string $method, string $uri): RoutingResults + { + $dispatcher = $this->createDispatcher(); + $results = $dispatcher->dispatch($method, $uri); + return new RoutingResults($this, $method, $uri, $results[0], $results[1], $results[2]); + } + + /** + * {@inheritdoc} + */ + public function getAllowedMethods(string $uri): array + { + $dispatcher = $this->createDispatcher(); + return $dispatcher->getAllowedMethods($uri); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/FastRouteDispatcher.php b/Sources/API/vendor/slim/slim/Slim/Routing/FastRouteDispatcher.php new file mode 100644 index 0000000..1f567b5 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/FastRouteDispatcher.php @@ -0,0 +1,109 @@ +} + */ + public function dispatch($httpMethod, $uri): array + { + $routingResults = $this->routingResults($httpMethod, $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + $routingResults = $this->routingResults('GET', $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + } + + // If nothing else matches, try fallback routes + $routingResults = $this->routingResults('*', $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + + if (!empty($this->getAllowedMethods($uri))) { + return [self::METHOD_NOT_ALLOWED, null, []]; + } + + return [self::NOT_FOUND, null, []]; + } + + /** + * @param string $httpMethod + * @param string $uri + * + * @return array{int, string|null, array} + */ + private function routingResults(string $httpMethod, string $uri): array + { + if (isset($this->staticRouteMap[$httpMethod][$uri])) { + /** @var string $routeIdentifier */ + $routeIdentifier = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $routeIdentifier, []]; + } + + if (isset($this->variableRouteData[$httpMethod])) { + /** @var array{0: int, 1?: string, 2?: array} $result */ + $result = $this->dispatchVariableRoute($this->variableRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + /** @var array{int, string, array} $result */ + return [self::FOUND, $result[1], $result[2]]; + } + } + + return [self::NOT_FOUND, null, []]; + } + + /** + * @param string $uri + * + * @return string[] + */ + public function getAllowedMethods(string $uri): array + { + if (isset($this->allowedMethods[$uri])) { + return $this->allowedMethods[$uri]; + } + + $this->allowedMethods[$uri] = []; + foreach ($this->staticRouteMap as $method => $uriMap) { + if (isset($uriMap[$uri])) { + $this->allowedMethods[$uri][] = $method; + } + } + + foreach ($this->variableRouteData as $method => $routeData) { + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $this->allowedMethods[$uri][] = $method; + } + } + + return $this->allowedMethods[$uri]; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/Route.php b/Sources/API/vendor/slim/slim/Slim/Routing/Route.php new file mode 100644 index 0000000..c498423 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/Route.php @@ -0,0 +1,360 @@ + + */ + protected array $arguments = []; + + /** + * Route arguments parameters + * + * @var string[] + */ + protected array $savedArguments = []; + + /** + * Container + */ + protected ?ContainerInterface $container = null; + + protected MiddlewareDispatcher $middlewareDispatcher; + + /** + * Route callable + * + * @var callable|string + */ + protected $callable; + + protected CallableResolverInterface $callableResolver; + + protected ResponseFactoryInterface $responseFactory; + + /** + * Route pattern + */ + protected string $pattern; + + protected bool $groupMiddlewareAppended = false; + + /** + * @param string[] $methods The route HTTP methods + * @param string $pattern The route pattern + * @param callable|string $callable The route callable + * @param ResponseFactoryInterface $responseFactory + * @param CallableResolverInterface $callableResolver + * @param ContainerInterface|null $container + * @param InvocationStrategyInterface|null $invocationStrategy + * @param RouteGroup[] $groups The parent route groups + * @param int $identifier The route identifier + */ + public function __construct( + array $methods, + string $pattern, + $callable, + ResponseFactoryInterface $responseFactory, + CallableResolverInterface $callableResolver, + ?ContainerInterface $container = null, + ?InvocationStrategyInterface $invocationStrategy = null, + array $groups = [], + int $identifier = 0 + ) { + $this->methods = $methods; + $this->pattern = $pattern; + $this->callable = $callable; + $this->responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->invocationStrategy = $invocationStrategy ?? new RequestResponse(); + $this->groups = $groups; + $this->identifier = 'route' . $identifier; + $this->middlewareDispatcher = new MiddlewareDispatcher($this, $callableResolver, $container); + } + + public function getCallableResolver(): CallableResolverInterface + { + return $this->callableResolver; + } + + /** + * {@inheritdoc} + */ + public function getInvocationStrategy(): InvocationStrategyInterface + { + return $this->invocationStrategy; + } + + /** + * {@inheritdoc} + */ + public function setInvocationStrategy(InvocationStrategyInterface $invocationStrategy): RouteInterface + { + $this->invocationStrategy = $invocationStrategy; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * {@inheritdoc} + */ + public function getPattern(): string + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function setPattern(string $pattern): RouteInterface + { + $this->pattern = $pattern; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCallable() + { + return $this->callable; + } + + /** + * {@inheritdoc} + */ + public function setCallable($callable): RouteInterface + { + $this->callable = $callable; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName(string $name): RouteInterface + { + $this->name = $name; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * {@inheritdoc} + */ + public function getArgument(string $name, ?string $default = null): ?string + { + if (array_key_exists($name, $this->arguments)) { + return $this->arguments[$name]; + } + return $default; + } + + /** + * {@inheritdoc} + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments, bool $includeInSavedArguments = true): RouteInterface + { + if ($includeInSavedArguments) { + $this->savedArguments = $arguments; + } + + $this->arguments = $arguments; + return $this; + } + + /** + * @return RouteGroupInterface[] + */ + public function getGroups(): array + { + return $this->groups; + } + + /** + * {@inheritdoc} + */ + public function add($middleware): RouteInterface + { + $this->middlewareDispatcher->add($middleware); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addMiddleware(MiddlewareInterface $middleware): RouteInterface + { + $this->middlewareDispatcher->addMiddleware($middleware); + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(array $arguments): RouteInterface + { + $this->arguments = array_replace($this->savedArguments, $arguments); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setArgument(string $name, string $value, bool $includeInSavedArguments = true): RouteInterface + { + if ($includeInSavedArguments) { + $this->savedArguments[$name] = $value; + } + + $this->arguments[$name] = $value; + return $this; + } + + /** + * {@inheritdoc} + */ + public function run(ServerRequestInterface $request): ResponseInterface + { + if (!$this->groupMiddlewareAppended) { + $this->appendGroupMiddlewareToRoute(); + } + + return $this->middlewareDispatcher->handle($request); + } + + /** + * @return void + */ + protected function appendGroupMiddlewareToRoute(): void + { + $inner = $this->middlewareDispatcher; + $this->middlewareDispatcher = new MiddlewareDispatcher($inner, $this->callableResolver, $this->container); + + /** @var RouteGroupInterface $group */ + foreach (array_reverse($this->groups) as $group) { + $group->appendMiddlewareToDispatcher($this->middlewareDispatcher); + } + + $this->groupMiddlewareAppended = true; + } + + /** + * {@inheritdoc} + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } + $strategy = $this->invocationStrategy; + + /** @var string[] $strategyImplements */ + $strategyImplements = class_implements($strategy); + + if ( + is_array($callable) + && $callable[0] instanceof RequestHandlerInterface + && !in_array(RequestHandlerInvocationStrategyInterface::class, $strategyImplements) + ) { + $strategy = new RequestHandler(); + } + + $response = $this->responseFactory->createResponse(); + return $strategy($callable, $request, $response, $this->arguments); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollector.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollector.php new file mode 100644 index 0000000..4a74285 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollector.php @@ -0,0 +1,280 @@ +responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->defaultInvocationStrategy = $defaultInvocationStrategy ?? new RequestResponse(); + $this->routeParser = $routeParser ?? new RouteParser($this); + + if ($cacheFile) { + $this->setCacheFile($cacheFile); + } + } + + public function getRouteParser(): RouteParserInterface + { + return $this->routeParser; + } + + /** + * Get default route invocation strategy + */ + public function getDefaultInvocationStrategy(): InvocationStrategyInterface + { + return $this->defaultInvocationStrategy; + } + + public function setDefaultInvocationStrategy(InvocationStrategyInterface $strategy): RouteCollectorInterface + { + $this->defaultInvocationStrategy = $strategy; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCacheFile(): ?string + { + return $this->cacheFile; + } + + /** + * {@inheritdoc} + */ + public function setCacheFile(string $cacheFile): RouteCollectorInterface + { + if (file_exists($cacheFile) && !is_readable($cacheFile)) { + throw new RuntimeException( + sprintf('Route collector cache file `%s` is not readable', $cacheFile) + ); + } + + if (!file_exists($cacheFile) && !is_writable(dirname($cacheFile))) { + throw new RuntimeException( + sprintf('Route collector cache file directory `%s` is not writable', dirname($cacheFile)) + ); + } + + $this->cacheFile = $cacheFile; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getBasePath(): string + { + return $this->basePath; + } + + /** + * Set the base path used in urlFor() + */ + public function setBasePath(string $basePath): RouteCollectorInterface + { + $this->basePath = $basePath; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRoutes(): array + { + return $this->routes; + } + + /** + * {@inheritdoc} + */ + public function removeNamedRoute(string $name): RouteCollectorInterface + { + $route = $this->getNamedRoute($name); + + unset($this->routesByName[$route->getName()], $this->routes[$route->getIdentifier()]); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNamedRoute(string $name): RouteInterface + { + if (isset($this->routesByName[$name])) { + $route = $this->routesByName[$name]; + if ($route->getName() === $name) { + return $route; + } + + unset($this->routesByName[$name]); + } + + foreach ($this->routes as $route) { + if ($name === $route->getName()) { + $this->routesByName[$name] = $route; + return $route; + } + } + + throw new RuntimeException('Named route does not exist for name: ' . $name); + } + + /** + * {@inheritdoc} + */ + public function lookupRoute(string $identifier): RouteInterface + { + if (!isset($this->routes[$identifier])) { + throw new RuntimeException('Route not found, looks like your route cache is stale.'); + } + return $this->routes[$identifier]; + } + + /** + * {@inheritdoc} + */ + public function group(string $pattern, $callable): RouteGroupInterface + { + $routeCollectorProxy = new RouteCollectorProxy( + $this->responseFactory, + $this->callableResolver, + $this->container, + $this, + $pattern + ); + + $routeGroup = new RouteGroup($pattern, $callable, $this->callableResolver, $routeCollectorProxy); + $this->routeGroups[] = $routeGroup; + + $routeGroup->collectRoutes(); + array_pop($this->routeGroups); + + return $routeGroup; + } + + /** + * {@inheritdoc} + */ + public function map(array $methods, string $pattern, $handler): RouteInterface + { + $route = $this->createRoute($methods, $pattern, $handler); + $this->routes[$route->getIdentifier()] = $route; + + $routeName = $route->getName(); + if ($routeName !== null && !isset($this->routesByName[$routeName])) { + $this->routesByName[$routeName] = $route; + } + + $this->routeCounter++; + + return $route; + } + + /** + * @param string[] $methods + * @param callable|string $callable + */ + protected function createRoute(array $methods, string $pattern, $callable): RouteInterface + { + return new Route( + $methods, + $pattern, + $callable, + $this->responseFactory, + $this->callableResolver, + $this->container, + $this->defaultInvocationStrategy, + $this->routeGroups, + $this->routeCounter + ); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollectorProxy.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollectorProxy.php new file mode 100644 index 0000000..f8bc232 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteCollectorProxy.php @@ -0,0 +1,187 @@ +responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->routeCollector = $routeCollector ?? new RouteCollector($responseFactory, $callableResolver, $container); + $this->groupPattern = $groupPattern; + } + + /** + * {@inheritdoc} + */ + public function getResponseFactory(): ResponseFactoryInterface + { + return $this->responseFactory; + } + + /** + * {@inheritdoc} + */ + public function getCallableResolver(): CallableResolverInterface + { + return $this->callableResolver; + } + + /** + * {@inheritdoc} + */ + public function getContainer(): ?ContainerInterface + { + return $this->container; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollector(): RouteCollectorInterface + { + return $this->routeCollector; + } + + /** + * {@inheritdoc} + */ + public function getBasePath(): string + { + return $this->routeCollector->getBasePath(); + } + + /** + * {@inheritdoc} + */ + public function setBasePath(string $basePath): RouteCollectorProxyInterface + { + $this->routeCollector->setBasePath($basePath); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get(string $pattern, $callable): RouteInterface + { + return $this->map(['GET'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function post(string $pattern, $callable): RouteInterface + { + return $this->map(['POST'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function put(string $pattern, $callable): RouteInterface + { + return $this->map(['PUT'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function patch(string $pattern, $callable): RouteInterface + { + return $this->map(['PATCH'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function delete(string $pattern, $callable): RouteInterface + { + return $this->map(['DELETE'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function options(string $pattern, $callable): RouteInterface + { + return $this->map(['OPTIONS'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function any(string $pattern, $callable): RouteInterface + { + return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function map(array $methods, string $pattern, $callable): RouteInterface + { + $pattern = $this->groupPattern . $pattern; + + return $this->routeCollector->map($methods, $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function group(string $pattern, $callable): RouteGroupInterface + { + $pattern = $this->groupPattern . $pattern; + + return $this->routeCollector->group($pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function redirect(string $from, $to, int $status = 302): RouteInterface + { + $responseFactory = $this->responseFactory; + + $handler = function () use ($to, $status, $responseFactory) { + $response = $responseFactory->createResponse($status); + return $response->withHeader('Location', (string) $to); + }; + + return $this->get($from, $handler); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteContext.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteContext.php new file mode 100644 index 0000000..3ba5e23 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteContext.php @@ -0,0 +1,88 @@ +getAttribute(self::ROUTE); + $routeParser = $serverRequest->getAttribute(self::ROUTE_PARSER); + $routingResults = $serverRequest->getAttribute(self::ROUTING_RESULTS); + $basePath = $serverRequest->getAttribute(self::BASE_PATH); + + if ($routeParser === null || $routingResults === null) { + throw new RuntimeException('Cannot create RouteContext before routing has been completed'); + } + + /** @var RouteInterface|null $route */ + /** @var RouteParserInterface $routeParser */ + /** @var RoutingResults $routingResults */ + /** @var string|null $basePath */ + return new self($route, $routeParser, $routingResults, $basePath); + } + + private ?RouteInterface $route; + + private RouteParserInterface $routeParser; + + private RoutingResults $routingResults; + + private ?string $basePath; + + private function __construct( + ?RouteInterface $route, + RouteParserInterface $routeParser, + RoutingResults $routingResults, + ?string $basePath = null + ) { + $this->route = $route; + $this->routeParser = $routeParser; + $this->routingResults = $routingResults; + $this->basePath = $basePath; + } + + public function getRoute(): ?RouteInterface + { + return $this->route; + } + + public function getRouteParser(): RouteParserInterface + { + return $this->routeParser; + } + + public function getRoutingResults(): RoutingResults + { + return $this->routingResults; + } + + public function getBasePath(): string + { + if ($this->basePath === null) { + throw new RuntimeException('No base path defined.'); + } + return $this->basePath; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteGroup.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteGroup.php new file mode 100644 index 0000000..cd2f4e7 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteGroup.php @@ -0,0 +1,104 @@ +pattern = $pattern; + $this->callable = $callable; + $this->callableResolver = $callableResolver; + $this->routeCollectorProxy = $routeCollectorProxy; + } + + /** + * {@inheritdoc} + */ + public function collectRoutes(): RouteGroupInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } + $callable($this->routeCollectorProxy); + return $this; + } + + /** + * {@inheritdoc} + */ + public function add($middleware): RouteGroupInterface + { + $this->middleware[] = $middleware; + return $this; + } + + /** + * {@inheritdoc} + */ + public function addMiddleware(MiddlewareInterface $middleware): RouteGroupInterface + { + $this->middleware[] = $middleware; + return $this; + } + + /** + * {@inheritdoc} + */ + public function appendMiddlewareToDispatcher(MiddlewareDispatcher $dispatcher): RouteGroupInterface + { + foreach ($this->middleware as $middleware) { + $dispatcher->add($middleware); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPattern(): string + { + return $this->pattern; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteParser.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteParser.php new file mode 100644 index 0000000..afb533c --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteParser.php @@ -0,0 +1,127 @@ +routeCollector = $routeCollector; + $this->routeParser = new Std(); + } + + /** + * {@inheritdoc} + */ + public function relativeUrlFor(string $routeName, array $data = [], array $queryParams = []): string + { + $route = $this->routeCollector->getNamedRoute($routeName); + $pattern = $route->getPattern(); + + $segments = []; + $segmentName = ''; + + /* + * $routes is an associative array of expressions representing a route as multiple segments + * There is an expression for each optional parameter plus one without the optional parameters + * The most specific is last, hence why we reverse the array before iterating over it + */ + $expressions = array_reverse($this->routeParser->parse($pattern)); + foreach ($expressions as $expression) { + foreach ($expression as $segment) { + /* + * Each $segment is either a string or an array of strings + * containing optional parameters of an expression + */ + if (is_string($segment)) { + $segments[] = $segment; + continue; + } + + /** @var string[] $segment */ + /* + * If we don't have a data element for this segment in the provided $data + * we cancel testing to move onto the next expression with a less specific item + */ + if (!array_key_exists($segment[0], $data)) { + $segments = []; + $segmentName = $segment[0]; + break; + } + + $segments[] = $data[$segment[0]]; + } + + /* + * If we get to this logic block we have found all the parameters + * for the provided $data which means we don't need to continue testing + * less specific expressions + */ + if (!empty($segments)) { + break; + } + } + + if (empty($segments)) { + throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName); + } + + $url = implode('', $segments); + if ($queryParams) { + $url .= '?' . http_build_query($queryParams); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function urlFor(string $routeName, array $data = [], array $queryParams = []): string + { + $basePath = $this->routeCollector->getBasePath(); + $url = $this->relativeUrlFor($routeName, $data, $queryParams); + + if ($basePath) { + $url = $basePath . $url; + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function fullUrlFor(UriInterface $uri, string $routeName, array $data = [], array $queryParams = []): string + { + $path = $this->urlFor($routeName, $data, $queryParams); + $scheme = $uri->getScheme(); + $authority = $uri->getAuthority(); + $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); + return $protocol . $path; + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteResolver.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteResolver.php new file mode 100644 index 0000000..d4f4eaf --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteResolver.php @@ -0,0 +1,56 @@ +routeCollector = $routeCollector; + $this->dispatcher = $dispatcher ?? new Dispatcher($routeCollector); + } + + /** + * @param string $uri Should be $request->getUri()->getPath() + */ + public function computeRoutingResults(string $uri, string $method): RoutingResults + { + $uri = rawurldecode($uri); + if ($uri === '' || $uri[0] !== '/') { + $uri = '/' . $uri; + } + return $this->dispatcher->dispatch($method, $uri); + } + + /** + * @throws RuntimeException + */ + public function resolveRoute(string $identifier): RouteInterface + { + return $this->routeCollector->lookupRoute($identifier); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RouteRunner.php b/Sources/API/vendor/slim/slim/Slim/Routing/RouteRunner.php new file mode 100644 index 0000000..40946af --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RouteRunner.php @@ -0,0 +1,70 @@ +routeResolver = $routeResolver; + $this->routeParser = $routeParser; + $this->routeCollectorProxy = $routeCollectorProxy; + } + + /** + * This request handler is instantiated automatically in App::__construct() + * It is at the very tip of the middleware queue meaning it will be executed + * last and it detects whether or not routing has been performed in the user + * defined middleware stack. In the event that the user did not perform routing + * it is done here + * + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // If routing hasn't been done, then do it now so we can dispatch + if ($request->getAttribute(RouteContext::ROUTING_RESULTS) === null) { + $routingMiddleware = new RoutingMiddleware($this->routeResolver, $this->routeParser); + $request = $routingMiddleware->performRouting($request); + } + + if ($this->routeCollectorProxy !== null) { + $request = $request->withAttribute( + RouteContext::BASE_PATH, + $this->routeCollectorProxy->getBasePath() + ); + } + + /** @var Route $route */ + $route = $request->getAttribute(RouteContext::ROUTE); + return $route->run($request); + } +} diff --git a/Sources/API/vendor/slim/slim/Slim/Routing/RoutingResults.php b/Sources/API/vendor/slim/slim/Slim/Routing/RoutingResults.php new file mode 100644 index 0000000..ac2fa64 --- /dev/null +++ b/Sources/API/vendor/slim/slim/Slim/Routing/RoutingResults.php @@ -0,0 +1,112 @@ + + */ + protected array $routeArguments; + + /** + * @param array $routeArguments + */ + public function __construct( + DispatcherInterface $dispatcher, + string $method, + string $uri, + int $routeStatus, + ?string $routeIdentifier = null, + array $routeArguments = [] + ) { + $this->dispatcher = $dispatcher; + $this->method = $method; + $this->uri = $uri; + $this->routeStatus = $routeStatus; + $this->routeIdentifier = $routeIdentifier; + $this->routeArguments = $routeArguments; + } + + public function getDispatcher(): DispatcherInterface + { + return $this->dispatcher; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getUri(): string + { + return $this->uri; + } + + public function getRouteStatus(): int + { + return $this->routeStatus; + } + + public function getRouteIdentifier(): ?string + { + return $this->routeIdentifier; + } + + /** + * @return array + */ + public function getRouteArguments(bool $urlDecode = true): array + { + if (!$urlDecode) { + return $this->routeArguments; + } + + $routeArguments = []; + foreach ($this->routeArguments as $key => $value) { + $routeArguments[$key] = rawurldecode($value); + } + + return $routeArguments; + } + + /** + * @return string[] + */ + public function getAllowedMethods(): array + { + return $this->dispatcher->getAllowedMethods($this->uri); + } +} diff --git a/Sources/API/vendor/slim/slim/composer.json b/Sources/API/vendor/slim/slim/composer.json new file mode 100644 index 0000000..31e8b55 --- /dev/null +++ b/Sources/API/vendor/slim/slim/composer.json @@ -0,0 +1,102 @@ +{ + "name": "slim/slim", + "type": "library", + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "keywords": ["framework","micro","api","router"], + "homepage": "https://www.slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "require": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + "nikic/fast-route": "^1.3", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "ext-simplexml": "*", + "adriansuter/php-autoload-override": "^1.3", + "guzzlehttp/psr7": "^2.4", + "httpsoft/http-message": "^1.0", + "httpsoft/http-server-request": "^1.0", + "laminas/laminas-diactoros": "^2.17", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "slim/http": "^1.2", + "slim/psr7": "^1.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "autoload-dev": { + "psr-4": { + "Slim\\Tests\\": "tests" + } + }, + "scripts": { + "test": [ + "@phpunit", + "@phpcs", + "@phpstan" + ], + "phpunit": "phpunit", + "phpcs": "phpcs", + "phpstan": "phpstan --memory-limit=-1" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information.", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim" + }, + "config": { + "sort-packages": true + } +} diff --git a/Sources/API/vendor/symfony/deprecation-contracts/CHANGELOG.md b/Sources/API/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/Sources/API/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/Sources/API/vendor/symfony/deprecation-contracts/LICENSE b/Sources/API/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..406242f --- /dev/null +++ b/Sources/API/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/deprecation-contracts/README.md b/Sources/API/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/Sources/API/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/Sources/API/vendor/symfony/deprecation-contracts/composer.json b/Sources/API/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..774200f --- /dev/null +++ b/Sources/API/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/Sources/API/vendor/symfony/deprecation-contracts/function.php b/Sources/API/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..2d56512 --- /dev/null +++ b/Sources/API/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/Sources/API/vendor/symfony/finder/CHANGELOG.md b/Sources/API/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000..1a12afe --- /dev/null +++ b/Sources/API/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,98 @@ +CHANGELOG +========= + +6.2 +--- + + * Add `Finder::sortByExtension()` and `Finder::sortBySize()` + * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods + +6.0 +--- + + * Remove `Comparator::setTarget()` and `Comparator::setOperator()` + +5.4.0 +----- + + * Deprecate `Comparator::setTarget()` and `Comparator::setOperator()` + * Add a constructor to `Comparator` that allows setting target and operator + * Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified + * Add recursive .gitignore files support + +5.0.0 +----- + + * added `$useNaturalSort` argument to `Finder::sortByName()` + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/Sources/API/vendor/symfony/finder/Comparator/Comparator.php b/Sources/API/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000..bd68583 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * @author Fabien Potencier + */ +class Comparator +{ + private string $target; + private string $operator; + + public function __construct(string $target, string $operator = '==') + { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->target = $target; + $this->operator = $operator; + } + + /** + * Gets the target value. + */ + public function getTarget(): string + { + return $this->target; + } + + /** + * Gets the comparison operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Tests against the target. + */ + public function test(mixed $test): bool + { + return match ($this->operator) { + '>' => $test > $this->target, + '>=' => $test >= $this->target, + '<' => $test < $this->target, + '<=' => $test <= $this->target, + '!=' => $test != $this->target, + default => $test == $this->target, + }; + } +} diff --git a/Sources/API/vendor/symfony/finder/Comparator/DateComparator.php b/Sources/API/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000..e0c523d --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTimeImmutable($matches[2]); + $target = $date->format('U'); + } catch (\Exception) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = $matches[1] ?? '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + parent::__construct($target, $operator); + } +} diff --git a/Sources/API/vendor/symfony/finder/Comparator/NumberComparator.php b/Sources/API/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..ff85d96 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + parent::__construct($target, $matches[1] ?: '=='); + } +} diff --git a/Sources/API/vendor/symfony/finder/Exception/AccessDeniedException.php b/Sources/API/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/Sources/API/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/Sources/API/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000..c6cc0f2 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/Sources/API/vendor/symfony/finder/Finder.php b/Sources/API/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000..7baecdf --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Finder.php @@ -0,0 +1,846 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\LazyIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class Finder implements \IteratorAggregate, \Countable +{ + public const IGNORE_VCS_FILES = 1; + public const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_IGNORED_FILES = 4; + + private int $mode = 0; + private array $names = []; + private array $notNames = []; + private array $exclude = []; + private array $filters = []; + private array $depths = []; + private array $sizes = []; + private bool $followLinks = false; + private bool $reverseSorting = false; + private \Closure|int|false $sort = false; + private int $ignore = 0; + private array $dirs = []; + private array $dates = []; + private array $iterators = []; + private array $contains = []; + private array $notContains = []; + private array $paths = []; + private array $notPaths = []; + private bool $ignoreUnreadableDirs = false; + + private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + */ + public static function create(): static + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth(string|int|array $levels): static + { + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date(string|array $dates): static + { + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name(string|array $patterns): static + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName(string|array $patterns): static + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains(string|array $patterns): static + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains(string|array $patterns): static + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path(string|array $patterns): static + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath(string|array $patterns): static + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size(string|int|array $sizes): static + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude(string|array $dirs): static + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles(bool $ignoreDotFiles): static + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS(bool $ignoreVCS): static + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern(string|array $pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure): static + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by extension. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByExtension(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_EXTENSION; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by name case insensitive. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : Iterator\SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; + + return $this; + } + + /** + * Sorts files and directories by size. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortBySize(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_SIZE; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting(): static + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure): static + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks(): static + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @return $this + */ + public function ignoreUnreadableDirs(bool $ignore = true): static + { + $this->ignoreUnreadableDirs = $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in(string|array $dirs): static + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = [$this->normalizeDir($dir)]; + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { + sort($glob); + $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, ...$resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator(): \Iterator + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + $iterator = $this->searchInDirectory($this->dirs[0]); + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { + return $this->searchInDirectory($dir); + }))); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append(iterable $iterator): static + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif (is_iterable($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); + $it[$file->getPathname()] = $file; + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if any results were found. + */ + public function hasResults(): bool + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + */ + public function count(): int + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + $minDepth = 0; + $maxDepth = \PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + if ('/' === $dir) { + return $dir; + } + + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/Sources/API/vendor/symfony/finder/Gitignore.php b/Sources/API/vendor/symfony/finder/Gitignore.php new file mode 100644 index 0000000..070074b --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Michael Voříšek + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * Format specification: https://git-scm.com/docs/gitignore#_pattern_format + */ + public static function toRegex(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, false); + } + + public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, true); + } + + private static function buildRegex(string $gitignoreFileContent, bool $inverted): string + { + $gitignoreFileContent = preg_replace('~(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + */ + public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..82ee81d --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class CustomFilterIterator extends \FilterIterator +{ + private array $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..718d42b --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param DateComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..1cddb5f --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private int $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..efe9364 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + * @implements \RecursiveIterator + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + /** @var \Iterator */ + private \Iterator $iterator; + private bool $isRecursive; + private array $excludedDirs = []; + private ?string $excludedPattern = null; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || str_contains($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + public function hasChildren(): bool + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren(): self + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..2130378 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class FileTypeFilterIterator extends \FilterIterator +{ + public const ONLY_FILES = 1; + public const ONLY_DIRECTORIES = 2; + + private int $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..bdc71ff --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..05d9535 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + * + * @extends MultiplePcreFilterIterator + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/LazyIterator.php b/Sources/API/vendor/symfony/finder/Iterator/LazyIterator.php new file mode 100644 index 0000000..5b5806b --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/LazyIterator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * @author Jérémy Derussé + * + * @internal + */ +class LazyIterator implements \IteratorAggregate +{ + private \Closure $iteratorFactory; + + public function __construct(callable $iteratorFactory) + { + $this->iteratorFactory = $iteratorFactory(...); + } + + public function getIterator(): \Traversable + { + yield from ($this->iteratorFactory)(); + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..82a9df3 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected $matchRegexps = []; + protected $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + */ + protected function isAccepted(string $string): bool + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + */ + protected function isRegex(string $str): bool + { + $availableModifiers = 'imsxuADU'; + + if (\PHP_VERSION_ID >= 80200) { + $availableModifiers .= 'n'; + } + + if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + */ + abstract protected function toRegex(string $str): string; +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/PathFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..c6d5813 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/Sources/API/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..c321aee --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + * @extends \RecursiveDirectoryIterator + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + private bool $ignoreUnreadableDirs; + private ?bool $rewindable = null; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private string $rootPath; + private string $subPath; + private string $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + */ + public function current(): SplFileInfo + { + // the logic here avoids redoing the same work in all iterations + + if (!isset($this->subPath)) { + $this->subPath = $this->getSubPath(); + } + $subPathname = $this->subPath; + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + if ('/' !== $basePath = $this->rootPath) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + public function hasChildren(bool $allowLinks = false): bool + { + $hasChildren = parent::hasChildren($allowLinks); + + if (!$hasChildren || !$this->ignoreUnreadableDirs) { + return $hasChildren; + } + + try { + parent::getChildren(); + + return true; + } catch (\UnexpectedValueException) { + // If directory is unreadable and finder is set to ignore it, skip children + return false; + } + } + + /** + * @throws AccessDeniedException + */ + public function getChildren(): \RecursiveDirectoryIterator + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind(): void + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + */ + public function isRewindable(): bool + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..925830a --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param NumberComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/SortableIterator.php b/Sources/API/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..e8b5565 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class SortableIterator implements \IteratorAggregate +{ + public const SORT_BY_NONE = 0; + public const SORT_BY_NAME = 1; + public const SORT_BY_TYPE = 2; + public const SORT_BY_ACCESSED_TIME = 3; + public const SORT_BY_CHANGED_TIME = 4; + public const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NAME_NATURAL = 6; + public const SORT_BY_NAME_CASE_INSENSITIVE = 7; + public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; + public const SORT_BY_EXTENSION = 9; + public const SORT_BY_SIZE = 10; + + /** @var \Traversable */ + private \Traversable $iterator; + private \Closure|int $sort; + + /** + * @param \Traversable $iterator + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; + } elseif (self::SORT_BY_EXTENSION === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcmp($a->getExtension(), $b->getExtension()); + }; + } elseif (self::SORT_BY_SIZE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getSize() - $b->getSize()); + }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort(...); + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator(): \Traversable + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/Sources/API/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/Sources/API/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php new file mode 100644 index 0000000..29fc2d9 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Gitignore; + +/** + * @extends \FilterIterator + */ +final class VcsIgnoredFilterIterator extends \FilterIterator +{ + /** + * @var string + */ + private $baseDir; + + /** + * @var array + */ + private $gitignoreFilesCache = []; + + /** + * @var array + */ + private $ignoredPathsCache = []; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator, string $baseDir) + { + $this->baseDir = $this->normalizePath($baseDir); + + foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) { + if (@is_dir("{$parentDirectory}/.git")) { + $this->baseDir = $parentDirectory; + break; + } + } + + parent::__construct($iterator); + } + + public function accept(): bool + { + $file = $this->current(); + + $fileRealPath = $this->normalizePath($file->getRealPath()); + + return !$this->isIgnored($fileRealPath); + } + + private function isIgnored(string $fileRealPath): bool + { + if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) { + $fileRealPath .= '/'; + } + + if (isset($this->ignoredPathsCache[$fileRealPath])) { + return $this->ignoredPathsCache[$fileRealPath]; + } + + $ignored = false; + + foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + if ($this->isIgnored($parentDirectory)) { + // rules in ignored directories are ignored, no need to check further. + break; + } + + $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1); + + if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) { + continue; + } + + [$exclusionRegex, $inclusionRegex] = $regexps; + + if (preg_match($exclusionRegex, $fileRelativePath)) { + $ignored = true; + + continue; + } + + if (preg_match($inclusionRegex, $fileRelativePath)) { + $ignored = false; + } + } + + return $this->ignoredPathsCache[$fileRealPath] = $ignored; + } + + /** + * @return list + */ + private function parentDirectoriesUpwards(string $from): array + { + $parentDirectories = []; + + $parentDirectory = $from; + + while (true) { + $newParentDirectory = \dirname($parentDirectory); + + // dirname('/') = '/' + if ($newParentDirectory === $parentDirectory) { + break; + } + + $parentDirectories[] = $parentDirectory = $newParentDirectory; + } + + return $parentDirectories; + } + + private function parentDirectoriesUpTo(string $from, string $upTo): array + { + return array_filter( + $this->parentDirectoriesUpwards($from), + static function (string $directory) use ($upTo): bool { + return str_starts_with($directory, $upTo); + } + ); + } + + /** + * @return list + */ + private function parentDirectoriesDownwards(string $fileRealPath): array + { + return array_reverse( + $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) + ); + } + + /** + * @return array{0: string, 1: string}|null + */ + private function readGitignoreFile(string $path): ?array + { + if (\array_key_exists($path, $this->gitignoreFilesCache)) { + return $this->gitignoreFilesCache[$path]; + } + + if (!file_exists($path)) { + return $this->gitignoreFilesCache[$path] = null; + } + + if (!is_file($path) || !is_readable($path)) { + throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); + } + + $gitignoreFileContent = file_get_contents($path); + + return $this->gitignoreFilesCache[$path] = [ + Gitignore::toRegex($gitignoreFileContent), + Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent), + ]; + } + + private function normalizePath(string $path): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return str_replace('\\', '/', $path); + } + + return $path; + } +} diff --git a/Sources/API/vendor/symfony/finder/LICENSE b/Sources/API/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000..88bf75b --- /dev/null +++ b/Sources/API/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/finder/README.md b/Sources/API/vendor/symfony/finder/README.md new file mode 100644 index 0000000..22bdeb9 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/Sources/API/vendor/symfony/finder/SplFileInfo.php b/Sources/API/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000..867e8e8 --- /dev/null +++ b/Sources/API/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private string $relativePath; + private string $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct(string $file, string $relativePath, string $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + */ + public function getRelativePathname(): string + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @throws \RuntimeException + */ + public function getContents(): string + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $content = file_get_contents($this->getPathname()); + } finally { + restore_error_handler(); + } + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/Sources/API/vendor/symfony/finder/composer.json b/Sources/API/vendor/symfony/finder/composer.json new file mode 100644 index 0000000..06d129c --- /dev/null +++ b/Sources/API/vendor/symfony/finder/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Finds files and directories via an intuitive fluent interface", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/Sources/API/vendor/symfony/polyfill-ctype/Ctype.php b/Sources/API/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..ba75a2c --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/Sources/API/vendor/symfony/polyfill-ctype/LICENSE b/Sources/API/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/polyfill-ctype/README.md b/Sources/API/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..b144d03 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/Sources/API/vendor/symfony/polyfill-ctype/bootstrap.php b/Sources/API/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..d54524b --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/Sources/API/vendor/symfony/polyfill-ctype/bootstrap80.php b/Sources/API/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000..ab2f861 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/Sources/API/vendor/symfony/polyfill-ctype/composer.json b/Sources/API/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000..1b3efff --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/LICENSE b/Sources/API/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/polyfill-php80/Php80.php b/Sources/API/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/PhpToken.php b/Sources/API/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/README.md b/Sources/API/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..2b95542 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..bd1212f --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..7c62d75 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..01c6c6c --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..783dbc2 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/bootstrap.php b/Sources/API/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/Sources/API/vendor/symfony/polyfill-php80/composer.json b/Sources/API/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..bd9a326 --- /dev/null +++ b/Sources/API/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/Sources/API/vendor/symfony/yaml/CHANGELOG.md b/Sources/API/vendor/symfony/yaml/CHANGELOG.md new file mode 100644 index 0000000..50852cb --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/CHANGELOG.md @@ -0,0 +1,248 @@ +CHANGELOG +========= + +6.2 +--- + + * Add support for `!php/enum` and `!php/enum *->value` + * Deprecate the `!php/const:` tag in key which will be replaced by the `!php/const` tag (without the colon) since 3.4 + +6.1 +--- + + * In cases where it will likely improve readability, strings containing single quotes will be double-quoted + +5.4 +--- + + * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` + option to exclude one or more specific files from multiple file list + * Allow negatable for the parse tags option with `--no-parse-tags` + +5.3 +--- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + +5.1.0 +----- + + * Added support for parsing numbers prefixed with `0o` as octal numbers. + * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` + so that they are parsed as octal numbers. + + Before: + + ```yaml + Yaml::parse('072'); + ``` + + After: + + ```yaml + Yaml::parse('0o72'); + ``` + + * Added `yaml-lint` binary. + * Deprecated using the `!php/object` and `!php/const` tags without a value. + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/Sources/API/vendor/symfony/yaml/Command/LintCommand.php b/Sources/API/vendor/symfony/yaml/Command/LintCommand.php new file mode 100644 index 0000000..19a0af0 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Command/LintCommand.php @@ -0,0 +1,276 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] +class LintCommand extends Command +{ + private Parser $parser; + private ?string $format = null; + private bool $displayCorrectFiles; + private ?\Closure $directoryIteratorProvider; + private ?\Closure $isReadableProvider; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); + $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); + } + + protected function configure() + { + $this + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') + ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +You can also exclude one or more specific files: + + php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $excludes = $input->getOption('exclude'); + $this->format = $input->getOption('format'); + $flags = $input->getOption('parse-tags'); + + if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { + throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; + + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + if (!\in_array($file->getPathname(), $excludes, true)) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($io, $files), + 'json' => $this->displayJson($io, $files), + 'github' => $this->displayTxt($io, $files, true), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)), + }; + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + + if (str_contains($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && str_contains($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + return $this->parser ??= new Parser(); + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } +} diff --git a/Sources/API/vendor/symfony/yaml/Dumper.php b/Sources/API/vendor/symfony/yaml/Dumper.php new file mode 100644 index 0000000..c5024ff --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + */ + private int $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { + $output .= $prefix.Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value, ' ') ? (string) $this->indentation : ''; + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value->getValue(), ' ') ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/Sources/API/vendor/symfony/yaml/Escaper.php b/Sources/API/vendor/symfony/yaml/Escaper.php new file mode 100644 index 0000000..e8090d8 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/DumpException.php b/Sources/API/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 0000000..cce972f --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php b/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000..9091316 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/ParseException.php b/Sources/API/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 0000000..07c59b9 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private ?string $parsedFile; + private int $parsedLine; + private ?string $snippet; + private string $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + */ + public function getSnippet(): string + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + */ + public function setSnippet(string $snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + */ + public function getParsedFile(): string + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + */ + public function setParsedFile(string $parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + */ + public function getParsedLine(): int + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + */ + public function setParsedLine(int $parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if (str_ends_with($this->message, '.')) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php b/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 0000000..3f36b73 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Inline.php b/Sources/API/vendor/symfony/yaml/Inline.php new file mode 100644 index 0000000..02cbe8c --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Inline.php @@ -0,0 +1,816 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + * + * @internal + */ +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static int $parsedLineNumber = -1; + public static ?string $parsedFilename = null; + + private static bool $exceptionOnInvalidType = false; + private static bool $objectSupport = false; + private static bool $objectForMap = false; + private static bool $constantSupport = false; + + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array &$references = []): mixed + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump(mixed $value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', $value::class, $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + return self::dumpHashArray($value, $flags); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + if (!str_contains($repr, 'E')) { + $repr = $repr.'.0'; + } + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + $singleQuoted = Escaper::escapeWithSingleQuotes($value); + if (!str_contains($value, "'")) { + return $singleQuoted; + } + // Attempt double-quoting the string instead to see if it's more efficient. + $doubleQuoted = Escaper::escapeWithDoubleQuotes($value); + + return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted; + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + */ + public static function isHash(array|\ArrayObject|\stdClass $value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + return self::dumpHashArray($value, $flags); + } + + /** + * Dumps hash array to a YAML string. + * + * @param array|\ArrayObject|\stdClass $value The hash array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $flags): string + { + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null): mixed + { + if (\in_array($scalar[$i], ['"', "'"], true)) { + // quoted scalar + $isQuoted = true; + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + $isQuoted = false; + + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, -1); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException) { + // no, it's not + } + } + + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if ('!php/const' === $key || '!php/enum' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null): mixed + { + $isQuotedString = false; + $scalar = trim($scalar); + + if (str_starts_with($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + $scalarLower = strtolower($scalar); + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case str_starts_with($scalar, '!!str '): + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; + case str_starts_with($scalar, '! '): + return substr($scalar, 2); + case str_starts_with($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/enum'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + $enum = self::parseScalar(substr($scalar, 10), 0, null, $i, false); + if ($useValue = str_ends_with($enum, '->value')) { + $enum = substr($enum, 0, -7); + } + if (!\defined($enum)) { + throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $value = \constant($enum); + + if (!$value instanceof \UnitEnum) { + throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!$useValue) { + return $value; + } + if (!$value instanceof \BackedEnum) { + throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return $value->value; + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!!float '): + return (float) substr($scalar, 8); + case str_starts_with($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + } + + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + case preg_match('/^(?:\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): + $value = str_replace('_', '', $matches['value']); + + if ('-' === $scalar[0]) { + return -octdec($value); + } + + return octdec($value); + case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + + switch (true) { + case ctype_digit($scalar): + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC')); + + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError) { + // no-op + } + + return $time->format('U'); + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value): bool + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/Sources/API/vendor/symfony/yaml/LICENSE b/Sources/API/vendor/symfony/yaml/LICENSE new file mode 100644 index 0000000..88bf75b --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/yaml/Parser.php b/Sources/API/vendor/symfony/yaml/Parser.php new file mode 100644 index 0000000..146a645 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Parser.php @@ -0,0 +1,1246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + * + * @final + */ +class Parser +{ + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; + + private ?string $filename = null; + private int $offset = 0; + private int $numberOfParsedLines = 0; + private ?int $totalNumberOfLines = null; + private array $lines = []; + private int $currentLineNb = -1; + private string $currentLine = ''; + private array $refs = []; + private array $skippedLineNumbers = []; + private array $locallySkippedLineNumbers = []; + private array $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0): mixed + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0): mixed + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + try { + $data = $this->doParse($value, $flags); + } finally { + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->numberOfParsedLines = 0; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + + return $data; + } + + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->numberOfParsedLines = \count($this->lines); + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines ??= $this->numberOfParsedLines; + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if ( + isset($values['leadspaces']) + && ( + '!' === $values['value'][0] + || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + // @todo in 7.0 remove legacy "(?:!?!php/const:)?" + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) + && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if (str_starts_with($values['key'], '!php/const:')) { + trigger_deprecation('symfony/yaml', '6.2', 'YAML syntax for key "%s" is deprecated and replaced by "!php/const %s".', $values['key'], substr($values['key'], 11)); + } + + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + $trimmedLine = trim($line); + if ('#' === ($trimmedLine[0] ?? '')) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (str_contains($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === $trimmedLine) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== $trimmedLine && str_ends_with($line, '\\')) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== $trimmedLine) { + $value .= $trimmedLine; + } + + if ('' === $trimmedLine) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif (str_ends_with($line, '\\')) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + private function getCurrentLineIndentation(): int + { + if (' ' !== ($this->currentLine[0] ?? '')) { + return 0; + } + + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context): mixed + { + if (str_starts_with($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + + return $parsedValue; + default: + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && str_contains($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + private function isCurrentLineBlank(): bool + { + return '' === $this->currentLine || '' === trim($this->currentLine, ' '); + } + + private function isCurrentLineComment(): bool + { + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine; + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || str_starts_with($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + throw new ParseException(preg_last_error_msg()); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + + return substr($this->currentLine, $offset, $cursor - $offset); + } + + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; + } +} diff --git a/Sources/API/vendor/symfony/yaml/README.md b/Sources/API/vendor/symfony/yaml/README.md new file mode 100644 index 0000000..ac25024 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php b/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php new file mode 100644 index 0000000..c7946c2 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tag; + +/** + * @author Nicolas Grekas + * @author Guilhem N. + */ +final class TaggedValue +{ + private string $tag; + private mixed $value; + + public function __construct(string $tag, mixed $value) + { + $this->tag = $tag; + $this->value = $value; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/Sources/API/vendor/symfony/yaml/Unescaper.php b/Sources/API/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 0000000..2238210 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + */ + private function unescapeCharacter(string $value): string + { + return match ($value[1]) { + '0' => "\x0", + 'a' => "\x7", + 'b' => "\x8", + 't' => "\t", + "\t" => "\t", + 'n' => "\n", + 'v' => "\xB", + 'f' => "\xC", + 'r' => "\r", + 'e' => "\x1B", + ' ' => ' ', + '"' => '"', + '/' => '/', + '\\' => '\\', + // U+0085 NEXT LINE + 'N' => "\xC2\x85", + // U+00A0 NO-BREAK SPACE + '_' => "\xC2\xA0", + // U+2028 LINE SEPARATOR + 'L' => "\xE2\x80\xA8", + // U+2029 PARAGRAPH SEPARATOR + 'P' => "\xE2\x80\xA9", + 'x' => self::utf8chr(hexdec(substr($value, 2, 2))), + 'u' => self::utf8chr(hexdec(substr($value, 2, 4))), + 'U' => self::utf8chr(hexdec(substr($value, 2, 8))), + default => throw new ParseException(sprintf('Found unknown escape character "%s".', $value)), + }; + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/Sources/API/vendor/symfony/yaml/Yaml.php b/Sources/API/vendor/symfony/yaml/Yaml.php new file mode 100644 index 0000000..4978421 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @final + */ +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + */ + public static function dump(mixed $input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/Sources/API/vendor/symfony/yaml/composer.json b/Sources/API/vendor/symfony/yaml/composer.json new file mode 100644 index 0000000..839314b --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Loads and dumps YAML files", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "minimum-stability": "dev" +} diff --git a/Sources/API/vendor/zircote/swagger-php/LICENSE b/Sources/API/vendor/zircote/swagger-php/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Sources/API/vendor/zircote/swagger-php/README.md b/Sources/API/vendor/zircote/swagger-php/README.md new file mode 100644 index 0000000..dd58fba --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/README.md @@ -0,0 +1,155 @@ +[![Build Status](https://img.shields.io/github/workflow/status/zircote/swagger-php/build?style=flat-square)](https://github.com/zircote/swagger-php/actions?query=workflow:build) +[![Total Downloads](https://img.shields.io/packagist/dt/zircote/swagger-php.svg?style=flat-square)](https://packagist.org/packages/zircote/swagger-php) +[![License](https://img.shields.io/badge/license-Apache2.0-blue.svg?style=flat-square)](LICENSE) + +# swagger-php + +Generate interactive [OpenAPI](https://www.openapis.org) documentation for your RESTful API using [doctrine annotations](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). + +For a full list of supported annotations, please have look at the [`OpenApi\Annotations` namespace](src/Annotations) or the [documentation website](https://zircote.github.io/swagger-php/guide/annotations.html). + +## Features + +- Compatible with the OpenAPI **3.0** and **3.1** specification. +- Extracts information from code & existing phpdoc annotations. +- Command-line interface available. +- [Documentation site](https://zircote.github.io/swagger-php/) with a getting started guide. +- Exceptional error reporting (with hints, context) +- As of PHP 8.1 all annotations are also available as PHP attributes + +## OpenAPI version support + +`swagger-php` allows to generate specs either for **OpenAPI 3.0.0** or **OpenAPI 3.1.0**. +By default the spec will be in version `3.0.0`. The command line option `--version` may be used to change this +to `3.1.0`. + +Programmatically, the method `Generator::setVersion()` can be used to change the version. + +## Requirements + +`swagger-php` requires at least PHP 7.2 for annotations and PHP 8.1 for using attributes. + +## Installation (with [Composer](https://getcomposer.org)) + +```bash +composer require zircote/swagger-php +``` + +For cli usage from anywhere install swagger-php globally and make sure to place the `~/.composer/vendor/bin` directory in your PATH so the `openapi` executable can be located by your system. + +```bash +composer global require zircote/swagger-php +``` + +## Usage + +Add annotations to your php files. + +```php +/** + * @OA\Info(title="My First API", version="0.1") + */ + +/** + * @OA\Get( + * path="/api/resource.json", + * @OA\Response(response="200", description="An example resource") + * ) + */ +``` + +Visit the [Documentation website](https://zircote.github.io/swagger-php/) for the [Getting started guide](https://zircote.github.io/swagger-php/guide) or look at the [Examples directory](Examples/) for more examples. + +### Usage from php + +Generate always-up-to-date documentation. + +```php +toYaml(); +``` +Documentation of how to use the `Generator` class can be found in the [Generator reference](https://zircote.github.io/swagger-php/reference/generator). + +### Usage from the Command Line Interface + +The `openapi` command line interface can be used to generate the documentation to a static yaml/json file. + +```bash +./vendor/bin/openapi --help +``` +Starting with version 4 the default analyser used on the command line is the new `ReflectionAnalyser`. + +Using the `--legacy` flag (`-l`) the legacy `TokenAnalyser` can still be used. + +### Usage from the Deserializer + +Generate the OpenApi annotation object from a json string, which makes it easier to manipulate objects programmatically. + +```php +deserialize($jsonString, 'OpenApi\Annotations\OpenApi'); +echo $openapi->toJson(); +``` + +### Usage from [docker](https://docker.com) + +Generate the swagger documentation to a static json file. + +``` +docker run -v "$PWD":/app -it tico/swagger-php --help +``` + +## More on OpenApi & Swagger + +- https://swagger.io +- https://www.openapis.org +- [OpenApi Documentation](https://swagger.io/docs/) +- [OpenApi Specification](http://swagger.io/specification/) +- [Related projects](docs/related-projects.md) + +## Contributing + +Feel free to submit [Github Issues](https://github.com/zircote/swagger-php/issues) +or pull requests. + +The documentation website is build from the [docs](docs/) folder with [vitepress](https://vitepress.vuejs.org). + +Make sure pull requests pass [PHPUnit](https://phpunit.de/) +and [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (PSR-2) tests. + +### To run both unit tests and linting execute: +```bash +composer test +``` + +### To run static-analysis execute: +```bash +composer analyse +``` + +### Running unit tests only: +```bash +./bin/phpunit +``` + +### Regenerate reference markup docs +```bash +composer docs:gen +``` + +### Running linting only: +```bash +composer lint +``` + +### To make `php-cs-fixer` fix linting errors: +```bash +composer cs +``` diff --git a/Sources/API/vendor/zircote/swagger-php/composer.json b/Sources/API/vendor/zircote/swagger-php/composer.json new file mode 100644 index 0000000..a68b5f4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/composer.json @@ -0,0 +1,118 @@ +{ + "name": "zircote/swagger-php", + "type": "library", + "license": "Apache-2.0", + "bin": [ + "bin/openapi" + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "keywords": [ + "json", + "rest", + "api", + "service discovery" + ], + "homepage": "https://github.com/zircote/swagger-php/", + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "config": { + "bin-dir": "bin", + "optimize-autoloader": true, + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true + } + }, + "minimum-stability": "stable", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "require": { + "php": ">=7.2", + "ext-json": "*", + "doctrine/annotations": "^1.7", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "autoload-dev": { + "exclude-from-classmap": [ + "/tests/Fixtures" + ], + "psr-4": { + "OpenApi\\Tools\\": "tools/src/", + "OpenApi\\Tests\\": "tests/", + "AnotherNamespace\\": "tests/Fixtures/AnotherNamespace" + } + }, + "scripts-descriptions": { + "cs": "Fix all codestyle issues", + "lint": "Test codestyle", + "test": "Run all non-legacy and codestyle tests", + "testlegacy": "Run tests using the legacy TokenAnalyser", + "testall": "Run all tests (test + testlegacy)", + "analyse": "Run static analysis (phpstan/psalm)", + "spectral": "Run spectral lint over all .yaml files in the Examples folder", + "docs:refgen": "Rebuild the annotations/attributes reference markup files", + "docs:dev": "Run dev server for local development of gh-pages", + "docs:build": "Re-build static gh-pages" + }, + "scripts": { + "cs": "export XDEBUG_MODE=off && php-cs-fixer fix --allow-risky=yes", + "lint": "@cs --dry-run", + "test": [ + "export XDEBUG_MODE=off && phpunit", + "@lint" + ], + "testlegacy": "export XDEBUG_MODE=off && export PHPUNIT_ANALYSER=legacy && phpunit", + "testall": [ + "@test", + "@testlegacy" + ], + "analyse": [ + "export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G", + "export XDEBUG_MODE=off && psalm" + ], + "spectral": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done", + "docs:refgen": "php tools/refgen.php", + "docs:procgen": "php tools/procgen.php", + "docs:gen": [ + "@docs:refgen", + "@docs:procgen" + ], + "docs:dev": "cd docs && npm run dev", + "docs:build": [ + "@docs:gen", + "cd docs && npm run build" + ] + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php new file mode 100644 index 0000000..864d226 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php @@ -0,0 +1,18 @@ + top level annotations + */ + public function build(\Reflector $reflector, Context $context): array; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php new file mode 100644 index 0000000..a8a1d84 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php @@ -0,0 +1,142 @@ +generator = $generator; + } + + public function build(\Reflector $reflector, Context $context): array + { + if (\PHP_VERSION_ID < 80100 || !method_exists($reflector, 'getAttributes')) { + return []; + } + + if ($reflector instanceof \ReflectionProperty && method_exists($reflector, 'isPromoted') && $reflector->isPromoted()) { + // handled via __construct() parameter + return []; + } + + // no proper way to inject + Generator::$context = $context; + + /** @var OA\AbstractAnnotation[] $annotations */ + $annotations = []; + try { + foreach ($reflector->getAttributes() as $attribute) { + try { + $instance = $attribute->newInstance(); + if ($instance instanceof OA\AbstractAnnotation) { + $annotations[] = $instance; + } + } catch (\Error $e) { + $context->logger->debug('Could not instantiate attribute: ' . $e->getMessage(), ['exception' => $e]); + } + } + + if ($reflector instanceof \ReflectionMethod) { + // also look at parameter attributes + foreach ($reflector->getParameters() as $rp) { + foreach ([OAT\Property::class, OAT\Parameter::class, OAT\PathParameter::class] as $attributeName) { + foreach ($rp->getAttributes($attributeName) as $attribute) { + $instance = $attribute->newInstance(); + $type = (($rnt = $rp->getType()) && $rnt instanceof \ReflectionNamedType) ? $rnt->getName() : Generator::UNDEFINED; + $nullable = $rnt ? $rnt->allowsNull() : true; + + if ($instance instanceof OAT\Property) { + $instance->property = $rp->getName(); + if (Generator::isDefault($instance->type)) { + $instance->type = $type; + } + $instance->nullable = $nullable; + } else { + $instance->name = $rp->getName(); + $instance->required = !$nullable; + $context = new Context(['nested' => $this], $context); + $context->comment = null; + $instance->merge([new OA\Schema(['type' => $type, '_context' => $context])]); + } + $annotations[] = $instance; + } + } + } + + if (($rrt = $reflector->getReturnType()) && $rrt instanceof \ReflectionNamedType) { + foreach ($annotations as $annotation) { + if ($annotation instanceof OAT\Property && Generator::isDefault($annotation->type)) { + // pick up simple return types + $annotation->type = $rrt->getName(); + } + } + } + } + } finally { + Generator::$context = null; + } + + $annotations = array_values(array_filter($annotations, function ($a) { + return $a !== null && $a instanceof OA\AbstractAnnotation; + })); + + // merge backwards into parents... + $isParent = function (OA\AbstractAnnotation $annotation, OA\AbstractAnnotation $possibleParent): bool { + // regular annotation hierarchy + $explicitParent = null !== $possibleParent::matchNested(get_class($annotation)); + + $isParentAllowed = false; + // support Attachable subclasses + if ($isAttachable = $annotation instanceof OAT\Attachable) { + if (!$isParentAllowed = (null === $annotation->allowedParents())) { + // check for allowed parents + foreach ($annotation->allowedParents() as $allowedParent) { + if ($possibleParent instanceof $allowedParent) { + $isParentAllowed = true; + break; + } + } + } + } + + // Property can be nested... + return get_class($annotation) != get_class($possibleParent) + && ($explicitParent || ($isAttachable && $isParentAllowed)); + }; + + $annotationsWithoutParent = []; + foreach ($annotations as $index => $annotation) { + $mergedIntoParent = false; + + for ($ii = 0; $ii < count($annotations); ++$ii) { + if ($ii === $index) { + continue; + } + $possibleParent = $annotations[$ii]; + if ($isParent($annotation, $possibleParent)) { + $mergedIntoParent = true; // + $possibleParent->merge([$annotation]); + } + } + + if (!$mergedIntoParent) { + $annotationsWithoutParent[] = $annotation; + } + } + + return $annotationsWithoutParent; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php new file mode 100644 index 0000000..572c264 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php @@ -0,0 +1,53 @@ + $namespaces + * + * @return array + */ + public function scan(array $namespaces): array + { + $units = []; + if ($autoloader = $this->getComposerAutoloader()) { + foreach (array_keys($autoloader->getClassMap()) as $unit) { + foreach ($namespaces as $namespace) { + if (0 === strpos($unit, $namespace)) { + $units[] = $unit; + break; + } + } + } + } + + return $units; + } + + public static function getComposerAutoloader(): ?ClassLoader + { + foreach (spl_autoload_functions() as $fkt) { + if (is_array($fkt) && $fkt[0] instanceof ClassLoader) { + return $fkt[0]; + } + } + + return null; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php new file mode 100644 index 0000000..21d1768 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php @@ -0,0 +1,58 @@ +docBlockParser = $docBlockParser ?: new DocBlockParser(); + } + + public function setGenerator(Generator $generator): void + { + $this->generator = $generator; + + $this->docBlockParser->setAliases($generator->getAliases()); + } + + public function build(\Reflector $reflector, Context $context): array + { + $aliases = $this->generator ? $this->generator->getAliases() : []; + + if (method_exists($reflector, 'getShortName') && method_exists($reflector, 'getName')) { + $aliases[strtolower($reflector->getShortName())] = $reflector->getName(); + } + + if ($context->with('scanned')) { + $details = $context->scanned; + foreach ($details['uses'] as $alias => $name) { + $aliasKey = strtolower($alias); + if ($name != $alias && !array_key_exists($aliasKey, $aliases)) { + // real aliases only + $aliases[strtolower($alias)] = $name; + } + } + } + $this->docBlockParser->setAliases($aliases); + + if (method_exists($reflector, 'getDocComment') && ($comment = $reflector->getDocComment())) { + return $this->docBlockParser->fromComment($comment, $context); + } + + return []; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php new file mode 100644 index 0000000..fa89047 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php @@ -0,0 +1,83 @@ + $aliases + */ + public function __construct(array $aliases = []) + { + $docParser = new DocParser(); + $docParser->setIgnoreNotImportedAnnotations(true); + $docParser->setImports($aliases); + $this->docParser = $docParser; + } + + /** + * @param array $aliases + */ + public function setAliases(array $aliases): void + { + $this->docParser->setImports($aliases); + } + + /** + * Use doctrine to parse the comment block and return the detected annotations. + * + * @param string $comment a T_DOC_COMMENT + * @param Context $context + * + * @return array + */ + public function fromComment(string $comment, Context $context): array + { + $context->comment = $comment; + + try { + Generator::$context = $context; + if ($context->is('annotations') === false) { + $context->annotations = []; + } + + return $this->docParser->parse($comment, $context->getDebugLocation()); + } catch (\Exception $e) { + if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string) $context, '/') . '\.$/', $e->getMessage(), $matches)) { + $errorMessage = $matches[1]; + $errorPos = (int) $matches[2]; + $atPos = strpos($comment, '@'); + $context->line += substr_count($comment, "\n", 0, $atPos + $errorPos); + $lines = explode("\n", substr($comment, $atPos, $errorPos)); + $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 + $context->logger->error($errorMessage . ' in ' . $context, ['exception' => $e]); + } else { + $context->logger->error( + $e->getMessage() . ($context->filename ? ('; file=' . $context->filename) : ''), + ['exception' => $e] + ); + } + + return []; + } finally { + Generator::$context = null; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php new file mode 100644 index 0000000..bcd18f1 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php @@ -0,0 +1,190 @@ + $annotationFactories + */ + public function __construct(array $annotationFactories = []) + { + $this->annotationFactories = $annotationFactories; + if (!$this->annotationFactories) { + throw new \InvalidArgumentException('Need at least one annotation factory'); + } + } + + public function setGenerator(Generator $generator): void + { + $this->generator = $generator; + + foreach ($this->annotationFactories as $annotationFactory) { + $annotationFactory->setGenerator($generator); + } + } + + public function fromFile(string $filename, Context $context): Analysis + { + $scanner = new TokenScanner(); + $fileDetails = $scanner->scanFile($filename); + + $analysis = new Analysis([], $context); + foreach ($fileDetails as $fqdn => $details) { + $this->analyzeFqdn($fqdn, $analysis, $details); + } + + return $analysis; + } + + public function fromFqdn(string $fqdn, Analysis $analysis): Analysis + { + $fqdn = ltrim($fqdn, '\\'); + + $rc = new \ReflectionClass($fqdn); + if (!$filename = $rc->getFileName()) { + return $analysis; + } + + $scanner = new TokenScanner(); + $fileDetails = $scanner->scanFile($filename); + + $this->analyzeFqdn($fqdn, $analysis, $fileDetails[$fqdn]); + + return $analysis; + } + + protected function analyzeFqdn(string $fqdn, Analysis $analysis, array $details): Analysis + { + if (!class_exists($fqdn) && !interface_exists($fqdn) && !trait_exists($fqdn) && (!function_exists('enum_exists') || !enum_exists($fqdn))) { + $analysis->context->logger->warning('Skipping unknown ' . $fqdn); + + return $analysis; + } + + $rc = new \ReflectionClass($fqdn); + $contextType = $rc->isInterface() ? 'interface' : ($rc->isTrait() ? 'trait' : ((method_exists($rc, 'isEnum') && $rc->isEnum()) ? 'enum' : 'class')); + $context = new Context([ + $contextType => $rc->getShortName(), + 'namespace' => $rc->getNamespaceName() ?: null, + 'uses' => $details['uses'], + 'comment' => $rc->getDocComment() ?: null, + 'filename' => $rc->getFileName() ?: null, + 'line' => $rc->getStartLine(), + 'annotations' => [], + 'scanned' => $details, + ], $analysis->context); + + $definition = [ + $contextType => $rc->getShortName(), + 'extends' => null, + 'implements' => [], + 'traits' => [], + 'properties' => [], + 'methods' => [], + 'context' => $context, + ]; + $normaliseClass = function (string $name): string { + return '\\' . ltrim($name, '\\'); + }; + if ($parentClass = $rc->getParentClass()) { + $definition['extends'] = $normaliseClass($parentClass->getName()); + } + $definition[$contextType == 'class' ? 'implements' : 'extends'] = array_map($normaliseClass, $details['interfaces']); + $definition['traits'] = array_map($normaliseClass, $details['traits']); + + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($rc, $context), $context); + } + + foreach ($rc->getMethods() as $method) { + if (in_array($method->name, $details['methods'])) { + $definition['methods'][$method->getName()] = $ctx = new Context([ + 'method' => $method->getName(), + 'comment' => $method->getDocComment() ?: null, + 'filename' => $method->getFileName() ?: null, + 'line' => $method->getStartLine(), + 'annotations' => [], + ], $context); + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($method, $ctx), $ctx); + } + } + } + + foreach ($rc->getProperties() as $property) { + if (in_array($property->name, $details['properties'])) { + $definition['properties'][$property->getName()] = $ctx = new Context([ + 'property' => $property->getName(), + 'comment' => $property->getDocComment() ?: null, + 'annotations' => [], + ], $context); + if ($property->isStatic()) { + $ctx->static = true; + } + if (\PHP_VERSION_ID >= 70400 && ($type = $property->getType())) { + $ctx->nullable = $type->allowsNull(); + if ($type instanceof \ReflectionNamedType) { + $ctx->type = $type->getName(); + // Context::fullyQualifiedName(...) expects this + if (class_exists($absFqn = '\\' . $ctx->type)) { + $ctx->type = $absFqn; + } + } + } + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($property, $ctx), $ctx); + } + } + } + + foreach ($rc->getReflectionConstants() as $constant) { + foreach ($this->annotationFactories as $annotationFactory) { + $definition['constants'][$constant->getName()] = $ctx = new Context([ + 'constant' => $constant->getName(), + 'comment' => $constant->getDocComment() ?: null, + 'annotations' => [], + ], $context); + foreach ($annotationFactory->build($constant, $ctx) as $annotation) { + if ($annotation instanceof OA\Property) { + if (Generator::isDefault($annotation->property)) { + $annotation->property = $constant->getName(); + } + if (Generator::isDefault($annotation->const)) { + $annotation->const = $constant->getValue(); + } + $analysis->addAnnotation($annotation, $ctx); + } + } + } + } + + $addDefinition = 'add' . ucfirst($contextType) . 'Definition'; + $analysis->{$addDefinition}($definition); + + return $analysis; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php new file mode 100644 index 0000000..07e252c --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php @@ -0,0 +1,639 @@ +generator = $generator; + } + + /** + * Extract and process all doc-comments from a file. + * + * @param string $filename path to a php file + */ + public function fromFile(string $filename, Context $context): Analysis + { + if (function_exists('opcache_get_status') && function_exists('opcache_get_configuration')) { + if (empty($GLOBALS['openapi_opcache_warning'])) { + $GLOBALS['openapi_opcache_warning'] = true; + $status = opcache_get_status(); + $config = opcache_get_configuration(); + if (is_array($status) && $status['opcache_enabled'] && $config['directives']['opcache.save_comments'] == false) { + $context->logger->error("php.ini \"opcache.save_comments = 0\" interferes with extracting annotations.\n[LINK] https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments"); + } + } + } + $tokens = token_get_all(file_get_contents($filename)); + + return $this->fromTokens($tokens, new Context(['filename' => $filename], $context)); + } + + /** + * Extract and process all doc-comments from the contents. + * + * @param string $code PHP code. (including fromTokens($tokens, $context); + } + + /** + * Shared implementation for parseFile() & parseContents(). + * + * @param array $tokens The result of a token_get_all() + */ + protected function fromTokens(array $tokens, Context $parseContext): Analysis + { + $generator = $this->generator ?: new Generator(); + $analysis = new Analysis([], $parseContext); + $docBlockParser = new DocBlockParser($generator->getAliases()); + + reset($tokens); + $token = ''; + + $aliases = $generator->getAliases(); + + $parseContext->uses = []; + // default to parse context to start with + $schemaContext = $parseContext; + + $classDefinition = false; + $interfaceDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + $comment = false; + + $line = 0; + $lineOffset = $parseContext->line ?: 0; + + while ($token !== false) { + $previousToken = $token; + $token = $this->nextToken($tokens, $parseContext); + + if (is_array($token) === false) { + // Ignore tokens like "{", "}", etc + continue; + } + + if (defined('T_ATTRIBUTE') && $token[0] === T_ATTRIBUTE) { + // consume + $this->parseAttribute($tokens, $token, $parseContext); + continue; + } + + if ($token[0] === T_DOC_COMMENT) { + if ($comment) { + // 2 Doc-comments in succession? + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + } + $comment = $token[1]; + $line = $token[2] + $lineOffset; + continue; + } + + if (in_array($token[0], [T_ABSTRACT, T_FINAL])) { + // skip + $token = $this->nextToken($tokens, $parseContext); + } + + if ($token[0] === T_CLASS) { + // Doc-comment before a class? + if (is_array($previousToken) && $previousToken[0] === T_DOUBLE_COLON) { + // php 5.5 class name resolution (i.e. ClassName::class) + continue; + } + + $token = $this->nextToken($tokens, $parseContext); + + if (is_string($token) && ($token === '(' || $token === '{')) { + // php7 anonymous classes (i.e. new class() { public function foo() {} };) + continue; + } + + if (is_array($token) && ($token[1] === 'extends' || $token[1] === 'implements')) { + // php7 anonymous classes with extends (i.e. new class() extends { public function foo() {} };) + continue; + } + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $interfaceDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + + $schemaContext = new Context(['class' => $token[1], 'line' => $token[2]], $parseContext); + if ($classDefinition) { + $analysis->addClassDefinition($classDefinition); + } + $classDefinition = [ + 'class' => $token[1], + 'extends' => null, + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_EXTENDS) { + $schemaContext->extends = $this->parseNamespace($tokens, $token, $parseContext); + $classDefinition['extends'] = $schemaContext->fullyQualifiedName($schemaContext->extends); + } + + if ($token[0] === T_IMPLEMENTS) { + $schemaContext->implements = $this->parseNamespaceList($tokens, $token, $parseContext); + $classDefinition['implements'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->implements); + } + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-class and reset $schemaContext + } + + if ($token[0] === T_INTERFACE) { // Doc-comment before an interface? + $classDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['interface' => $token[1], 'line' => $token[2]], $parseContext); + if ($interfaceDefinition) { + $analysis->addInterfaceDefinition($interfaceDefinition); + } + $interfaceDefinition = [ + 'interface' => $token[1], + 'extends' => null, + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_EXTENDS) { + $schemaContext->extends = $this->parseNamespaceList($tokens, $token, $parseContext); + $interfaceDefinition['extends'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->extends); + } + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-interface and reset $schemaContext + } + + if ($token[0] === T_TRAIT) { + $classDefinition = false; + $interfaceDefinition = false; + $enumDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['trait' => $token[1], 'line' => $token[2]], $parseContext); + if ($traitDefinition) { + $analysis->addTraitDefinition($traitDefinition); + } + $traitDefinition = [ + 'trait' => $token[1], + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-trait and reset $schemaContext + } + + if (defined('T_ENUM') && $token[0] === T_ENUM) { + $classDefinition = false; + $interfaceDefinition = false; + $traitDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['enum' => $token[1], 'line' => $token[2]], $parseContext); + if ($enumDefinition) { + $analysis->addEnumDefinition($enumDefinition); + } + $enumDefinition = [ + 'enum' => $token[1], + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-trait and reset $schemaContext + } + + if ($token[0] === T_STATIC) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_VARIABLE) { + // static property + $propertyContext = new Context( + [ + 'property' => substr($token[1], 1), + 'static' => true, + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($traitDefinition) { + $traitDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext); + $comment = false; + } + continue; + } + } + + if (in_array($token[0], [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR])) { // Scope + [$type, $nullable, $token] = $this->parseTypeAndNextToken($tokens, $parseContext); + if ($token[0] === T_VARIABLE) { + // instance property + $propertyContext = new Context( + [ + 'property' => substr($token[1], 1), + 'type' => $type, + 'nullable' => $nullable, + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($traitDefinition) { + $traitDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext); + $comment = false; + } + } elseif ($token[0] === T_FUNCTION) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_STRING) { + $methodContext = new Context( + [ + 'method' => $token[1], + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['methods'][$token[1]] = $methodContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['methods'][$token[1]] = $methodContext; + } + if ($traitDefinition) { + $traitDefinition['methods'][$token[1]] = $methodContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $methodContext); + $comment = false; + } + } + } + continue; + } elseif ($token[0] === T_FUNCTION) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_STRING) { + $methodContext = new Context( + [ + 'method' => $token[1], + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['methods'][$token[1]] = $methodContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['methods'][$token[1]] = $methodContext; + } + if ($traitDefinition) { + $traitDefinition['methods'][$token[1]] = $methodContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $methodContext); + $comment = false; + } + } + } + + if (in_array($token[0], [T_NAMESPACE, T_USE]) === false) { + // Skip "use" & "namespace" to prevent "never imported" warnings) + if ($comment) { + // Not a doc-comment for a class, property or method? + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + $comment = false; + } + } + + if ($token[0] === T_NAMESPACE) { + $parseContext->namespace = $this->parseNamespace($tokens, $token, $parseContext); + $aliases['__NAMESPACE__'] = $parseContext->namespace; + $docBlockParser->setAliases($aliases); + continue; + } + + if ($token[0] === T_USE) { + $statements = $this->parseUseStatement($tokens, $token, $parseContext); + foreach ($statements as $alias => $target) { + if ($classDefinition) { + // class traits + $classDefinition['traits'][] = $schemaContext->fullyQualifiedName($target); + } elseif ($traitDefinition) { + // trait traits + $traitDefinition['traits'][] = $schemaContext->fullyQualifiedName($target); + } else { + // not a trait use + $parseContext->uses[$alias] = $target; + + $namespaces = $generator->getNamespaces(); + if (null === $namespaces) { + $aliases[strtolower($alias)] = $target; + } else { + foreach ($namespaces as $namespace) { + if (strcasecmp(substr($target . '\\', 0, strlen($namespace)), $namespace) === 0) { + $aliases[strtolower($alias)] = $target; + break; + } + } + } + $docBlockParser->setAliases($aliases); + } + } + } + } + + // cleanup final comment and definition + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + } + if ($classDefinition) { + $analysis->addClassDefinition($classDefinition); + } + if ($interfaceDefinition) { + $analysis->addInterfaceDefinition($interfaceDefinition); + } + if ($traitDefinition) { + $analysis->addTraitDefinition($traitDefinition); + } + if ($enumDefinition) { + $analysis->addEnumDefinition($enumDefinition); + } + + return $analysis; + } + + /** + * Parse comment and add annotations to analysis. + */ + private function analyseComment(Analysis $analysis, DocBlockParser $docBlockParser, string $comment, Context $context): void + { + $analysis->addAnnotations($docBlockParser->fromComment($comment, $context), $context); + } + + /** + * The next non-whitespace, non-comment token. + * + * + * @return array|string The next token (or false) + */ + private function nextToken(array &$tokens, Context $context) + { + while (true) { + $token = next($tokens); + if (is_array($token)) { + if ($token[0] === T_WHITESPACE) { + continue; + } + if ($token[0] === T_COMMENT) { + $pos = strpos($token[1], '@OA\\'); + if ($pos) { + $line = $context->line ? $context->line + $token[2] : $token[2]; + $commentContext = new Context(['line' => $line], $context); + $context->logger->warning('Annotations are only parsed inside `/**` DocBlocks, skipping ' . $commentContext); + } + continue; + } + } + + return $token; + } + } + + private function parseAttribute(array &$tokens, &$token, Context $parseContext): void + { + $nesting = 1; + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + if (!is_array($token) && '[' === $token) { + ++$nesting; + continue; + } + + if (!is_array($token) && ']' === $token) { + --$nesting; + if (!$nesting) { + break; + } + } + } + } + + /** + * @return int[] + */ + private function php8NamespaceToken(): array + { + return defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : []; + } + + /** + * Parse namespaced string. + * + * @param array|string $token + */ + private function parseNamespace(array &$tokens, &$token, Context $parseContext): string + { + $namespace = ''; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken()); + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + if (!in_array($token[0], $nsToken)) { + break; + } + $namespace .= $token[1]; + } + + return $namespace; + } + + /** + * Parse comma separated list of namespaced strings. + * + * @param array|string $token + */ + private function parseNamespaceList(array &$tokens, &$token, Context $parseContext): array + { + $namespaces = []; + while ($namespace = $this->parseNamespace($tokens, $token, $parseContext)) { + $namespaces[] = $namespace; + if ($token != ',') { + break; + } + } + + return $namespaces; + } + + /** + * Parse a use statement. + */ + private function parseUseStatement(array &$tokens, &$token, Context $parseContext): array + { + $normalizeAlias = function ($alias): string { + $alias = ltrim($alias, '\\'); + $elements = explode('\\', $alias); + + return array_pop($elements); + }; + + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken()); + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + $isNameToken = in_array($token[0], $nsToken); + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token === ',') { + $statements[$normalizeAlias($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[$normalizeAlias($alias)] = $class; + break; + } else { + break; + } + } + + return $statements; + } + + /** + * Parse type of variable (if it exists). + */ + private function parseTypeAndNextToken(array &$tokens, Context $parseContext): array + { + $type = Generator::UNDEFINED; + $nullable = false; + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_STATIC) { + $token = $this->nextToken($tokens, $parseContext); + } + + if ($token === '?') { // nullable type + $nullable = true; + $token = $this->nextToken($tokens, $parseContext); + } + + $qualifiedToken = array_merge([T_NS_SEPARATOR, T_STRING, T_ARRAY], $this->php8NamespaceToken()); + $typeToken = array_merge([T_STRING], $this->php8NamespaceToken()); + // drill down namespace segments to basename property type declaration + while (in_array($token[0], $qualifiedToken)) { + if (in_array($token[0], $typeToken)) { + $type = $token[1]; + } + $token = $this->nextToken($tokens, $parseContext); + } + + return [$type, $nullable, $token]; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php new file mode 100644 index 0000000..1df38be --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php @@ -0,0 +1,366 @@ +scanTokens(token_get_all(file_get_contents($filename))); + } + + /** + * Scan file for all classes, interfaces and traits. + * + * @return array> File details + */ + protected function scanTokens(array $tokens): array + { + $units = []; + $uses = []; + $isInterface = false; + $namespace = ''; + $currentName = null; + $unitLevel = 0; + $lastToken = null; + $stack = []; + + $initUnit = function ($uses): array { + return [ + 'uses' => $uses, + 'interfaces' => [], + 'traits' => [], + 'enums' => [], + 'methods' => [], + 'properties' => [], + ]; + }; + + while (false !== ($token = $this->nextToken($tokens))) { + if (!is_array($token)) { + switch ($token) { + case '{': + $stack[] = $token; + break; + case '}': + array_pop($stack); + if (count($stack) == $unitLevel) { + $currentName = null; + } + break; + } + continue; + } + + switch ($token[0]) { + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + $stack[] = $token[1]; + break; + + case T_NAMESPACE: + $namespace = $this->nextWord($tokens); + break; + + case T_USE: + if (!$stack) { + $uses = array_merge($uses, $this->parseFQNStatement($tokens, $token)); + } elseif ($currentName) { + $traits = $this->resolveFQN($this->parseFQNStatement($tokens, $token), $namespace, $uses); + $units[$currentName]['traits'] = array_merge($units[$currentName]['traits'], $traits); + } + break; + + case T_CLASS: + if ($currentName) { + break; + } + + if ($lastToken && is_array($lastToken) && $lastToken[0] === T_DOUBLE_COLON) { + // ::class + break; + } + + // class name + $token = $this->nextToken($tokens); + + // unless ... + if (is_string($token) && ($token === '(' || $token === '{')) { + // new class[()] { ... } + if ('{' == $token) { + prev($tokens); + } + break; + } elseif (is_array($token) && in_array($token[1], ['extends', 'implements'])) { + // new class[()] extends { ... } + break; + } + + $isInterface = false; + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $units[$currentName] = $initUnit($uses); + break; + + case T_INTERFACE: + if ($currentName) { + break; + } + + $isInterface = true; + $token = $this->nextToken($tokens); + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $units[$currentName] = $initUnit($uses); + break; + + case T_EXTENDS: + $fqns = $this->parseFQNStatement($tokens, $token); + if ($isInterface && $currentName) { + $units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses); + } + if (!is_array($token) || T_IMPLEMENTS !== $token[0]) { + break; + } + // no break + case T_IMPLEMENTS: + $fqns = $this->parseFQNStatement($tokens, $token); + if ($currentName) { + $units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses); + } + break; + + case T_FUNCTION: + $token = $this->nextToken($tokens); + if ((!is_array($token) && '&' == $token) + || (defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') && T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG == $token[0])) { + $token = $this->nextToken($tokens); + } + + if (($unitLevel + 1) == count($stack) && $currentName) { + $units[$currentName]['methods'][] = $token[1]; + if (!$isInterface) { + // more nesting + $units[$currentName]['properties'] = array_merge( + $units[$currentName]['properties'], + $this->parsePromotedProperties($tokens) + ); + $this->skipTo($tokens, '{', true); + } else { + // no function body + $this->skipTo($tokens, ';'); + } + } + break; + + case T_VARIABLE: + if (($unitLevel + 1) == count($stack) && $currentName) { + $units[$currentName]['properties'][] = substr($token[1], 1); + } + break; + default: + // handle trait here too to avoid duplication + if (T_TRAIT === $token[0] || (defined('T_ENUM') && T_ENUM === $token[0])) { + if ($currentName) { + break; + } + + $isInterface = false; + $token = $this->nextToken($tokens); + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $this->skipTo($tokens, '{', true); + $units[$currentName] = $initUnit($uses); + } + break; + } + $lastToken = $token; + } + + /* @phpstan-ignore-next-line */ + return $units; + } + + /** + * Get the next token that is not whitespace or comment. + * + * @return string|array + */ + protected function nextToken(array &$tokens) + { + $token = true; + while ($token) { + $token = next($tokens); + if (is_array($token)) { + if (in_array($token[0], [T_WHITESPACE, T_COMMENT])) { + continue; + } + } + + return $token; + } + + return $token; + } + + /** + * @return array + */ + protected function resolveFQN(array $names, string $namespace, array $uses): array + { + $resolve = function ($name) use ($namespace, $uses) { + if ('\\' == $name[0]) { + return substr($name, 1); + } + + if (array_key_exists($name, $uses)) { + return $uses[$name]; + } + + return $namespace . '\\' . $name; + }; + + return array_values(array_map($resolve, $names)); + } + + protected function skipTo(array &$tokens, string $char, bool $prev = false): void + { + while (false !== ($token = next($tokens))) { + if (is_string($token) && $token == $char) { + if ($prev) { + prev($tokens); + } + + break; + } + } + } + + /** + * Read next word. + * + * Skips leading whitespace. + */ + protected function nextWord(array &$tokens): string + { + $word = ''; + while (false !== ($token = next($tokens))) { + if (is_array($token)) { + if ($token[0] === T_WHITESPACE) { + if ($word) { + break; + } + continue; + } + $word .= $token[1]; + } + } + + return $word; + } + + /** + * Parse a use statement. + */ + protected function parseFQNStatement(array &$tokens, array &$token): array + { + $normalizeAlias = function ($alias): string { + $alias = ltrim($alias, '\\'); + $elements = explode('\\', $alias); + + return array_pop($elements); + }; + + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + $php8NSToken = defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : []; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $php8NSToken); + while ($token !== false) { + $token = $this->nextToken($tokens); + $isNameToken = in_array($token[0], $nsToken); + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token[0] === T_IMPLEMENTS) { + $statements[$normalizeAlias($alias)] = $class; + break; + } elseif ($token === ',') { + $statements[$normalizeAlias($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[$normalizeAlias($alias)] = $class; + break; + } elseif ($token === '{') { + $statements[$normalizeAlias($alias)] = $class; + prev($tokens); + break; + } else { + break; + } + } + + return $statements; + } + + protected function parsePromotedProperties(array &$tokens): array + { + $properties = []; + + $this->skipTo($tokens, '('); + $round = 1; + $promoted = false; + while (false !== ($token = $this->nextToken($tokens))) { + if (is_string($token)) { + switch ($token) { + case '(': + ++$round; + break; + case ')': + --$round; + if (0 == $round) { + return $properties; + } + } + } + if (is_array($token)) { + switch ($token[0]) { + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + $promoted = true; + break; + case T_VARIABLE: + if ($promoted) { + $properties[] = ltrim($token[1], '$'); + $promoted = false; + } + break; + } + } + } + + /* @phpstan-ignore-next-line */ + return $properties; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysis.php b/Sources/API/vendor/zircote/swagger-php/src/Analysis.php new file mode 100644 index 0000000..83a62a4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysis.php @@ -0,0 +1,439 @@ +annotations = new \SplObjectStorage(); + $this->context = $context; + + $this->addAnnotations($annotations, $context); + } + + public function addAnnotation(object $annotation, Context $context): void + { + if ($this->annotations->contains($annotation)) { + return; + } + + if ($annotation instanceof OA\OpenApi) { + $this->openapi = $this->openapi ?: $annotation; + } else { + if ($context->is('annotations') === false) { + $context->annotations = []; + } + + if (in_array($annotation, $context->annotations, true) === false) { + $context->annotations[] = $annotation; + } + } + $this->annotations->attach($annotation, $context); + $blacklist = property_exists($annotation, '_blacklist') ? $annotation::$_blacklist : []; + foreach ($annotation as $property => $value) { + if (in_array($property, $blacklist)) { + if ($property === '_unmerged') { + foreach ($value as $item) { + $this->addAnnotation($item, $context); + } + } + } elseif (is_array($value)) { + foreach ($value as $item) { + if ($item instanceof OA\AbstractAnnotation) { + $this->addAnnotation($item, $context); + } + } + } elseif ($value instanceof OA\AbstractAnnotation) { + $this->addAnnotation($value, $context); + } + } + } + + public function addAnnotations(array $annotations, Context $context): void + { + foreach ($annotations as $annotation) { + $this->addAnnotation($annotation, $context); + } + } + + public function addClassDefinition(array $definition): void + { + $class = $definition['context']->fullyQualifiedName($definition['class']); + $this->classes[$class] = $definition; + } + + public function addInterfaceDefinition(array $definition): void + { + $interface = $definition['context']->fullyQualifiedName($definition['interface']); + $this->interfaces[$interface] = $definition; + } + + public function addTraitDefinition(array $definition): void + { + $trait = $definition['context']->fullyQualifiedName($definition['trait']); + $this->traits[$trait] = $definition; + } + + public function addEnumDefinition(array $definition): void + { + $enum = $definition['context']->fullyQualifiedName($definition['enum']); + $this->enums[$enum] = $definition; + } + + public function addAnalysis(Analysis $analysis): void + { + foreach ($analysis->annotations as $annotation) { + $this->addAnnotation($annotation, $analysis->annotations[$annotation]); + } + $this->classes = array_merge($this->classes, $analysis->classes); + $this->interfaces = array_merge($this->interfaces, $analysis->interfaces); + $this->traits = array_merge($this->traits, $analysis->traits); + $this->enums = array_merge($this->enums, $analysis->enums); + if ($this->openapi === null && $analysis->openapi !== null) { + $this->openapi = $analysis->openapi; + } + } + + /** + * Get all subclasses of the given parent class. + * + * @param string $parent the parent class + * + * @return array map of class => definition pairs of sub-classes + */ + public function getSubClasses(string $parent): array + { + $definitions = []; + foreach ($this->classes as $class => $classDefinition) { + if ($classDefinition['extends'] === $parent) { + $definitions[$class] = $classDefinition; + $definitions = array_merge($definitions, $this->getSubClasses($class)); + } + } + + return $definitions; + } + + /** + * Get a list of all super classes for the given class. + * + * @param string $class the class name + * @param bool $direct flag to find only the actual class parents + * + * @return array map of class => definition pairs of parent classes + */ + public function getSuperClasses(string $class, bool $direct = false): array + { + $classDefinition = $this->classes[$class] ?? null; + if (!$classDefinition || empty($classDefinition['extends'])) { + // unknown class, or no inheritance + return []; + } + + $extends = $classDefinition['extends']; + $extendsDefinition = $this->classes[$extends] ?? null; + if (!$extendsDefinition) { + return []; + } + + $parentDetails = [$extends => $extendsDefinition]; + + if ($direct) { + return $parentDetails; + } + + return array_merge($parentDetails, $this->getSuperClasses($extends)); + } + + /** + * Get the list of interfaces used by the given class or by classes which it extends. + * + * @param string $class the class name + * @param bool $direct flag to find only the actual class interfaces + * + * @return array map of class => definition pairs of interfaces + */ + public function getInterfacesOfClass(string $class, bool $direct = false): array + { + $classes = $direct ? [] : array_keys($this->getSuperClasses($class)); + // add self + $classes[] = $class; + + $definitions = []; + foreach ($classes as $clazz) { + if (isset($this->classes[$clazz])) { + $definition = $this->classes[$clazz]; + if (isset($definition['implements'])) { + foreach ($definition['implements'] as $interface) { + if (array_key_exists($interface, $this->interfaces)) { + $definitions[$interface] = $this->interfaces[$interface]; + } + } + } + } + } + + if (!$direct) { + // expand recursively for interfaces extending other interfaces + $collect = function ($interfaces, $cb) use (&$definitions): void { + foreach ($interfaces as $interface) { + if (isset($this->interfaces[$interface]['extends'])) { + $cb($this->interfaces[$interface]['extends'], $cb); + foreach ($this->interfaces[$interface]['extends'] as $fqdn) { + $definitions[$fqdn] = $this->interfaces[$fqdn]; + } + } + } + }; + $collect(array_keys($definitions), $collect); + } + + return $definitions; + } + + /** + * Get the list of traits used by the given class/trait or by classes which it extends. + * + * @param string $source the source name + * @param bool $direct flag to find only the actual class traits + * + * @return array map of class => definition pairs of traits + */ + public function getTraitsOfClass(string $source, bool $direct = false): array + { + $sources = $direct ? [] : array_keys($this->getSuperClasses($source)); + // add self + $sources[] = $source; + + $definitions = []; + foreach ($sources as $sourze) { + if (isset($this->classes[$sourze]) || isset($this->traits[$sourze])) { + $definition = $this->classes[$sourze] ?? $this->traits[$sourze]; + if (isset($definition['traits'])) { + foreach ($definition['traits'] as $trait) { + if (array_key_exists($trait, $this->traits)) { + $definitions[$trait] = $this->traits[$trait]; + } + } + } + } + } + + if (!$direct) { + // expand recursively for traits using other traits + $collect = function ($traits, $cb) use (&$definitions): void { + foreach ($traits as $trait) { + if (isset($this->traits[$trait]['traits'])) { + $cb($this->traits[$trait]['traits'], $cb); + foreach ($this->traits[$trait]['traits'] as $fqdn) { + $definitions[$fqdn] = $this->traits[$fqdn]; + } + } + } + }; + $collect(array_keys($definitions), $collect); + } + + return $definitions; + } + + /** + * @param string|array $classes One ore more class names + * @param bool $strict in non-strict mode child classes are also detected + * + * @return OA\AbstractAnnotation[] + */ + public function getAnnotationsOfType($classes, bool $strict = false): array + { + $annotations = []; + if ($strict) { + foreach ((array) $classes as $class) { + foreach ($this->annotations as $annotation) { + if (get_class($annotation) === $class) { + $annotations[] = $annotation; + } + } + } + } else { + foreach ((array) $classes as $class) { + foreach ($this->annotations as $annotation) { + if ($annotation instanceof $class) { + $annotations[] = $annotation; + } + } + } + } + + return $annotations; + } + + /** + * @param string $fqdn the source class/interface/trait + */ + public function getSchemaForSource(string $fqdn): ?OA\Schema + { + $fqdn = '\\' . ltrim($fqdn, '\\'); + + foreach ([$this->classes, $this->interfaces, $this->traits, $this->enums] as $definitions) { + if (array_key_exists($fqdn, $definitions)) { + $definition = $definitions[$fqdn]; + if (is_iterable($definition['context']->annotations)) { + foreach (array_reverse($definition['context']->annotations) as $annotation) { + if (in_array(get_class($annotation), [OA\Schema::class, OAT\Schema::class]) && !$annotation->_context->is('generated')) { + return $annotation; + } + } + } + } + } + + return null; + } + + public function getContext(object $annotation): ?Context + { + if ($annotation instanceof OA\AbstractAnnotation) { + return $annotation->_context; + } + if ($this->annotations->contains($annotation) === false) { + throw new \Exception('Annotation not found'); + } + $context = $this->annotations[$annotation]; + if ($context instanceof Context) { + return $context; + } + + // Weird, did you use the addAnnotation/addAnnotations methods? + throw new \Exception('Annotation has no context'); + } + + /** + * Build an analysis with only the annotations that are merged into the OpenAPI annotation. + */ + public function merged(): Analysis + { + if ($this->openapi === null) { + throw new \Exception('No openapi target set. Run the MergeIntoOpenApi processor'); + } + $unmerged = $this->openapi->_unmerged; + $this->openapi->_unmerged = []; + $analysis = new Analysis([$this->openapi], $this->context); + $this->openapi->_unmerged = $unmerged; + + return $analysis; + } + + /** + * Analysis with only the annotations that not merged. + */ + public function unmerged(): Analysis + { + return $this->split()->unmerged; + } + + /** + * Split the annotation into two analysis. + * One with annotations that are merged and one with annotations that are not merged. + * + * @return object {merged: Analysis, unmerged: Analysis} + */ + public function split() + { + $result = new \stdClass(); + $result->merged = $this->merged(); + $result->unmerged = new Analysis([], $this->context); + foreach ($this->annotations as $annotation) { + if ($result->merged->annotations->contains($annotation) === false) { + $result->unmerged->annotations->attach($annotation, $this->annotations[$annotation]); + } + } + + return $result; + } + + /** + * Apply the processor(s). + * + * @param callable|callable[] $processors One or more processors + */ + public function process($processors = null): void + { + if (is_array($processors) === false && is_callable($processors)) { + $processors = [$processors]; + } + + foreach ($processors as $processor) { + $processor($this); + } + } + + public function validate(): bool + { + if ($this->openapi !== null) { + return $this->openapi->validate(); + } + + $this->context->logger->warning('No openapi target set. Run the MergeIntoOpenApi processor before validate()'); + + return false; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php new file mode 100644 index 0000000..de5407c --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php @@ -0,0 +1,716 @@ + + */ + public $x = Generator::UNDEFINED; + + /** + * Arbitrary attachables for this annotation. + * These will be ignored but can be used for custom processing. + * + * @var array + */ + public $attachables = Generator::UNDEFINED; + + /** + * @var Context|null + */ + public $_context = null; + + /** + * Annotations that couldn't be merged by mapping or postprocessing. + * + * @var array + */ + public $_unmerged = []; + + /** + * The properties which are required by [the spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md). + * + * @var array + */ + public static $_required = []; + + /** + * Specify the type of the property. + * + * Examples: + * 'name' => 'string' // a string + * 'required' => 'boolean', // true or false + * 'tags' => '[string]', // array containing strings + * 'in' => ["query", "header", "path", "formData", "body"] // must be one on these + * 'oneOf' => [Schema::class] // array of schema objects. + * + * @var array> + */ + public static $_types = []; + + /** + * Declarative mapping of Annotation types to properties. + * Examples: + * Info::clas => 'info', // Set @OA\Info annotation as the info property. + * Parameter::clas => ['parameters'], // Append @OA\Parameter annotations the parameters array. + * PathItem::clas => ['paths', 'path'], // Append @OA\PathItem annotations the paths array and use path as key. + * + * @var array,string|array> + */ + public static $_nested = []; + + /** + * Reverse mapping of $_nested with the allowed parent annotations. + * + * @var array> + */ + public static $_parents = []; + + /** + * List of properties are blacklisted from the JSON output. + * + * @var array + */ + public static $_blacklist = ['_context', '_unmerged', '_analysis', 'attachables']; + + public function __construct(array $properties) + { + if (isset($properties['_context'])) { + $this->_context = $properties['_context']; + unset($properties['_context']); + } elseif (Generator::$context) { + $this->_context = Generator::$context; + } else { + $this->_context = Context::detect(1); + } + + if ($this->_context->is('annotations') === false) { + $this->_context->annotations = []; + } + + $this->_context->annotations[] = $this; + $nestedContext = new Context(['nested' => $this], $this->_context); + foreach ($properties as $property => $value) { + if (property_exists($this, $property)) { + $this->{$property} = $value; + if (is_array($value)) { + foreach ($value as $key => $annotation) { + if ($annotation instanceof AbstractAnnotation) { + $this->{$property}[$key] = $this->nested($annotation, $nestedContext); + } + } + } + } elseif ($property !== 'value') { + $this->{$property} = $value; + } elseif (is_array($value)) { + $annotations = []; + foreach ($value as $annotation) { + if ($annotation instanceof AbstractAnnotation) { + $annotations[] = $annotation; + } else { + $this->_context->logger->warning('Unexpected field in ' . $this->identity() . ' in ' . $this->_context); + } + } + $this->merge($annotations); + } elseif (is_object($value)) { + $this->merge([$value]); + } else { + if ($value !== Generator::UNDEFINED) { + $this->_context->logger->warning('Unexpected parameter "' . $property . '" in ' . $this->identity()); + } + } + } + + if ($this instanceof OpenApi) { + if ($this->_context->root()->version) { + // override via `Generator::setVersion()` + $this->openapi = $this->_context->root()->version; + } else { + $this->_context->root()->version = $this->openapi; + } + } + } + + public function __get(string $property) + { + $properties = get_object_vars($this); + $this->_context->logger->warning('Property "' . $property . '" doesn\'t exist in a ' . $this->identity() . ', existing properties: "' . implode('", "', array_keys($properties)) . '" in ' . $this->_context); + } + + /** + * @param mixed $value + */ + public function __set(string $property, $value): void + { + $fields = get_object_vars($this); + foreach (static::$_blacklist as $_property) { + unset($fields[$_property]); + } + $this->_context->logger->warning('Unexpected field "' . $property . '" for ' . $this->identity() . ', expecting "' . implode('", "', array_keys($fields)) . '" in ' . $this->_context); + $this->{$property} = $value; + } + + /** + * Merge given annotations to their mapped properties configured in static::$_nested. + * + * Annotations that couldn't be merged are added to the _unmerged array. + * + * @param AbstractAnnotation[] $annotations + * @param bool $ignore Ignore unmerged annotations + * + * @return AbstractAnnotation[] The unmerged annotations + */ + public function merge(array $annotations, bool $ignore = false): array + { + $unmerged = []; + $nestedContext = new Context(['nested' => $this], $this->_context); + + foreach ($annotations as $annotation) { + $mapped = false; + if ($details = static::matchNested(get_class($annotation))) { + $property = $details->value; + if (is_array($property)) { + $property = $property[0]; + if (Generator::isDefault($this->{$property})) { + $this->{$property} = []; + } + $this->{$property}[] = $this->nested($annotation, $nestedContext); + $mapped = true; + } elseif (Generator::isDefault($this->{$property})) { + // ignore duplicate nested if only one expected + $this->{$property} = $this->nested($annotation, $nestedContext); + $mapped = true; + } + } + if (!$mapped) { + $unmerged[] = $annotation; + } + } + if (!$ignore) { + foreach ($unmerged as $annotation) { + $this->_unmerged[] = $this->nested($annotation, $nestedContext); + } + } + + return $unmerged; + } + + /** + * Merge the properties from the given object into this annotation. + * Prevents overwriting properties that are already configured. + * + * @param object $object + */ + public function mergeProperties($object): void + { + $defaultValues = get_class_vars(get_class($this)); + $currentValues = get_object_vars($this); + foreach ($object as $property => $value) { + if ($property === '_context') { + continue; + } + if ($currentValues[$property] === $defaultValues[$property]) { // Overwrite default values + $this->{$property} = $value; + continue; + } + if ($property === '_unmerged') { + $this->_unmerged = array_merge($this->_unmerged, $value); + continue; + } + if ($currentValues[$property] !== $value) { // New value is not the same? + if ($defaultValues[$property] === $value) { // but is the same as the default? + continue; // Keep current, no notice + } + $identity = method_exists($object, 'identity') ? $object->identity() : get_class($object); + $context1 = $this->_context; + $context2 = property_exists($object, '_context') ? $object->_context : 'unknown'; + if (is_object($this->{$property}) && $this->{$property} instanceof AbstractAnnotation) { + $context1 = $this->{$property}->_context; + } + $this->_context->logger->error('Multiple definitions for ' . $identity . '->' . $property . "\n Using: " . $context1 . "\n Skipping: " . $context2); + } + } + } + + /** + * Generate the documentation in YAML format. + */ + public function toYaml(?int $flags = null): string + { + if ($flags === null) { + $flags = Yaml::DUMP_OBJECT_AS_MAP ^ Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE; + } + + return Yaml::dump(json_decode($this->toJson(JSON_INVALID_UTF8_IGNORE)), 10, 2, $flags); + } + + /** + * Generate the documentation in JSON format. + */ + public function toJson(?int $flags = null): string + { + if ($flags === null) { + $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_IGNORE; + } + + return json_encode($this, $flags); + } + + public function __debugInfo() + { + $properties = []; + foreach (get_object_vars($this) as $property => $value) { + if (!Generator::isDefault($value)) { + $properties[$property] = $value; + } + } + + return $properties; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = new \stdClass(); + + // Strip undefined values. + foreach (get_object_vars($this) as $property => $value) { + if (!Generator::isDefault($value)) { + $data->{$property} = $value; + } + } + + // Strip properties that are for internal (swagger-php) use. + foreach (static::$_blacklist as $property) { + unset($data->{$property}); + } + + // Correct empty array to empty objects. + foreach (static::$_types as $property => $type) { + if ($type === 'object' && is_array($data->{$property}) && empty($data->{$property})) { + $data->{$property} = new \stdClass(); + } + } + + // Inject vendor properties. + unset($data->x); + if (is_array($this->x)) { + foreach ($this->x as $property => $value) { + $prefixed = 'x-' . $property; + $data->{$prefixed} = $value; + } + } + + // Map nested keys + foreach (static::$_nested as $nested) { + if (is_string($nested) || count($nested) === 1) { + continue; + } + $property = $nested[0]; + if (Generator::isDefault($this->{$property})) { + continue; + } + $keyField = $nested[1]; + $object = new \stdClass(); + foreach ($this->{$property} as $key => $item) { + if (is_numeric($key) === false && is_array($item)) { + $object->{$key} = $item; + } else { + $key = $item->{$keyField}; + if (!Generator::isDefault($key) && empty($object->{$key})) { + if ($item instanceof \JsonSerializable) { + $object->{$key} = $item->jsonSerialize(); + } else { + $object->{$key} = $item; + } + unset($object->{$key}->{$keyField}); + } + } + } + $data->{$property} = $object; + } + + // $ref + if (isset($data->ref)) { + // Only specific https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#reference-object + $ref = ['$ref' => $data->ref]; + if ($this->_context->version == OpenApi::VERSION_3_1_0) { + $defaultValues = get_class_vars(get_class($this)); + foreach (['summary', 'description'] as $prop) { + if (property_exists($this, $prop)) { + if ($this->{$prop} !== $defaultValues[$prop]) { + $ref[$prop] = $data->{$prop}; + } + } + } + } + $data = (object) $ref; + } + + if ($this->_context->version == OpenApi::VERSION_3_1_0) { + if (isset($data->nullable)) { + if (true === $data->nullable) { + $data->type = (array) $data->type; + $data->type[] = 'null'; + } + unset($data->nullable); + } + } + + return $data; + } + + /** + * Validate annotation tree, and log notices & warnings. + * + * @param array $stack the path of annotations above this annotation in the tree + * @param array $skip (prevent stack overflow, when traversing an infinite dependency graph) + * @param string $ref Current ref path? + * @param object $context a free-form context contains + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = true; + + // Report orphaned annotations + foreach ($this->_unmerged as $annotation) { + if (!is_object($annotation)) { + $this->_context->logger->warning('Unexpected type: "' . gettype($annotation) . '" in ' . $this->identity() . '->_unmerged, expecting a Annotation object'); + break; + } + + /** @var class-string $class */ + $class = get_class($annotation); + if ($details = static::matchNested($class)) { + $property = $details->value; + if (is_array($property)) { + $this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . ' multiple found, skipped: ' . $annotation->_context); + } else { + $this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . " multiple found in:\n Using: " . $this->{$property}->_context . "\n Skipped: " . $annotation->_context); + } + } elseif ($annotation instanceof AbstractAnnotation) { + $message = 'Unexpected ' . $annotation->identity(); + if ($class::$_parents) { + $message .= ', expected to be inside ' . implode(', ', Util::shorten($class::$_parents)); + } + $this->_context->logger->warning($message . ' in ' . $annotation->_context); + } + $valid = false; + } + + // Report conflicting key + foreach (static::$_nested as $annotationClass => $nested) { + if (is_string($nested) || count($nested) === 1) { + continue; + } + $property = $nested[0]; + if (Generator::isDefault($this->{$property})) { + continue; + } + $keys = []; + $keyField = $nested[1]; + foreach ($this->{$property} as $key => $item) { + if (is_array($item) && is_numeric($key) === false) { + $this->_context->logger->warning($this->identity() . '->' . $property . ' is an object literal, use nested ' . Util::shorten($annotationClass) . '() annotation(s) in ' . $this->_context); + $keys[$key] = $item; + } elseif (Generator::isDefault($item->{$keyField})) { + $this->_context->logger->error($item->identity() . ' is missing key-field: "' . $keyField . '" in ' . $item->_context); + } elseif (isset($keys[$item->{$keyField}])) { + $this->_context->logger->error('Multiple ' . $item->_identity([]) . ' with the same ' . $keyField . '="' . $item->{$keyField} . "\":\n " . $item->_context . "\n " . $keys[$item->{$keyField}]->_context); + } else { + $keys[$item->{$keyField}] = $item; + } + } + } + + if (property_exists($this, 'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) { + if (substr($this->ref, 0, 2) === '#/' && count($stack) > 0 && $stack[0] instanceof OpenApi) { + // Internal reference + try { + $stack[0]->ref($this->ref); + } catch (\Exception $e) { + $this->_context->logger->warning($e->getMessage() . ' for ' . $this->identity() . ' in ' . $this->_context, ['exception' => $e]); + } + } + } else { + // Report missing required fields (when not a $ref) + foreach (static::$_required as $property) { + if (Generator::isDefault($this->{$property})) { + $message = 'Missing required field "' . $property . '" for ' . $this->identity() . ' in ' . $this->_context; + foreach (static::$_nested as $class => $nested) { + $nestedProperty = is_array($nested) ? $nested[0] : $nested; + if ($property === $nestedProperty) { + if ($this instanceof OpenApi) { + $message = 'Required ' . Util::shorten($class) . '() not found'; + } elseif (is_array($nested)) { + $message = $this->identity() . ' requires at least one ' . Util::shorten($class) . '() in ' . $this->_context; + } else { + $message = $this->identity() . ' requires a ' . Util::shorten($class) . '() in ' . $this->_context; + } + break; + } + } + $this->_context->logger->warning($message); + } + } + } + + // Report invalid types + foreach (static::$_types as $property => $type) { + $value = $this->{$property}; + if (Generator::isDefault($value) || $value === null) { + continue; + } + if (is_string($type)) { + if ($this->validateType($type, $value) === false) { + $valid = false; + $this->_context->logger->warning($this->identity() . '->' . $property . ' is a "' . gettype($value) . '", expecting a "' . $type . '" in ' . $this->_context); + } + } elseif (is_array($type)) { // enum? + if (in_array($value, $type) === false) { + $this->_context->logger->warning($this->identity() . '->' . $property . ' "' . $value . '" is invalid, expecting "' . implode('", "', $type) . '" in ' . $this->_context); + } + } else { + throw new \Exception('Invalid ' . get_class($this) . '::$_types[' . $property . ']'); + } + } + $stack[] = $this; + + return self::_validate($this, $stack, $skip, $ref, $context) ? $valid : false; + } + + /** + * Recursively validate all annotation properties. + * + * @param array|object $fields + */ + private static function _validate($fields, array $stack, array $skip, string $baseRef, ?object $context): bool + { + $valid = true; + $blacklist = []; + if (is_object($fields)) { + if (in_array($fields, $skip, true)) { + return true; + } + $skip[] = $fields; + $blacklist = property_exists($fields, '_blacklist') ? $fields::$_blacklist : []; + } + + foreach ($fields as $field => $value) { + if ($value === null || is_scalar($value) || in_array($field, $blacklist)) { + continue; + } + $ref = $baseRef !== '' ? $baseRef . '/' . urlencode((string) $field) : urlencode((string) $field); + if (is_object($value)) { + if (method_exists($value, 'validate')) { + if (!$value->validate($stack, $skip, $ref, $context)) { + $valid = false; + } + } elseif (!self::_validate($value, $stack, $skip, $ref, $context)) { + $valid = false; + } + } elseif (is_array($value) && !self::_validate($value, $stack, $skip, $ref, $context)) { + $valid = false; + } + } + + return $valid; + } + + /** + * Return a identity for easy debugging. + * Example: "@OA\Get(path="/pets")". + */ + public function identity(): string + { + $class = get_class($this); + $properties = []; + /** @var class-string $parent */ + foreach (static::$_parents as $parent) { + foreach ($parent::$_nested as $annotationClass => $entry) { + if ($annotationClass === $class && is_array($entry) && !Generator::isDefault($this->{$entry[1]})) { + $properties[] = $entry[1]; + break 2; + } + } + } + + return $this->_identity($properties); + } + + /** + * Find matching nested details. + * + * @param string $class the class to match + * + * @return null|object key/value object or `null` + */ + public static function matchNested(string $class) + { + if (array_key_exists($class, static::$_nested)) { + return (object) ['key' => $class, 'value' => static::$_nested[$class]]; + } + + $parent = $class; + // only consider the immediate OpenApi parent + while (0 !== strpos($parent, 'OpenApi\\Annotations\\') && $parent = get_parent_class($parent)) { + if ($kvp = static::matchNested($parent)) { + return $kvp; + } + } + + return null; + } + + /** + * Helper for generating the identity(). + */ + protected function _identity(array $properties): string + { + $fields = []; + foreach ($properties as $property) { + $value = $this->{$property}; + if ($value !== null && !Generator::isDefault($value)) { + $fields[] = $property . '=' . (is_string($value) ? '"' . $value . '"' : $value); + } + } + + return Util::shorten(get_class($this)) . '(' . implode(',', $fields) . ')'; + } + + /** + * Validates the matching of the property value to a annotation type. + * + * @param string $type The annotations property type + * @param mixed $value The property value + */ + private function validateType(string $type, $value): bool + { + if (substr($type, 0, 1) === '[' && substr($type, -1) === ']') { // Array of a specified type? + if ($this->validateType('array', $value) === false) { + return false; + } + $itemType = substr($type, 1, -1); + foreach ($value as $i => $item) { + if ($this->validateType($itemType, $item) === false) { + return false; + } + } + + return true; + } + + if (is_subclass_of($type, AbstractAnnotation::class)) { + $type = 'object'; + } + + return $this->validateDefaultTypes($type, $value); + } + + /** + * Validates default Open Api types. + * + * @param string $type The property type + * @param mixed $value The value to validate + */ + private function validateDefaultTypes(string $type, $value): bool + { + switch ($type) { + case 'string': + return is_string($value); + case 'boolean': + return is_bool($value); + case 'integer': + return is_int($value); + case 'number': + return is_numeric($value); + case 'object': + return is_object($value); + case 'array': + return $this->validateArrayType($value); + case 'scheme': + return in_array($value, ['http', 'https', 'ws', 'wss'], true); + default: + throw new \Exception('Invalid type "' . $type . '"'); + } + } + + /** + * Validate array type. + * + * @param mixed $value + */ + private function validateArrayType($value): bool + { + if (is_array($value) === false) { + return false; + } + $count = 0; + foreach ($value as $i => $item) { + // not a array, but a hash/map + if ($count !== $i) { + return false; + } + $count++; + } + + return true; + } + + /** + * Wrap the context with a reference to the annotation it is nested in. + * + * @param AbstractAnnotation $annotation + * + * @return AbstractAnnotation + */ + protected function nested(AbstractAnnotation $annotation, Context $nestedContext) + { + if (property_exists($annotation, '_context') && $annotation->_context === $this->_context) { + $annotation->_context = $nestedContext; + } + + return $annotation; + } + + protected function combine(...$args): array + { + $combined = []; + foreach ($args as $arg) { + if (is_array($arg)) { + $combined = array_merge($combined, $arg); + } else { + $combined[] = $arg; + } + } + + return array_filter($combined, function ($value) { + return !Generator::isDefault($value) && $value !== null; + }); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php new file mode 100644 index 0000000..0cdf211 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php @@ -0,0 +1,38 @@ + 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php new file mode 100644 index 0000000..7a7ab99 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php @@ -0,0 +1,73 @@ +|null List of valid parent annotation classes. If `null`, the default nesting rules apply. + */ + public function allowedParents(): ?array + { + return null; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php new file mode 100644 index 0000000..e09a3d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php @@ -0,0 +1,146 @@ + + */ + public $schemas = Generator::UNDEFINED; + + /** + * Reusable Responses. + * + * @var Response[] + */ + public $responses = Generator::UNDEFINED; + + /** + * Reusable Parameters. + * + * @var Parameter[] + */ + public $parameters = Generator::UNDEFINED; + + /** + * Reusable Examples. + * + * @var Examples[] + */ + public $examples = Generator::UNDEFINED; + + /** + * Reusable Request Bodies. + * + * @var RequestBody[] + */ + public $requestBodies = Generator::UNDEFINED; + + /** + * Reusable Headers. + * + * @var Header[] + */ + public $headers = Generator::UNDEFINED; + + /** + * Reusable Security Schemes. + * + * @var SecurityScheme[] + */ + public $securitySchemes = Generator::UNDEFINED; + + /** + * Reusable Links. + * + * @var Link[] + */ + public $links = Generator::UNDEFINED; + + /** + * Reusable Callbacks. + * + * @var array + */ + public $callbacks = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Response::class => ['responses', 'response'], + Parameter::class => ['parameters', 'parameter'], + PathParameter::class => ['parameters', 'parameter'], + RequestBody::class => ['requestBodies', 'request'], + Examples::class => ['examples', 'example'], + Header::class => ['headers', 'header'], + SecurityScheme::class => ['securitySchemes', 'securityScheme'], + Link::class => ['links', 'link'], + Schema::class => ['schemas', 'schema'], + Attachable::class => ['attachables'], + ]; + + /** + * Generate a `#/components/...` reference for the given annotation. + * + * A `string` component value always assumes type `Schema`. + * + * @param AbstractAnnotation|string $component + */ + public static function ref($component, bool $encode = true): string + { + if ($component instanceof AbstractAnnotation) { + foreach (Components::$_nested as $type => $nested) { + // exclude attachables + if (2 == count($nested)) { + if ($component instanceof $type) { + $type = $nested[0]; + $name = $component->{$nested[1]}; + break; + } + } + } + } else { + $type = 'schemas'; + $name = $component; + } + + return self::COMPONENTS_PREFIX . $type . '/' . ($encode ? Util::refEncode((string) $name) : $name); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php new file mode 100644 index 0000000..1075627 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php @@ -0,0 +1,63 @@ + 'string', + 'url' => 'string', + 'email' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Info::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php new file mode 100644 index 0000000..8c3a54f --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php @@ -0,0 +1,25 @@ + 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Schema::class, + Property::class, + AdditionalProperties::class, + Items::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php new file mode 100644 index 0000000..c38a24b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php @@ -0,0 +1,96 @@ + 'string', + 'description' => 'string', + 'externalValue' => 'string', + ]; + + public static $_required = ['summary']; + + public static $_parents = [ + Components::class, + Parameter::class, + PathParameter::class, + MediaType::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php new file mode 100644 index 0000000..81bff45 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php @@ -0,0 +1,76 @@ + 'string', + 'url' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_required = ['url']; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + Tag::class, + Schema::class, + AdditionalProperties::class, + Property::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Delete::class, + Patch::class, + Head::class, + Options::class, + Trace::class, + Items::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php new file mode 100644 index 0000000..5409206 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php @@ -0,0 +1,106 @@ + ['implicit', 'password', 'authorizationCode', 'clientCredentials'], + 'refreshUrl' => 'string', + 'authorizationUrl' => 'string', + 'tokenUrl' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + SecurityScheme::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + if (is_array($this->scopes) && empty($this->scopes)) { + $this->scopes = new \stdClass(); + } + + return parent::jsonSerialize(); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php new file mode 100644 index 0000000..c0ff442 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php @@ -0,0 +1,25 @@ + 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Response::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php new file mode 100644 index 0000000..db535b3 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php @@ -0,0 +1,98 @@ + 'string', + 'version' => 'string', + 'description' => 'string', + 'termsOfService' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Contact::class => 'contact', + License::class => 'license', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php new file mode 100644 index 0000000..c46ec94 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php @@ -0,0 +1,62 @@ + 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Property::class, + AdditionalProperties::class, + Schema::class, + JsonContent::class, + XmlContent::class, + Items::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + $parent = end($stack); + if ($parent instanceof Schema && $parent->type !== 'array') { + $this->_context->logger->warning('@OA\\Items() parent type must be "array" in ' . $this->_context); + $valid = false; + } + + // @todo Additional validation when used inside a Header or Parameter context. + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php new file mode 100644 index 0000000..348f8d6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php @@ -0,0 +1,42 @@ +`'application/json'` will be generated. + * + * @Annotation + */ +class JsonContent extends Schema +{ + /** + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = []; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + AdditionalProperties::class => 'additionalProperties', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php new file mode 100644 index 0000000..ac04bc6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php @@ -0,0 +1,102 @@ + 'string', + 'identifier' => 'string', + 'url' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_required = ['name']; + + /** + * @inheritdoc + */ + public static $_parents = [ + Info::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) { + unset($data->identifier); + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + $valid = parent::validate($stack, $skip, $ref, $context); + + if ($this->_context->isVersion(OpenApi::VERSION_3_1_0)) { + if (!Generator::isDefault($this->url) && $this->identifier !== Generator::UNDEFINED) { + $this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive'); + $valid = false; + } + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php new file mode 100644 index 0000000..b224a55 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php @@ -0,0 +1,114 @@ +links array. + * + * @var string + */ + public $link = Generator::UNDEFINED; + + /** + * A relative or absolute reference to an OA operation. + * + * This field is mutually exclusive of the operationId field, and must point to an Operation object. + * + * Relative values may be used to locate an existing Operation object in the OpenAPI definition. + * + * @var string + */ + public $operationRef = Generator::UNDEFINED; + + /** + * The name of an existing, resolvable OA operation, as defined with a unique operationId. + * + * This field is mutually exclusive of the operationRef field. + * + * @var string + */ + public $operationId = Generator::UNDEFINED; + + /** + * A map representing parameters to pass to an operation as specified with operationId or identified via + * operationRef. + * + * The key is the parameter name to be used, whereas the value can be a constant or an expression to + * be evaluated and passed to the linked operation. + * The parameter name can be qualified using the parameter location [{in}.]{name} for operations + * that use the same parameter name in different locations (e.g. path.id). + * + * @var array + */ + public $parameters = Generator::UNDEFINED; + + /** + * A literal value or {expression} to use as a request body when calling the target operation. + * + * @var mixed + */ + public $requestBody = Generator::UNDEFINED; + + /** + * A description of the link. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * A server object to be used by the target operation. + * + * @var Server + */ + public $server = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_nested = [ + Server::class => 'server', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Response::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php new file mode 100644 index 0000000..624fe69 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php @@ -0,0 +1,88 @@ +content array. + * + * @var string + */ + public $mediaType = Generator::UNDEFINED; + + /** + * The schema defining the type used for the request body. + * + * @var Schema + */ + public $schema = Generator::UNDEFINED; + + /** + * Example of the media type. + * + * The example object should be in the correct format as specified by the media type. + * The example object is mutually exclusive of the examples object. + * + * Furthermore, if referencing a schema which contains an example, + * the example value shall override the example provided by the schema. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Examples of the media type. + * + * Each example object should match the media type and specified schema if present. + * The examples object is mutually exclusive of the example object. + * + * Furthermore, if referencing a schema which contains an example, + * the examples value shall override the example provided by the schema. + * + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * A map between a property name and its encoding information. + * + * The key, being the property name, must exist in the schema as a property. + * + * The encoding object shall only apply to requestBody objects when the media type is multipart or + * application/x-www-form-urlencoded. + * + * @var array + */ + public $encoding = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Response::class, + RequestBody::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php new file mode 100644 index 0000000..7477527 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php @@ -0,0 +1,233 @@ +@Server objects, which provide connectivity information to a target server. + * + * If not provided, or is an empty array, the default value would be a Server Object with an url value of /. + * + * @var Server[] + */ + public $servers = Generator::UNDEFINED; + + /** + * The available paths and operations for the API. + * + * @var PathItem[] + */ + public $paths = Generator::UNDEFINED; + + /** + * An element to hold various components for the specification. + * + * @var Components + */ + public $components = Generator::UNDEFINED; + + /** + * A declaration of which security mechanisms can be used across the API. + * + * The list of values includes alternative security requirement objects that can be used. + * Only one of the security requirement objects need to be satisfied to authorize a request. + * Individual operations can override this definition. + * To make security optional, an empty security requirement `({})` can be included in the array. + * + * @var array + */ + public $security = Generator::UNDEFINED; + + /** + * A list of tags used by the specification with additional metadata. + * + * The order of the tags can be used to reflect on their order by the parsing tools. + * Not all tags that are used by the Operation Object must be declared. + * The tags that are not declared may be organized randomly or based on the tools' logic. + * Each tag name in the list must be unique. + * + * @var Tag[] + */ + public $tags = Generator::UNDEFINED; + + /** + * Additional external documentation. + * + * @var ExternalDocumentation + */ + public $externalDocs = Generator::UNDEFINED; + + /** + * @var Analysis + */ + public $_analysis = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['openapi', 'info', 'paths']; + + /** + * @inheritdoc + */ + public static $_nested = [ + Info::class => 'info', + Server::class => ['servers'], + PathItem::class => ['paths', 'path'], + Components::class => 'components', + Tag::class => ['tags'], + ExternalDocumentation::class => 'externalDocs', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_types = []; + + /** + * @inheritdoc + */ + public function validate(array $stack = null, array $skip = null, string $ref = '', $context = null): bool + { + if ($stack !== null || $skip !== null || $ref !== '') { + $this->_context->logger->warning('Nested validation for ' . $this->identity() . ' not allowed'); + + return false; + } + + if (!in_array($this->openapi, self::SUPPORTED_VERSIONS)) { + $this->_context->logger->warning('Unsupported OpenAPI version "' . $this->openapi . '". Allowed versions are: ' . implode(', ', self::SUPPORTED_VERSIONS)); + + return false; + } + + return parent::validate([], [], '#', new \stdClass()); + } + + /** + * Save the OpenAPI documentation to a file. + */ + public function saveAs(string $filename, string $format = 'auto'): void + { + if ($format === 'auto') { + $format = strtolower(substr($filename, -5)) === '.json' ? 'json' : 'yaml'; + } + + if (strtolower($format) === 'json') { + $content = $this->toJson(); + } else { + $content = $this->toYaml(); + } + + if (file_put_contents($filename, $content) === false) { + throw new \Exception('Failed to saveAs("' . $filename . '", "' . $format . '")'); + } + } + + /** + * Look up an annotation with a $ref url. + * + * @param string $ref The $ref value, for example: "#/components/schemas/Product" + */ + public function ref(string $ref) + { + if (substr($ref, 0, 2) !== '#/') { + // @todo Add support for external (http) refs? + throw new \Exception('Unsupported $ref "' . $ref . '", it should start with "#/"'); + } + + return $this->resolveRef($ref, '#/', $this, []); + } + + /** + * Recursive helper for ref(). + * + * @param array|AbstractAnnotation $container + */ + private static function resolveRef(string $ref, string $resolved, $container, array $mapping) + { + if ($ref === $resolved) { + return $container; + } + $path = substr($ref, strlen($resolved)); + $slash = strpos($path, '/'); + + $subpath = $slash === false ? $path : substr($path, 0, $slash); + $property = Util::refDecode($subpath); + $unresolved = $slash === false ? $resolved . $subpath : $resolved . $subpath . '/'; + + if (is_object($container)) { + if (property_exists($container, $property) === false) { + throw new \Exception('$ref "' . $ref . '" not found'); + } + if ($slash === false) { + return $container->{$property}; + } + $mapping = []; + if ($container instanceof AbstractAnnotation) { + foreach ($container::$_nested as $nestedClass => $nested) { + if (is_string($nested) === false && count($nested) === 2 && $nested[0] === $property) { + $mapping[$nestedClass] = $nested[1]; + } + } + } + + return self::resolveRef($ref, $unresolved, $container->{$property}, $mapping); + } elseif (is_array($container)) { + if (array_key_exists($property, $container)) { + return self::resolveRef($ref, $unresolved, $container[$property], []); + } + foreach ($mapping as $nestedClass => $keyField) { + foreach ($container as $key => $item) { + if (is_numeric($key) && is_object($item) && $item instanceof $nestedClass && (string) $item->{$keyField} === $property) { + return self::resolveRef($ref, $unresolved, $item, []); + } + } + } + } + + throw new \Exception('$ref "' . $unresolved . '" not found'); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php new file mode 100644 index 0000000..e4d1e9a --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php @@ -0,0 +1,248 @@ + 'string', + 'method' => 'string', + 'tags' => '[string]', + 'summary' => 'string', + 'description' => 'string', + 'deprecated' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Parameter::class => ['parameters'], + PathParameter::class => ['parameters'], + Response::class => ['responses', 'response'], + ExternalDocumentation::class => 'externalDocs', + Server::class => ['servers'], + RequestBody::class => 'requestBody', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + unset($data->method); + unset($data->path); + + // ensure security elements are object + if (isset($data->security) && is_array($data->security)) { + foreach ($data->security as $key => $scheme) { + $data->security[$key] = (object) $scheme; + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + if (!Generator::isDefault($this->responses)) { + foreach ($this->responses as $response) { + if (!Generator::isDefault($response->response) && $response->response !== 'default' && preg_match('/^([12345]{1}[0-9]{2})|([12345]{1}XX)$/', (string) $response->response) === 0) { + $this->_context->logger->warning('Invalid value "' . $response->response . '" for ' . $response->_identity([]) . '->response, expecting "default", a HTTP Status Code or HTTP Status Code range definition in ' . $response->_context); + $valid = false; + } + } + } + + if (is_object($context) && !Generator::isDefault($this->operationId)) { + if (!property_exists($context, 'operationIds')) { + $context->operationIds = []; + } + + if (in_array($this->operationId, $context->operationIds)) { + $this->_context->logger->warning('operationId must be unique. Duplicate value found: "' . $this->operationId . '"'); + $valid = false; + } + + $context->operationIds[] = $this->operationId; + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php new file mode 100644 index 0000000..312505e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php @@ -0,0 +1,25 @@ +Components::parameters or PathItem::parameters array. + * + * @var string + */ + public $parameter = Generator::UNDEFINED; + + /** + * The (case-sensitive) name of the parameter. + * + * If in is "path", the name field must correspond to the associated path segment from the path field in the Paths Object. + * + * If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition shall be ignored. + * For all other cases, the name corresponds to the parameter name used by the in property. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * The location of the parameter. + * + * Possible values are "query", "header", "path" or "cookie". + * + * @var string + */ + public $in = Generator::UNDEFINED; + + /** + * A brief description of the parameter. + * + * This could contain examples of use. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * Determines whether this parameter is mandatory. + * + * If the parameter location is "path", this property is required and its value must be true. + * Otherwise, the property may be included and its default value is false. + * + * @var bool + */ + public $required = Generator::UNDEFINED; + + /** + * Specifies that a parameter is deprecated and should be transitioned out of usage. + * + * @var bool + */ + public $deprecated = Generator::UNDEFINED; + + /** + * Sets the ability to pass empty-valued parameters. + * + * This is valid only for query parameters and allows sending a parameter with an empty value. + * + * Default value is false. + * + * If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue shall be ignored. + * + * @var bool + */ + public $allowEmptyValue = Generator::UNDEFINED; + + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value. + * + * Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form. + * + * @var string + */ + public $style = Generator::UNDEFINED; + + /** + * When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map. + * + * For other types of parameters this property has no effect. + * + * When style is form, the default value is true. + * For all other styles, the default value is false. + * + * @var bool + */ + public $explode = Generator::UNDEFINED; + + /** + * Determines whether the parameter value should allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding. + * + * This property only applies to parameters with an in value of query. + * + * The default value is false. + * + * @var bool + */ + public $allowReserved = Generator::UNDEFINED; + + /** + * The schema defining the type used for the parameter. + * + * @var Schema + */ + public $schema = Generator::UNDEFINED; + + /** + * Example of the media type. + * + * The example should match the specified schema and encoding properties if present. + * The example object is mutually exclusive of the examples object. + * Furthermore, if referencing a schema which contains an example, the example value shall override the example provided by the schema. + * To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Examples of the media type. + * + * Each example should contain a value in the correct format as specified in the parameter encoding. + * The examples object is mutually exclusive of the example object. + * Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema. + * + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * A map containing the representations for the parameter. + * + * The key is the media type and the value describes it. + * The map must only contain one entry. + * + * @var array|JsonContent|XmlContent|Attachable + */ + public $content = Generator::UNDEFINED; + + /** + * Path-style parameters defined by RFC6570. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.7) + */ + public $matrix = Generator::UNDEFINED; + + /** + * Label style parameters defined by RFC6570. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.5) + */ + public $label = Generator::UNDEFINED; + + /** + * Form style parameters defined by RFC6570. + * + * This option replaces collectionFormat with a csv (when explode is false) or multi (when explode is true) value from OpenAPI 2.0. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.8) + */ + public $form = Generator::UNDEFINED; + + /** + * Simple style parameters defined by RFC6570. + * + * This option replaces collectionFormat with a csv value from OpenAPI 2.0. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.2) + * + * @var array + */ + public $simple = Generator::UNDEFINED; + + /** + * Space separated array values. + * + * This option replaces collectionFormat equal to ssv from OpenAPI 2.0. + * + * @var array + */ + public $spaceDelimited = Generator::UNDEFINED; + + /** + * Pipe separated array values. + * + * This option replaces collectionFormat equal to pipes from OpenAPI 2.0. + * + * @var array + */ + public $pipeDelimited = Generator::UNDEFINED; + + /** + * Provides a simple way of rendering nested objects using form parameters. + */ + public $deepObject = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['name', 'in']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'name' => 'string', + 'in' => ['query', 'header', 'path', 'cookie'], + 'description' => 'string', + 'style' => ['matrix', 'label', 'form', 'simple', 'spaceDelimited', 'pipeDelimited', 'deepObject'], + 'required' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + PathItem::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Delete::class, + Patch::class, + Head::class, + Options::class, + Trace::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + if (Generator::isDefault($this->ref)) { + if ($this->in === 'body') { + if (Generator::isDefault($this->schema)) { + $this->_context->logger->warning('Field "schema" is required when ' . $this->identity() . ' is in "' . $this->in . '" in ' . $this->_context); + $valid = false; + } + } + } + + return $valid; + } + + /** + * @inheritdoc + */ + public function identity(): string + { + return parent::_identity(['name', 'in']); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php new file mode 100644 index 0000000..7972f3b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php @@ -0,0 +1,25 @@ +paths array). + * + * @var string + */ + public $path = Generator::UNDEFINED; + + /** + * An optional, string summary, intended to apply to all operations in this path. + * + * @var string + */ + public $summary = Generator::UNDEFINED; + + /** + * A definition of a GET operation on this path. + * + * @var Get + */ + public $get = Generator::UNDEFINED; + + /** + * A definition of a PUT operation on this path. + * + * @var Put + */ + public $put = Generator::UNDEFINED; + + /** + * A definition of a POST operation on this path. + * + * @var Post + */ + public $post = Generator::UNDEFINED; + + /** + * A definition of a DELETE operation on this path. + * + * @var Delete + */ + public $delete = Generator::UNDEFINED; + + /** + * A definition of a OPTIONS operation on this path. + * + * @var Options + */ + public $options = Generator::UNDEFINED; + + /** + * A definition of a HEAD operation on this path. + * + * @var Head + */ + public $head = Generator::UNDEFINED; + + /** + * A definition of a PATCH operation on this path. + * + * @var Patch + */ + public $patch = Generator::UNDEFINED; + + /** + * A definition of a TRACE operation on this path. + * + * @var Trace + */ + public $trace = Generator::UNDEFINED; + + /** + * An alternative server array to service all operations in this path. + * + * @var Server[] + */ + public $servers = Generator::UNDEFINED; + + /** + * A list of parameters that are applicable for all the operations described under this path. + * + * These parameters can be overridden at the operation level, but cannot be removed there. + * The list must not include duplicated parameters. + * A unique parameter is defined by a combination of a name and location. + * The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters. + * + * @var Parameter[] + */ + public $parameters = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'path' => 'string', + 'summary' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Get::class => 'get', + Post::class => 'post', + Put::class => 'put', + Delete::class => 'delete', + Patch::class => 'patch', + Trace::class => 'trace', + Head::class => 'head', + Options::class => 'options', + Parameter::class => ['parameters'], + PathParameter::class => ['parameters'], + Server::class => ['servers'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php new file mode 100644 index 0000000..9e600b4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php @@ -0,0 +1,25 @@ +properties array. + * + * @var string + */ + public $property = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + AdditionalProperties::class, + Schema::class, + JsonContent::class, + XmlContent::class, + Property::class, + Items::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php new file mode 100644 index 0000000..a0a0303 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php @@ -0,0 +1,25 @@ +|JsonContent|XmlContent|Attachable + */ + public $content = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'description' => 'string', + 'required' => 'boolean', + 'request' => 'string', + ]; + + public static $_parents = [ + Components::class, + Delete::class, + Get::class, + Head::class, + Operation::class, + Options::class, + Patch::class, + Post::class, + Trace::class, + Put::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + MediaType::class => ['content', 'mediaType'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php new file mode 100644 index 0000000..73d91bc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php @@ -0,0 +1,128 @@ +responses array. + * + * A HTTP status code or default. + * + * @var string|int + */ + public $response = Generator::UNDEFINED; + + /** + * A short description of the response. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * Maps a header name to its definition. + * + * RFC7230 states header names are case insensitive. + * + * If a response header is defined with the name "Content-Type", it shall be ignored. + * + * @see [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) + * + * @var Header[] + */ + public $headers = Generator::UNDEFINED; + + /** + * A map containing descriptions of potential response payloads. + * + * The key is a media type or media type range and the value describes it. + * + * For responses that match multiple keys, only the most specific key is applicable; + * e.g. text/plain overrides text/*. + * + * @var MediaType|JsonContent|XmlContent|Attachable|array + */ + public $content = Generator::UNDEFINED; + + /** + * A map of operations links that can be followed from the response. + * + * The key of the map is a short name for the link, following the naming constraints of the names for Component + * Objects. + * + * @var Link[] + */ + public $links = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + MediaType::class => ['content', 'mediaType'], + Header::class => ['headers', 'header'], + Link::class => ['links', 'link'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Patch::class, + Delete::class, + Head::class, + Options::class, + Trace::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + $valid = parent::validate($stack, $skip, $ref, $context); + + if (Generator::isDefault($this->description) && Generator::isDefault($this->ref)) { + $this->_context->logger->warning($this->identity() . ' One of description or ref is required'); + $valid = false; + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php new file mode 100644 index 0000000..fc3c908 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php @@ -0,0 +1,444 @@ +schemas array. + * + * @var string + */ + public $schema = Generator::UNDEFINED; + + /** + * Can be used to decorate a user interface with information about the data produced by this user interface. + * + * Preferably short; use description for more details. + * + * @var string + */ + public $title = Generator::UNDEFINED; + + /** + * A description will provide explanation about the purpose of the instance described by this schema. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the + * value of this property. + * + * @var int + */ + public $maxProperties = Generator::UNDEFINED; + + /** + * An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, + * the value of this property. + * + * @var int + */ + public $minProperties = Generator::UNDEFINED; + + /** + * An object instance is valid against this property if its property set contains all elements in this property's + * array value. + * + * @var string[] + */ + public $required = Generator::UNDEFINED; + + /** + * @var Property[] + */ + public $properties = Generator::UNDEFINED; + + /** + * The type of the schema/property. The value MUST be one of "string", "number", "integer", "boolean", "array" or + * "object". + * + * @var string + */ + public $type = Generator::UNDEFINED; + + /** + * The extending format for the previously mentioned type. See Data Type Formats for further details. + * + * @var string + */ + public $format = Generator::UNDEFINED; + + /** + * Required if type is "array". Describes the type of items in the array. + * + * @var Items + */ + public $items = Generator::UNDEFINED; + + /** + * Determines the format of the array if type array is used. + * Possible values are: + * - csv: comma separated values foo,bar. + * - ssv: space separated values foo bar. + * - tsv: tab separated values foo\tbar. + * - pipes: pipe separated values foo|bar. + * - multi: corresponds to multiple parameter instances instead of multiple values for a single instance foo=bar&foo=baz. + * This is valid only for parameters of type query or formData. + * Default value is csv. + * + * @var string + */ + public $collectionFormat = Generator::UNDEFINED; + + /** + * Sets a default value to the parameter. The type of the value depends on the defined type. + * + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor101) + * + * @var mixed + */ + public $default = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17) + * + * @var int|float + */ + public $maximum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17) + * + * @var bool + */ + public $exclusiveMaximum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21) + * + * @var int|float + */ + public $minimum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21) + * + * @var bool + */ + public $exclusiveMinimum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor26) + * + * @var int + */ + public $maxLength = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor29) + * + * @var int + */ + public $minLength = Generator::UNDEFINED; + + /** + * A string instance is considered valid if the regular expression matches the instance successfully. + * + * @var string + */ + public $pattern = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor42) + * + * @var int + */ + public $maxItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor45) + * + * @var int + */ + public $minItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor49) + * + * @var bool + */ + public $uniqueItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor76) + * + * @var string[]|int[]|float[]|\UnitEnum[]|class-string + */ + public $enum = Generator::UNDEFINED; + + /** + * A numeric instance is valid against "multipleOf" if the result of the division of the instance by this + * property's value is an integer. + * + * @var int|float + */ + public $multipleOf = Generator::UNDEFINED; + + /** + * Adds support for polymorphism. + * + * The discriminator is an object name that is used to differentiate between other schemas which may satisfy the + * payload description. See Composition and Inheritance for more details. + * + * @var Discriminator + */ + public $discriminator = Generator::UNDEFINED; + + /** + * Declares the property as "read only". + * + * Relevant only for Schema "properties" definitions. + * + * This means that it may be sent as part of a response but should not be sent as part of the request. + * If the property is marked as readOnly being true and is in the required list, the required will take effect on + * the response only. A property must not be marked as both readOnly and writeOnly being true. Default value is + * false. + * + * @var bool + */ + public $readOnly = Generator::UNDEFINED; + + /** + * Declares the property as "write only". + * + * Relevant only for Schema "properties" definitions. + * Therefore, it may be sent as part of a request but should not be sent as part of the response. + * If the property is marked as writeOnly being true and is in the required list, the required will take effect on + * the request only. A property must not be marked as both readOnly and writeOnly being true. Default value is + * false. + * + * @var bool + */ + public $writeOnly = Generator::UNDEFINED; + + /** + * This may be used only on properties schemas. + * + * It has no effect on root schemas. + * Adds additional metadata to describe the XML representation of this property. + * + * @var Xml + */ + public $xml = Generator::UNDEFINED; + + /** + * Additional external documentation for this schema. + * + * @var ExternalDocumentation + */ + public $externalDocs = Generator::UNDEFINED; + + /** + * A free-form property to include an example of an instance for this schema. + * + * To represent examples that cannot naturally be represented in JSON or YAML, a string value can be used to + * contain the example with escaping where necessary. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Allows sending a null value for the defined schema. + * Default value is false. + * + * @var bool + */ + public $nullable = Generator::UNDEFINED; + + /** + * Specifies that a schema is deprecated and should be transitioned out of usage. + * Default value is false. + * + * @var bool + */ + public $deprecated = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against all schemas + * defined by this property's value. + * + * @var array + */ + public $allOf = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against at least one + * schema defined by this property's value. + * + * @var array + */ + public $anyOf = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against exactly one schema + * defined by this property's value. + * + * @var array + */ + public $oneOf = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.29. + */ + public $not = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#anchor64. + * + * @var bool|AdditionalProperties + */ + public $additionalProperties = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.10. + */ + public $additionalItems = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.14. + */ + public $contains = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.19. + */ + public $patternProperties = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.21. + */ + public $dependencies = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.22. + */ + public $propertyNames = Generator::UNDEFINED; + + /** + * http://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.3. + * + * @var mixed + */ + public $const = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'title' => 'string', + 'description' => 'string', + 'required' => '[string]', + 'format' => 'string', + 'collectionFormat' => ['csv', 'ssv', 'tsv', 'pipes', 'multi'], + 'maximum' => 'number', + 'exclusiveMaximum' => 'boolean', + 'minimum' => 'number', + 'exclusiveMinimum' => 'boolean', + 'maxLength' => 'integer', + 'minLength' => 'integer', + 'pattern' => 'string', + 'maxItems' => 'integer', + 'minItems' => 'integer', + 'uniqueItems' => 'boolean', + 'multipleOf' => 'integer', + 'allOf' => '[' . Schema::class . ']', + 'oneOf' => '[' . Schema::class . ']', + 'anyOf' => '[' . Schema::class . ']', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Parameter::class, + PathParameter::class, + MediaType::class, + Header::class, + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + if (isset($data->const)) { + if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) { + $data->enum = [$data->const]; + unset($data->const); + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if ($this->type === 'array' && Generator::isDefault($this->items)) { + $this->_context->logger->warning('@OA\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context); + + return false; + } + + return parent::validate($stack, $skip, $ref, $context); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php new file mode 100644 index 0000000..6564e1f --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php @@ -0,0 +1,136 @@ +security array. + * + * @var string + */ + public $securityScheme = Generator::UNDEFINED; + + /** + * The type of the security scheme. + * + * @var string + */ + public $type = Generator::UNDEFINED; + + /** + * A short description for security scheme. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * The name of the header or query parameter to be used. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * Required The location of the API key. + * + * @var string + */ + public $in = Generator::UNDEFINED; + + /** + * The flow used by the OAuth2 security scheme. + * + * @var Flow[] + */ + public $flows = Generator::UNDEFINED; + + /** + * A hint to the client to identify how the bearer token is formatted. + * + * Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. + * + * @var string + */ + public $bearerFormat = Generator::UNDEFINED; + + /** + * The name of the HTTP Authorization scheme. + * + * @see [RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1) + * + * @var string + */ + public $scheme = Generator::UNDEFINED; + + /** + * OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of a URL. + * + * @var string + */ + public $openIdConnectUrl = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['securityScheme', 'type']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'type' => ['http', 'apiKey', 'oauth2', 'openIdConnect'], + 'description' => 'string', + 'name' => 'string', + 'bearerFormat' => 'string', + 'in' => ['query', 'header', 'cookie'], + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Flow::class => ['flows', 'flow'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + ]; + + /** + * @inheritdoc + */ + public function merge(array $annotations, bool $ignore = false): array + { + $unmerged = parent::merge($annotations, $ignore); + + if ($this->type === 'oauth2') { + $this->name = Generator::UNDEFINED; + } + + return $unmerged; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php new file mode 100644 index 0000000..df0cb0d --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php @@ -0,0 +1,87 @@ + ['variables', 'serverVariable'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_required = ['url']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'url' => 'string', + 'description' => 'string', + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php new file mode 100644 index 0000000..0dade38 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php @@ -0,0 +1,87 @@ +variables array. + * + * @var string + */ + public $serverVariable = Generator::UNDEFINED; + + /** + * An enumeration of values to be used if the substitution options are from a limited set. + * + * @var string[]|int[]|float[]|\UnitEnum[]|class-string + */ + public $enum = Generator::UNDEFINED; + + /** + * The default value to use for substitution, and to send, if an alternate value is not supplied. + * + * Unlike the Schema Object's default, this value must be provided by the consumer. + * + * @var string + */ + public $default = Generator::UNDEFINED; + + /** + * A map between a variable name and its value. + * + * The value is used for substitution in the server's URL template. + * + * @var array + */ + public $variables = Generator::UNDEFINED; + + /** + * An optional description for the server variable. + * + * CommonMark syntax MAY be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + Server::class, + ]; + + /** + * @inheritdoc + */ + public static $_required = ['default']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'default' => 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php new file mode 100644 index 0000000..670ac87 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php @@ -0,0 +1,66 @@ + 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + ExternalDocumentation::class => 'externalDocs', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php new file mode 100644 index 0000000..aac2820 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php @@ -0,0 +1,25 @@ +true. + * + * If wrapped is false, it will be ignored. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * The URL of the namespace definition. Value SHOULD be in the form of a URL. + * + * @var string + */ + public $namespace = Generator::UNDEFINED; + + /** + * The prefix to be used for the name. + * + * @var string + */ + public $prefix = Generator::UNDEFINED; + + /** + * Declares whether the property definition translates to an attribute instead of an element. + * + * Default value is false. + * + * @var bool + */ + public $attribute = Generator::UNDEFINED; + + /** + * MAY be used only for an array definition. + * + * Signifies whether the array is wrapped (for example <books><book/><book/></books>) + * or unwrapped (<book/><book/>). + * + * Default value is false. The definition takes effect only when defined alongside type being array (outside the items). + * + * @var bool + */ + public $wrapped = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'name' => 'string', + 'namespace' => 'string', + 'prefix' => 'string', + 'attribute' => 'boolean', + 'wrapped' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + AdditionalProperties::class, + Schema::class, + Property::class, + Schema::class, + Items::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php new file mode 100644 index 0000000..59f7651 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php @@ -0,0 +1,43 @@ +`'application/xml'` will be generated. + * + * @Annotation + */ +class XmlContent extends Schema +{ + /** + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = []; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php new file mode 100644 index 0000000..5a56dce --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php @@ -0,0 +1,101 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php new file mode 100644 index 0000000..0fc6fa7 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php @@ -0,0 +1,16 @@ +|null $schemas + * @param Response[]|null $responses + * @param Parameter[]|null $parameters + * @param RequestBody[]|null $requestBodies + * @param Examples[]|null $examples + * @param Header[]|null $headers + * @param SecurityScheme[]|null $securitySchemes + * @param Link[]|null $links + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $schemas = null, + ?array $responses = null, + ?array $parameters = null, + ?array $requestBodies = null, + ?array $examples = null, + ?array $headers = null, + ?array $securitySchemes = null, + ?array $links = null, + ?array $callbacks = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'callbacks' => $callbacks ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($schemas, $responses, $parameters, $examples, $requestBodies, $headers, $securitySchemes, $links, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php new file mode 100644 index 0000000..332979e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $url = null, + ?string $email = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'email' => $email ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php new file mode 100644 index 0000000..d2f1c73 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $propertyName = null, + ?array $mapping = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'propertyName' => $propertyName ?? Generator::UNDEFINED, + 'mapping' => $mapping ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php new file mode 100644 index 0000000..9e1a9a0 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php @@ -0,0 +1,42 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $example = null, + ?string $summary = null, + ?string $description = null, + int|string|array|null $value = null, + ?string $externalValue = null, + string|object|null $ref = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'example' => $example ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'value' => $value ?? Generator::UNDEFINED, + 'externalValue' => $externalValue ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + ]); + if ($attachables) { + $this->merge($attachables); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php new file mode 100644 index 0000000..f9bfa94 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php @@ -0,0 +1,32 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $description = null, + ?string $url = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'description' => $description ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php new file mode 100644 index 0000000..80e6ebc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php @@ -0,0 +1,38 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $authorizationUrl = null, + ?string $tokenUrl = null, + ?string $refreshUrl = null, + ?string $flow = null, + ?array $scopes = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'authorizationUrl' => $authorizationUrl ?? Generator::UNDEFINED, + 'tokenUrl' => $tokenUrl ?? Generator::UNDEFINED, + 'refreshUrl' => $refreshUrl ?? Generator::UNDEFINED, + 'flow' => $flow ?? Generator::UNDEFINED, + 'scopes' => $scopes ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php new file mode 100644 index 0000000..81e5ad8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $header = null, + ?string $description = null, + ?bool $required = null, + ?Schema $schema = null, + ?bool $deprecated = null, + ?bool $allowEmptyValue = null, + // annotation4 + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'header' => $header ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables, $schema), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php new file mode 100644 index 0000000..39348f5 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php @@ -0,0 +1,38 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $version = null, + ?string $description = null, + ?string $title = null, + ?string $termsOfService = null, + ?Contact $contact = null, + ?License $license = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'version' => $version ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'termsOfService' => $termsOfService ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($contact, $license, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php new file mode 100644 index 0000000..1c0dc28 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php @@ -0,0 +1,105 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php new file mode 100644 index 0000000..d9606ef --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php @@ -0,0 +1,108 @@ + $examples + * @param string[] $required + * @param Property[] $properties + * @param int|float $maximum + * @param int|float $minimum + * @param string[]|int[]|float[]|\UnitEnum[]|class-string $enum + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $examples = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'examples' => $examples ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php new file mode 100644 index 0000000..b929237 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $identifier = null, + ?string $url = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'identifier' => $identifier ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php new file mode 100644 index 0000000..4f90659 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php @@ -0,0 +1,44 @@ + $parameters + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $link = null, + ?string $operationRef = null, + string|object|null $ref = null, + ?string $operationId = null, + ?array $parameters = null, + mixed $requestBody = null, + ?string $description = null, + ?Server $server = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'link' => $link ?? Generator::UNDEFINED, + 'operationRef' => $operationRef ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'operationId' => $operationId ?? Generator::UNDEFINED, + 'parameters' => $parameters ?? Generator::UNDEFINED, + 'requestBody' => $requestBody ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($server, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php new file mode 100644 index 0000000..4f307d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php @@ -0,0 +1,38 @@ + $examples + * @param array $encoding + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $mediaType = null, + ?Schema $schema = null, + mixed $example = Generator::UNDEFINED, + ?array $examples = null, + ?array $encoding = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'mediaType' => $mediaType ?? Generator::UNDEFINED, + 'example' => $example, + 'encoding' => $encoding ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($schema, $examples, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php new file mode 100644 index 0000000..ce625e2 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php @@ -0,0 +1,41 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string $openapi = self::DEFAULT_VERSION, + ?Info $info = null, + ?array $servers = null, + ?array $security = null, + ?array $tags = null, + ?ExternalDocumentation $externalDocs = null, + ?array $paths = null, + ?Components $components = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'openapi' => $openapi, + 'security' => $security ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php new file mode 100644 index 0000000..40d26fe --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php @@ -0,0 +1,54 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $path = null, + ?string $operationId = null, + ?string $description = null, + ?string $summary = null, + ?array $security = null, + ?array $servers = null, + ?RequestBody $requestBody = null, + ?array $tags = null, + ?array $parameters = null, + ?array $responses = null, + ?array $callbacks = null, + ?ExternalDocumentation $externalDocs = null, + ?bool $deprecated = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'path' => $path ?? Generator::UNDEFINED, + 'operationId' => $operationId ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'security' => $security ?? Generator::UNDEFINED, + 'servers' => $servers ?? Generator::UNDEFINED, + 'tags' => $tags ?? Generator::UNDEFINED, + 'callbacks' => $callbacks ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($requestBody, $responses, $parameters, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php new file mode 100644 index 0000000..175b962 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php @@ -0,0 +1,13 @@ + $examples + * @param array|JsonContent|XmlContent|Attachable|null $content + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $parameter = null, + ?string $name = null, + ?string $description = null, + ?string $in = null, + ?bool $required = null, + ?bool $deprecated = null, + ?bool $allowEmptyValue = null, + string|object|null $ref = null, + ?Schema $schema = null, + mixed $example = Generator::UNDEFINED, + ?array $examples = null, + array|JsonContent|XmlContent|Attachable|null $content = null, + ?string $style = null, + ?bool $explode = null, + ?bool $allowReserved = null, + ?array $spaceDelimited = null, + ?array $pipeDelimited = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'parameter' => $parameter ?? Generator::UNDEFINED, + 'name' => $name ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'in' => Generator::isDefault($this->in) ? $in : $this->in, + 'required' => $required ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'example' => $example, + 'style' => $style ?? Generator::UNDEFINED, + 'explode' => $explode ?? Generator::UNDEFINED, + 'allowReserved' => $allowReserved ?? Generator::UNDEFINED, + 'spaceDelimited' => $spaceDelimited ?? Generator::UNDEFINED, + 'pipeDelimited' => $pipeDelimited ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($schema, $examples, $content, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php new file mode 100644 index 0000000..1a0a3c8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $path = null, + ?string $summary = null, + ?array $servers = null, + ?array $parameters = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'path' => $path ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($servers, $parameters, $attachables), + ]); + } +} + +// Missing parameters: get, put, post, delete, options, head, patch, trace, parameters diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php new file mode 100644 index 0000000..70ccfd6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php @@ -0,0 +1,13 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $property = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'property' => $property ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php new file mode 100644 index 0000000..2133102 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php @@ -0,0 +1,13 @@ +|JsonContent|XmlContent|Attachable|null $content + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $request = null, + ?string $description = null, + ?bool $required = null, + array|JsonContent|XmlContent|Attachable|null $content = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'request' => $request ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($content, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php new file mode 100644 index 0000000..996a697 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php @@ -0,0 +1,41 @@ + $content + * @param Link[] $links + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + int|string $response = null, + ?string $description = null, + ?array $headers = null, + MediaType|JsonContent|XmlContent|Attachable|array|null $content = null, + ?array $links = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'response' => $response ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($headers, $content, $links, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php new file mode 100644 index 0000000..6426f00 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php @@ -0,0 +1,106 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param mixed $const + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + $const = Generator::UNDEFINED, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + 'const' => $const, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php new file mode 100644 index 0000000..f1d8bec --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php @@ -0,0 +1,48 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $securityScheme = null, + ?string $type = null, + ?string $description = null, + ?string $name = null, + ?string $in = null, + ?string $bearerFormat = null, + ?string $scheme = null, + ?string $openIdConnectUrl = null, + ?array $flows = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'securityScheme' => $securityScheme ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'name' => $name ?? Generator::UNDEFINED, + 'in' => $in ?? Generator::UNDEFINED, + 'bearerFormat' => $bearerFormat ?? Generator::UNDEFINED, + 'scheme' => $scheme ?? Generator::UNDEFINED, + 'openIdConnectUrl' => $openIdConnectUrl ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($flows, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php new file mode 100644 index 0000000..337d595 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $url = null, + ?string $description = null, + ?array $variables = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'url' => $url ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($variables, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php new file mode 100644 index 0000000..3b096d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php @@ -0,0 +1,39 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $serverVariable = null, + ?string $description = null, + ?string $default = null, + array|string|null $enum = null, + ?array $variables = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'serverVariable' => $serverVariable ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'default' => $default ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'variables' => $variables ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php new file mode 100644 index 0000000..8ef0ef1 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php @@ -0,0 +1,33 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $description = null, + ?ExternalDocumentation $externalDocs = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php new file mode 100644 index 0000000..aff7fe7 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $namespace = null, + ?string $prefix = null, + ?bool $attribute = null, + ?bool $wrapped = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'namespace' => $namespace ?? Generator::UNDEFINED, + 'prefix' => $prefix ?? Generator::UNDEFINED, + 'attribute' => $attribute ?? Generator::UNDEFINED, + 'wrapped' => $wrapped ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php new file mode 100644 index 0000000..47a3083 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php @@ -0,0 +1,108 @@ + $examples + * @param string[] $required + * @param int|float $maximum + * @param int|float $minimum + * @param Property[] $properties + * @param string[]|int[]|float[]|\UnitEnum[]|class-string $enum + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $examples = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'examples' => $examples ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Context.php b/Sources/API/vendor/zircote/swagger-php/src/Context.php new file mode 100644 index 0000000..58bdb49 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Context.php @@ -0,0 +1,274 @@ +parent); + } + + public function __construct(array $properties = [], ?Context $parent = null) + { + foreach ($properties as $property => $value) { + $this->{$property} = $value; + } + $this->parent = $parent; + + $this->logger = $this->logger ?: new DefaultLogger(); + } + + /** + * Check if a property is set directly on this context and not its parent context. + * + * Example: $c->is('method') or $c->is('class') + */ + public function is(string $property): bool + { + return property_exists($this, $property); + } + + /** + * Check if a property is NOT set directly on this context and its parent context. + * + * Example: $c->not('method') or $c->not('class') + */ + public function not(string $property): bool + { + return $this->is($property) === false; + } + + /** + * Return the context containing the specified property. + */ + public function with(string $property): ?Context + { + if ($this->is($property)) { + return $this; + } + if ($this->parent !== null) { + return $this->parent->with($property); + } + + return null; + } + + /** + * Get the root context. + */ + public function root(): Context + { + if ($this->parent !== null) { + return $this->parent->root(); + } + + return $this; + } + + /** + * Check if one of the given version numbers matches the current OpenAPI version. + * + * @param string|array $versions One or more version numbers + */ + public function isVersion($versions): bool + { + if (!$this->version) { + throw new \RuntimeException('Version is only available reliably for validation and serialization'); + } + + $versions = (array) $versions; + $currentVersion = $this->version ?: OA\OpenApi::DEFAULT_VERSION; + + return in_array($currentVersion, $versions); + } + + /** + * Export location for debugging. + * + * @return string Example: "file1.php on line 12" + */ + public function getDebugLocation(): string + { + $location = ''; + if ($this->class && ($this->method || $this->property)) { + $location .= $this->fullyQualifiedName($this->class); + if ($this->method) { + $location .= ($this->static ? '::' : '->') . $this->method . '()'; + } elseif ($this->property) { + $location .= ($this->static ? '::$' : '->') . $this->property; + } + } + if ($this->filename) { + if ($location !== '') { + $location .= ' in '; + } + $location .= $this->filename; + } + if ($this->line) { + if ($location !== '') { + $location .= ' on'; + } + $location .= ' line ' . $this->line; + if ($this->character) { + $location .= ':' . $this->character; + } + } + + return $location; + } + + /** + * Traverse the context tree to get the property value. + */ + public function __get(string $property) + { + if ($this->parent !== null) { + return $this->parent->{$property}; + } + + return null; + } + + public function __toString() + { + return $this->getDebugLocation(); + } + + public function __debugInfo() + { + return ['-' => $this->getDebugLocation()]; + } + + /** + * Create a Context based on the debug_backtrace. + * + * @deprecated + */ + public static function detect(int $index = 0): Context + { + // trigger_deprecation('zircote/swagger-php', '4.0', 'Context detecting is deprecated'); + + $context = new Context(); + $backtrace = debug_backtrace(); + $position = $backtrace[$index]; + if (isset($position['file'])) { + $context->filename = $position['file']; + } + if (isset($position['line'])) { + $context->line = $position['line']; + } + $caller = $backtrace[$index + 1] ?? null; + if (isset($caller['function'])) { + $context->method = $caller['function']; + if (isset($caller['type']) && $caller['type'] === '::') { + $context->static = true; + } + } + if (isset($caller['class'])) { + $fqn = explode('\\', $caller['class']); + $context->class = array_pop($fqn); + if (count($fqn)) { + $context->namespace = implode('\\', $fqn); + } + } + + // @todo extract namespaces and use statements + return $context; + } + + /** + * Resolve the fully qualified name. + */ + public function fullyQualifiedName(?string $source): string + { + if ($source === null) { + return ''; + } + + if ($this->namespace) { + $namespace = str_replace('\\\\', '\\', '\\' . $this->namespace . '\\'); + } else { + // global namespace + $namespace = '\\'; + } + + $thisSource = $this->class ?? $this->interface ?? $this->trait; + if ($thisSource && strcasecmp($source, $thisSource) === 0) { + return $namespace . $thisSource; + } + $pos = strpos($source, '\\'); + if ($pos !== false) { + if ($pos === 0) { + // Fully qualified name (\Foo\Bar) + return $source; + } + // Qualified name (Foo\Bar) + if ($this->uses) { + foreach ($this->uses as $alias => $aliasedNamespace) { + $alias .= '\\'; + if (strcasecmp(substr($source, 0, strlen($alias)), $alias) === 0) { + // Aliased namespace (use \Long\Namespace as Foo) + return '\\' . $aliasedNamespace . substr($source, strlen($alias) - 1); + } + } + } + } elseif ($this->uses) { + // Unqualified name (Foo) + foreach ($this->uses as $alias => $aliasedNamespace) { + if (strcasecmp($alias, $source) === 0) { + return '\\' . $aliasedNamespace; + } + } + } + + return $namespace . $source; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Generator.php b/Sources/API/vendor/zircote/swagger-php/src/Generator.php new file mode 100644 index 0000000..d37116e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Generator.php @@ -0,0 +1,475 @@ + */ + public const DEFAULT_ALIASES = ['oa' => 'OpenApi\\Annotations']; + /** @var array */ + public const DEFAULT_NAMESPACES = ['OpenApi\\Annotations\\']; + + /** @var array Map of namespace aliases to be supported by doctrine. */ + protected $aliases; + + /** @var array|null List of annotation namespaces to be autoloaded by doctrine. */ + protected $namespaces; + + /** @var AnalyserInterface|null The configured analyzer. */ + protected $analyser; + + /** @var array */ + protected $config = []; + + /** @var callable[]|null List of configured processors. */ + protected $processors = null; + + /** @var LoggerInterface|null PSR logger. */ + protected $logger = null; + + /** + * OpenApi version override. + * + * If set, it will override the version set in the `OpenApi` annotation. + * + * Due to the order of processing any conditional code using this (via `Context::$version`) + * must come only after the analysis is finished. + * + * @var string|null + */ + protected $version = null; + + private $configStack; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->setAliases(self::DEFAULT_ALIASES); + $this->setNamespaces(self::DEFAULT_NAMESPACES); + + // kinda config stack to stay BC... + $this->configStack = new class() { + protected $generator; + + public function push(Generator $generator): void + { + $this->generator = $generator; + if (class_exists(AnnotationRegistry::class, true)) { + // keeping track of &this->generator allows to 'disable' the loader after we are done; + // no unload, unfortunately :/ + $gref = &$this->generator; + AnnotationRegistry::registerLoader( + function (string $class) use (&$gref): bool { + if ($gref) { + foreach ($gref->getNamespaces() as $namespace) { + if (strtolower(substr($class, 0, strlen($namespace))) === strtolower($namespace)) { + $loaded = class_exists($class); + if (!$loaded && $namespace === 'OpenApi\\Annotations\\') { + if (in_array(strtolower(substr($class, 20)), ['definition', 'path'])) { + // Detected an 2.x annotation? + throw new \Exception('The annotation @SWG\\' . substr($class, 20) . '() is deprecated. Found in ' . Generator::$context . "\nFor more information read the migration guide: https://github.com/zircote/swagger-php/blob/master/docs/Migrating-to-v3.md"); + } + } + + return $loaded; + } + } + } + + return false; + } + ); + } + } + + public function pop(): void + { + $this->generator = null; + } + }; + } + + /** + * @param mixed $value + */ + public static function isDefault($value): bool + { + return $value === Generator::UNDEFINED; + } + + /** + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + public function addAlias(string $alias, string $namespace): Generator + { + $this->aliases[$alias] = $namespace; + + return $this; + } + + public function setAliases(array $aliases): Generator + { + $this->aliases = $aliases; + + return $this; + } + + /** + * @return array|null + */ + public function getNamespaces(): ?array + { + return $this->namespaces; + } + + public function addNamespace(string $namespace): Generator + { + $namespaces = (array) $this->getNamespaces(); + $namespaces[] = $namespace; + + return $this->setNamespaces(array_unique($namespaces)); + } + + public function setNamespaces(?array $namespaces): Generator + { + $this->namespaces = $namespaces; + + return $this; + } + + public function getAnalyser(): AnalyserInterface + { + $this->analyser = $this->analyser ?: new ReflectionAnalyser([new DocBlockAnnotationFactory(), new AttributeAnnotationFactory()]); + $this->analyser->setGenerator($this); + + return $this->analyser; + } + + public function setAnalyser(?AnalyserInterface $analyser): Generator + { + $this->analyser = $analyser; + + return $this; + } + + public function getDefaultConfig(): array + { + return [ + 'operationId' => [ + 'hash' => true, + ], + ]; + } + + public function getConfig(): array + { + return $this->config + $this->getDefaultConfig(); + } + + protected function normaliseConfig(array $config): array + { + $normalised = []; + foreach ($config as $key => $value) { + if (is_numeric($key)) { + $token = explode('=', $value); + if (2 == count($token)) { + // 'operationId.hash=false' + [$key, $value] = $token; + } + } + + if (in_array($value, ['true', 'false'])) { + $value = 'true' == $value; + } + + $token = explode('.', $key); + if (2 == count($token)) { + // 'operationId.hash' => false + $normalised[$token[0]][$token[1]] = $value; + } else { + $normalised[$key] = $value; + } + } + + return $normalised; + } + + /** + * Set generator and/or processor config. + * + * @param array $config + */ + public function setConfig(array $config): Generator + { + $this->config = $this->normaliseConfig($config) + $this->config; + + return $this; + } + + /** + * @return callable[] + */ + public function getProcessors(): array + { + if (null === $this->processors) { + $this->processors = [ + new Processors\DocBlockDescriptions(), + new Processors\MergeIntoOpenApi(), + new Processors\MergeIntoComponents(), + new Processors\ExpandClasses(), + new Processors\ExpandInterfaces(), + new Processors\ExpandTraits(), + new Processors\ExpandEnums(), + new Processors\AugmentSchemas(), + new Processors\AugmentProperties(), + new Processors\BuildPaths(), + new Processors\AugmentParameters(), + new Processors\AugmentRefs(), + new Processors\MergeJsonContent(), + new Processors\MergeXmlContent(), + new Processors\OperationId(), + new Processors\CleanUnmerged(), + ]; + } + + $config = $this->getConfig(); + foreach ($this->processors as $processor) { + $rc = new \ReflectionClass($processor); + + // apply config + $processorKey = lcfirst($rc->getShortName()); + if (array_key_exists($processorKey, $config)) { + foreach ($config[$processorKey] as $name => $value) { + $setter = 'set' . ucfirst($name); + if (method_exists($processor, $setter)) { + $processor->{$setter}($value); + } + } + } + } + + return $this->processors; + } + + /** + * @param null|callable[] $processors + */ + public function setProcessors(?array $processors): Generator + { + $this->processors = $processors; + + return $this; + } + + public function addProcessor(callable $processor): Generator + { + $processors = $this->getProcessors(); + $processors[] = $processor; + $this->setProcessors($processors); + + return $this; + } + + public function removeProcessor(callable $processor, bool $silent = false): Generator + { + $processors = $this->getProcessors(); + if (false === ($key = array_search($processor, $processors, true))) { + if ($silent) { + return $this; + } + throw new \InvalidArgumentException('Processor not found'); + } + unset($processors[$key]); + $this->setProcessors($processors); + + return $this; + } + + /** + * Update/replace an existing processor with a new one. + * + * @param callable $processor The new processor + * @param null|callable $matcher Optional matcher callable to identify the processor to replace. + * If none given, matching is based on the processors class. + */ + public function updateProcessor(callable $processor, ?callable $matcher = null): Generator + { + $matcher = $matcher ?: function ($other) use ($processor): bool { + $otherClass = get_class($other); + + return $processor instanceof $otherClass; + }; + + $processors = array_map(function ($other) use ($processor, $matcher) { + return $matcher($other) ? $processor : $other; + }, $this->getProcessors()); + $this->setProcessors($processors); + + return $this; + } + + public function getLogger(): ?LoggerInterface + { + return $this->logger ?: new DefaultLogger(); + } + + public function getVersion(): ?string + { + return $this->version; + } + + public function setVersion(?string $version): Generator + { + $this->version = $version; + + return $this; + } + + public static function scan(iterable $sources, array $options = []): ?OA\OpenApi + { + // merge with defaults + $config = $options + [ + 'aliases' => self::DEFAULT_ALIASES, + 'namespaces' => self::DEFAULT_NAMESPACES, + 'analyser' => null, + 'analysis' => null, + 'processors' => null, + 'logger' => null, + 'validate' => true, + 'version' => null, + ]; + + return (new Generator($config['logger'])) + ->setVersion($config['version']) + ->setAliases($config['aliases']) + ->setNamespaces($config['namespaces']) + ->setAnalyser($config['analyser']) + ->setProcessors($config['processors']) + ->generate($sources, $config['analysis'], $config['validate']); + } + + /** + * Run code in the context of this generator. + * + * @param callable $callable Callable in the form of + * `function(Generator $generator, Analysis $analysis, Context $context): mixed` + * + * @return mixed the result of the `callable` + */ + public function withContext(callable $callable) + { + $rootContext = new Context([ + 'version' => $this->getVersion(), + 'logger' => $this->getLogger(), + ]); + $analysis = new Analysis([], $rootContext); + + $this->configStack->push($this); + try { + return $callable($this, $analysis, $rootContext); + } finally { + $this->configStack->pop(); + } + } + + /** + * Generate OpenAPI spec by scanning the given source files. + * + * @param iterable $sources PHP source files to scan. + * Supported sources: + * * string - file / directory name + * * \SplFileInfo + * * \Symfony\Component\Finder\Finder + * @param null|Analysis $analysis custom analysis instance + * @param bool $validate flag to enable/disable validation of the returned spec + */ + public function generate(iterable $sources, ?Analysis $analysis = null, bool $validate = true): ?OA\OpenApi + { + $rootContext = new Context([ + 'version' => $this->getVersion(), + 'logger' => $this->getLogger(), + ]); + $analysis = $analysis ?: new Analysis([], $rootContext); + + $this->configStack->push($this); + try { + $this->scanSources($sources, $analysis, $rootContext); + + // post-processing + $analysis->process($this->getProcessors()); + + if ($analysis->openapi) { + $analysis->openapi->openapi = $this->version ?: $analysis->openapi->openapi; + $rootContext->version = $analysis->openapi->openapi; + } + + // validation + if ($validate) { + $analysis->validate(); + } + } finally { + $this->configStack->pop(); + } + + return $analysis->openapi; + } + + protected function scanSources(iterable $sources, Analysis $analysis, Context $rootContext): void + { + $analyser = $this->getAnalyser(); + + foreach ($sources as $source) { + if (is_iterable($source)) { + $this->scanSources($source, $analysis, $rootContext); + } else { + $resolvedSource = $source instanceof \SplFileInfo ? $source->getPathname() : realpath($source); + if (!$resolvedSource) { + $rootContext->logger->warning(sprintf('Skipping invalid source: %s', $source)); + continue; + } + if (is_dir($resolvedSource)) { + $this->scanSources(Util::finder($resolvedSource), $analysis, $rootContext); + } else { + $analysis->addAnalysis($analyser->fromFile($resolvedSource, $rootContext)); + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php b/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php new file mode 100644 index 0000000..064c251 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php @@ -0,0 +1,97 @@ +debug = $debug; + } + + public function loggedMessageAboveNotice(): bool + { + return $this->loggedMessageAboveNotice; + } + + /** + * @param string $level + * @param string|\Exception $message + * @param array $context additional details; supports custom `prefix` and `exception` + */ + public function log($level, $message, array $context = []): void + { + $prefix = ''; + $color = ''; + // level adjustments + switch ($level) { + case LogLevel::DEBUG: + if (!$this->debug) { + return; + } + // no break + case LogLevel::WARNING: + $prefix = $context['prefix'] ?? 'Warning: '; + $color = static::COLOR_WARNING; + break; + case LogLevel::ERROR: + $prefix = $context['prefix'] ?? 'Error: '; + $color = static::COLOR_ERROR; + break; + } + $stop = !empty($color) ? static::COLOR_STOP : ''; + + if (!in_array($level, self::LOG_LEVELS_UP_TO_NOTICE, true)) { + $this->loggedMessageAboveNotice = true; + } + + /** @var ?\Exception $exception */ + $exception = $context['exception'] ?? null; + if ($message instanceof \Exception) { + $exception = $message; + $message = $exception->getMessage(); + } + + $logLine = sprintf('%s%s%s%s', $color, $prefix, $message, $stop); + error_log($logLine); + + if ($this->debug) { + if ($exception) { + error_log($exception->getTraceAsString()); + } elseif (!empty($logLine)) { + $stack = explode(PHP_EOL, (new \Exception())->getTraceAsString()); + // self + array_shift($stack); + // AbstractLogger + array_shift($stack); + foreach ($stack as $line) { + error_log($line); + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php b/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php new file mode 100644 index 0000000..d4118f0 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php @@ -0,0 +1,33 @@ +getMessage(); + } + + if (in_array($level, [LogLevel::NOTICE, LogLevel::INFO])) { + $error_level = E_USER_NOTICE; + } else { + $error_level = E_USER_WARNING; + } + + trigger_error($message, $error_level); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php new file mode 100644 index 0000000..4b616b3 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php @@ -0,0 +1,96 @@ +augmentOperationParameters = $augmentOperationParameters; + } + + public function isAugmentOperationParameters(): bool + { + return $this->augmentOperationParameters; + } + + /** + * If set to true try to find operation parameter descriptions in the operation docblock. + * + * @param bool $augmentOperationParameters + */ + public function setAugmentOperationParameters(bool $augmentOperationParameters): void + { + $this->augmentOperationParameters = $augmentOperationParameters; + } + + public function __invoke(Analysis $analysis) + { + $this->augmentSharedParameters($analysis); + if ($this->augmentOperationParameters) { + $this->augmentOperationParameters($analysis); + } + } + + /** + * Use the parameter->name as key field (parameter->parameter) when used as reusable component + * (openapi->components->parameters). + */ + protected function augmentSharedParameters(Analysis $analysis): void + { + if (!Generator::isDefault($analysis->openapi->components) && !Generator::isDefault($analysis->openapi->components->parameters)) { + $keys = []; + $parametersWithoutKey = []; + foreach ($analysis->openapi->components->parameters as $parameter) { + if (!Generator::isDefault($parameter->parameter)) { + $keys[$parameter->parameter] = $parameter; + } else { + $parametersWithoutKey[] = $parameter; + } + } + foreach ($parametersWithoutKey as $parameter) { + if (!Generator::isDefault($parameter->name) && empty($keys[$parameter->name])) { + $parameter->parameter = $parameter->name; + $keys[$parameter->parameter] = $parameter; + } + } + } + } + + protected function augmentOperationParameters(Analysis $analysis): void + { + /** @var OA\Operation[] $operations */ + $operations = $analysis->getAnnotationsOfType(OA\Operation::class); + + foreach ($operations as $operation) { + if (!Generator::isDefault($operation->parameters)) { + $tags = []; + $this->extractContent($operation->_context->comment, $tags); + if (array_key_exists('param', $tags)) { + foreach ($tags['param'] as $name => $details) { + foreach ($operation->parameters as $parameter) { + if ($parameter->name == $name) { + if (Generator::isDefault($parameter->description) && $details['description']) { + $parameter->description = $details['description']; + } + } + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php new file mode 100644 index 0000000..7b55104 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php @@ -0,0 +1,191 @@ +openapi->components) && !Generator::isDefault($analysis->openapi->components->schemas)) { + foreach ($analysis->openapi->components->schemas as $schema) { + if (!Generator::isDefault($schema->schema)) { + $refKey = $this->toRefKey($schema->_context, $schema->_context->class); + $refs[$refKey] = OA\Components::ref($schema); + } + } + } + + /** @var OA\Property[] $properties */ + $properties = $analysis->getAnnotationsOfType(OA\Property::class); + + foreach ($properties as $property) { + $context = $property->_context; + + if (Generator::isDefault($property->property)) { + $property->property = $context->property; + } + + if (!Generator::isDefault($property->ref)) { + continue; + } + + $comment = str_replace("\r\n", "\n", (string) $context->comment); + preg_match('/@var\s+(?[^\s]+)([ \t])?(?.+)?$/im', $comment, $varMatches); + + if (Generator::isDefault($property->type)) { + $this->augmentType($analysis, $property, $context, $refs, $varMatches); + } else { + $this->mapNativeType($property, $property->type); + } + + if (Generator::isDefault($property->description) && isset($varMatches['description'])) { + $property->description = trim($varMatches['description']); + } + if (Generator::isDefault($property->description) && $this->isRoot($property)) { + $property->description = $this->extractContent($context->comment); + } + + if (Generator::isDefault($property->example) && preg_match('/@example\s+([ \t])?(?.+)?$/im', $comment, $varMatches)) { + $property->example = $varMatches['example']; + } + } + } + + protected function toRefKey(Context $context, ?string $name): string + { + $fqn = strtolower($context->fullyQualifiedName($name)); + + return ltrim($fqn, '\\'); + } + + protected function augmentType(Analysis $analysis, OA\Property $property, Context $context, array $refs, array $varMatches): void + { + // docblock typehints + if (isset($varMatches['type'])) { + $allTypes = strtolower(trim($varMatches['type'])); + + if ($this->isNullable($allTypes) && Generator::isDefault($property->nullable)) { + $property->nullable = true; + } + + $allTypes = $this->stripNull($allTypes); + preg_match('/^([^\[]+)(.*$)/', $allTypes, $typeMatches); + $type = $typeMatches[1]; + + // finalise property type/ref + if (!$this->mapNativeType($property, $type)) { + $refKey = $this->toRefKey($context, $type); + if (Generator::isDefault($property->ref) && array_key_exists($refKey, $refs)) { + $property->ref = $refs[$refKey]; + } + } + + // ok, so we possibly have a type or ref + if (!Generator::isDefault($property->ref) && $typeMatches[2] === '' && $property->nullable) { + $refKey = $this->toRefKey($context, $type); + $property->oneOf = [ + $schema = new OA\Schema([ + 'ref' => $refs[$refKey], + '_context' => new Context(['generated' => true], $property->_context), + ]), + ]; + $analysis->addAnnotation($schema, $schema->_context); + $property->nullable = true; + } elseif ($typeMatches[2] === '[]') { + if (Generator::isDefault($property->items)) { + $property->items = $items = new OA\Items( + [ + 'type' => $property->type, + '_context' => new Context(['generated' => true], $context), + ] + ); + $analysis->addAnnotation($items, $items->_context); + if (!Generator::isDefault($property->ref)) { + $property->items->ref = $property->ref; + $property->ref = Generator::UNDEFINED; + } + $property->type = 'array'; + } + } + } + + // native typehints + if ($context->type && !Generator::isDefault($context->type)) { + if ($context->nullable === true) { + $property->nullable = true; + } + $type = strtolower($context->type); + if (!$this->mapNativeType($property, $type)) { + $refKey = $this->toRefKey($context, $type); + if (Generator::isDefault($property->ref) && array_key_exists($refKey, $refs)) { + $this->applyRef($analysis, $property, $refs[$refKey]); + } else { + if ($typeSchema = $analysis->getSchemaForSource($context->type)) { + if (Generator::isDefault($property->format)) { + $property->ref = OA\Components::ref($typeSchema); + $property->type = Generator::UNDEFINED; + } + } + } + } + } + + if (!Generator::isDefault($property->const) && Generator::isDefault($property->type)) { + if (!$this->mapNativeType($property, gettype($property->const))) { + $property->type = Generator::UNDEFINED; + } + } + } + + protected function isNullable(string $typeDescription): bool + { + return in_array('null', explode('|', strtolower($typeDescription))); + } + + protected function stripNull(string $typeDescription): string + { + if (strpos($typeDescription, '|') === false) { + return $typeDescription; + } + $types = []; + foreach (explode('|', $typeDescription) as $type) { + if (strtolower($type) === 'null') { + continue; + } + $types[] = $type; + } + + return implode('|', $types); + } + + protected function applyRef(Analysis $analysis, OA\Property $property, string $ref): void + { + if ($property->nullable === true) { + $property->oneOf = [ + $schema = new OA\Schema([ + 'ref' => $ref, + '_context' => new Context(['generated' => true], $property->_context), + ]), + ]; + $analysis->addAnnotation($schema, $schema->_context); + } else { + $property->ref = $ref; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php new file mode 100644 index 0000000..d2bd92b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php @@ -0,0 +1,49 @@ +getAnnotationsOfType(OA\Schema::class); + + // ref rewriting + $updatedRefs = []; + foreach ($schemas as $schema) { + if ($schema->allOf!== Generator::UNDEFINED) { + // do we have to keep track of properties refs that need updating? + foreach ($schema->allOf as $ii => $allOfSchema) { + if ($allOfSchema->properties!== Generator::UNDEFINED) { + $updatedRefs[OA\Components::ref($schema->schema . '/properties', false)] = OA\Components::ref($schema->schema . '/allOf/' . $ii . '/properties', false); + break; + } + } + } + } + + if ($updatedRefs) { + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, 'ref') && $annotation->ref !== Generator::UNDEFINED && $annotation->ref !== null) { + foreach ($updatedRefs as $origRef => $updatedRef) { + if (0 === strpos($annotation->ref, $origRef)) { + $annotation->ref = str_replace($origRef, $updatedRef, $annotation->ref); + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php new file mode 100644 index 0000000..d126bc2 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php @@ -0,0 +1,112 @@ +getAnnotationsOfType(OA\Schema::class); + + // Use the class names for @OA\Schema() + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->schema)) { + if ($schema->_context->is('class')) { + $schema->schema = $schema->_context->class; + } elseif ($schema->_context->is('interface')) { + $schema->schema = $schema->_context->interface; + } elseif ($schema->_context->is('trait')) { + $schema->schema = $schema->_context->trait; + } elseif ($schema->_context->is('enum')) { + $schema->schema = $schema->_context->enum; + } + } + } + + // Merge unmerged @OA\Property annotations into the @OA\Schema of the class + $unmergedProperties = $analysis->unmerged()->getAnnotationsOfType(OA\Property::class); + foreach ($unmergedProperties as $property) { + if ($property->_context->nested) { + continue; + } + + $schemaContext = $property->_context->with('class') + ?: $property->_context->with('interface') + ?: $property->_context->with('trait') + ?: $property->_context->with('enum'); + if ($schemaContext->annotations) { + foreach ($schemaContext->annotations as $annotation) { + if ($annotation instanceof OA\Schema) { + if ($annotation->_context->nested) { + // we shouldn't merge property into nested schemas + continue; + } + + $annotation->merge([$property], true); + break; + } + } + } + } + + // set schema type based on various properties + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->type)) { + if (is_array($schema->properties) && count($schema->properties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->additionalProperties) && count($schema->additionalProperties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->patternProperties) && count($schema->patternProperties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->propertyNames) && count($schema->propertyNames) > 0) { + $schema->type = 'object'; + } + } else { + if ($typeSchema = $analysis->getSchemaForSource($schema->type)) { + if (Generator::isDefault($schema->format)) { + $schema->ref = OA\Components::ref($typeSchema); + $schema->type = Generator::UNDEFINED; + } + } + } + } + + // move schema properties into allOf if both exist + foreach ($schemas as $schema) { + if (!Generator::isDefault($schema->properties) && !Generator::isDefault($schema->allOf)) { + $allOfPropertiesSchema = null; + foreach ($schema->allOf as $allOfSchema) { + if (!Generator::isDefault($allOfSchema->properties)) { + $allOfPropertiesSchema = $allOfSchema; + break; + } + } + if (!$allOfPropertiesSchema) { + $allOfPropertiesSchema = new OA\Schema([ + 'properties' => [], + '_context' => new Context(['generated' => true], $schema->_context), + ]); + $analysis->addAnnotation($allOfPropertiesSchema, $allOfPropertiesSchema->_context); + $schema->allOf[] = $allOfPropertiesSchema; + } + $allOfPropertiesSchema->properties = array_merge($allOfPropertiesSchema->properties, $schema->properties); + $schema->properties = Generator::UNDEFINED; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php new file mode 100644 index 0000000..84ed100 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php @@ -0,0 +1,60 @@ +paths using the detected `@OA\PathItem` and `@OA\Operation` (`@OA\Get`, `@OA\Post`, etc). + */ +class BuildPaths +{ + public function __invoke(Analysis $analysis) + { + $paths = []; + // Merge @OA\PathItems with the same path. + if (!Generator::isDefault($analysis->openapi->paths)) { + foreach ($analysis->openapi->paths as $annotation) { + if (empty($annotation->path)) { + $annotation->_context->logger->warning($annotation->identity() . ' is missing required property "path" in ' . $annotation->_context); + } elseif (isset($paths[$annotation->path])) { + $paths[$annotation->path]->mergeProperties($annotation); + $analysis->annotations->detach($annotation); + } else { + $paths[$annotation->path] = $annotation; + } + } + } + + /** @var OA\Operation[] $operations */ + $operations = $analysis->unmerged()->getAnnotationsOfType(OA\Operation::class); + + // Merge @OA\Operations into existing @OA\PathItems or create a new one. + foreach ($operations as $operation) { + if ($operation->path) { + if (empty($paths[$operation->path])) { + $paths[$operation->path] = $pathItem = new OA\PathItem( + [ + 'path' => $operation->path, + '_context' => new Context(['generated' => true], $operation->_context), + ] + ); + $analysis->addAnnotation($pathItem, $pathItem->_context); + } + if ($paths[$operation->path]->merge([$operation])) { + $operation->_context->logger->warning('Unable to merge ' . $operation->identity() . ' in ' . $operation->_context); + } + } + } + if ($paths) { + $analysis->openapi->paths = array_values($paths); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php new file mode 100644 index 0000000..65ac526 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php @@ -0,0 +1,35 @@ +split(); + $merged = $split->merged->annotations; + $unmerged = $split->unmerged->annotations; + + /** @var OA\AbstractAnnotation $annotation */ + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, '_unmerged')) { + foreach ($annotation->_unmerged as $i => $item) { + if ($merged->contains($item)) { + unset($annotation->_unmerged[$i]); // Property was merged + } + } + } + } + $analysis->openapi->_unmerged = []; + foreach ($unmerged as $annotation) { + $analysis->openapi->_unmerged[] = $annotation; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php new file mode 100644 index 0000000..25ac1ad --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php @@ -0,0 +1,96 @@ +openapi->components)) { + return; + } + + $analysis->annotations = $this->collect($analysis->annotations); + + // allow multiple runs to catch nested dependencies + for ($ii = 0; $ii < 10; ++$ii) { + if (!$this->cleanup($analysis)) { + break; + } + } + } + + protected function cleanup(Analysis $analysis): bool + { + $usedRefs = []; + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, 'ref') && !Generator::isDefault($annotation->ref) && $annotation->ref !== null) { + $usedRefs[$annotation->ref] = $annotation->ref; + } + + foreach (['allOf', 'anyOf', 'oneOff'] as $sub) { + if (property_exists($annotation, $sub) && !Generator::isDefault($annotation->{$sub})) { + foreach ($annotation->{$sub} as $subElem) { + if (is_object($subElem) && property_exists($subElem, 'ref') && !Generator::isDefault($subElem->ref) && $subElem->ref !== null) { + $usedRefs[$subElem->ref] = $subElem->ref; + } + } + } + } + + if ($annotation instanceof OA\OpenApi || $annotation instanceof OA\Operation) { + if (!Generator::isDefault($annotation->security)) { + foreach ($annotation->security as $security) { + foreach (array_keys($security) as $securityName) { + $ref = OA\Components::COMPONENTS_PREFIX . 'securitySchemes/' . $securityName; + $usedRefs[$ref] = $ref; + } + } + } + } + } + + $unusedRefs = []; + foreach (OA\Components::$_nested as $nested) { + if (2 == count($nested)) { + // $nested[1] is the name of the property that holds the component name + [$componentType, $nameProperty] = $nested; + if (!Generator::isDefault($analysis->openapi->components->{$componentType})) { + foreach ($analysis->openapi->components->{$componentType} as $component) { + $ref = OA\Components::ref($component); + if (!in_array($ref, $usedRefs)) { + $unusedRefs[$ref] = [$ref, $nameProperty]; + } + } + } + } + } + + // remove unused + foreach ($unusedRefs as $refDetails) { + [$ref, $nameProperty] = $refDetails; + [$hash, $components, $componentType, $name] = explode('/', $ref); + foreach ($analysis->openapi->components->{$componentType} as $ii => $component) { + if ($component->{$nameProperty} == $name) { + $annotation = $analysis->openapi->components->{$componentType}[$ii]; + foreach ($this->collect([$annotation]) as $unused) { + $analysis->annotations->detach($unused); + } + unset($analysis->openapi->components->{$componentType}[$ii]); + } + } + } + + return 0 != count($unusedRefs); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php new file mode 100644 index 0000000..de5f271 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php @@ -0,0 +1,67 @@ +addAll($this->traverse($annotation)); + } + } + + return $storage; + } + + public function traverse(OA\AbstractAnnotation $annotation): \SplObjectStorage + { + $storage = new \SplObjectStorage(); + + if ($storage->contains($annotation)) { + return $storage; + } + + $storage->attach($annotation); + + foreach (array_merge($annotation::$_nested, ['allOf', 'anyOf', 'oneOff', 'callbacks']) as $properties) { + foreach ((array) $properties as $property) { + if (isset($annotation->{$property})) { + $storage->addAll($this->traverseNested($annotation->{$property})); + } + } + } + + return $storage; + } + + /** + * @param string|array|OA\AbstractAnnotation $nested + */ + protected function traverseNested($nested): \SplObjectStorage + { + $storage = new \SplObjectStorage(); + + if (is_array($nested)) { + foreach ($nested as $value) { + $storage->addAll($this->traverseNested($value)); + } + } elseif ($nested instanceof OA\AbstractAnnotation) { + $storage->addAll($this->traverse($nested)); + } + + return $storage; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php new file mode 100644 index 0000000..52683e9 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php @@ -0,0 +1,171 @@ +_context) { + return true; + } + + if (1 == count($annotation->_context->annotations)) { + return true; + } + + /** @var array $matchPriorityMap */ + $matchPriorityMap = [ + OA\OpenApi::class, + + OA\Operation::class => false, + OA\Property::class => false, + OA\Parameter::class => false, + OA\Response::class => false, + + OA\Schema::class => true, + OAT\Schema::class => true, + ]; + // try to find best root match + foreach ($matchPriorityMap as $className => $strict) { + foreach ($annotation->_context->annotations as $contextAnnotation) { + if ($strict) { + if ($className === get_class($contextAnnotation)) { + return $annotation === $contextAnnotation; + } + } else { + if ($contextAnnotation instanceof $className) { + return $annotation === $contextAnnotation; + } + } + } + } + + return false; + } + + protected function handleTag(string $line, ?array &$tags = null): void + { + if (null === $tags) { + return; + } + + // split of tag name + $token = preg_split("@[\s+ ]@u", $line, 2); + if (2 == count($token)) { + $tag = substr($token[0], 1); + $tail = $token[1]; + if (!array_key_exists($tag, $tags)) { + $tags[$tag] = []; + } + + if (false !== ($dpos = strpos($tail, '$'))) { + $type = trim(substr($tail, 0, $dpos)); + $token = preg_split("@[\s+ ]@u", substr($tail, $dpos), 2); + $name = trim(substr($token[0], 1)); + $description = 2 == count($token) ? trim($token[1]) : null; + + $tags[$tag][$name] = [ + 'type' => $type, + 'description' => $description, + ]; + } + } + } + + /** + * The text contents of the phpdoc comment (excl. tags). + */ + public function extractContent(?string $docblock, ?array &$tags = null): string + { + if (Generator::isDefault($docblock)) { + return Generator::UNDEFINED; + } + + $comment = preg_split('/(\n|\r\n)/', (string) $docblock); + $comment[0] = preg_replace('/[ \t]*\\/\*\*/', '', $comment[0]); // strip '/**' + $i = count($comment) - 1; + $comment[$i] = preg_replace('/\*\/[ \t]*$/', '', $comment[$i]); // strip '*/' + $lines = []; + $append = false; + $skip = false; + foreach ($comment as $line) { + $line = ltrim($line, "\t *"); + if (substr($line, 0, 1) === '@') { + $this->handleTag($line, $tags); + $skip = true; + } + if ($skip) { + continue; + } + if ($append) { + $i = count($lines) - 1; + $lines[$i] = substr($lines[$i], 0, -1) . $line; + } else { + $lines[] = $line; + } + $append = (substr($line, -1) === '\\'); + } + $description = trim(implode("\n", $lines)); + if ($description === '') { + return Generator::UNDEFINED; + } + + return $description; + } + + /** + * A short piece of text, usually one line, providing the basic function of the associated element. + */ + public function extractSummary(?string $docblock): string + { + if (!$content = $this->extractContent($docblock)) { + return Generator::UNDEFINED; + } + $lines = preg_split('/(\n|\r\n)/', $content); + $summary = ''; + foreach ($lines as $line) { + $summary .= $line . "\n"; + if ($line === '' || substr($line, -1) === '.') { + return trim($summary); + } + } + $summary = trim($summary); + if ($summary === '') { + return Generator::UNDEFINED; + } + + return $summary; + } + + /** + * An optional longer piece of text providing more details on the associated element’s function. + * + * This is very useful when working with a complex element. + */ + public function extractDescription(?string $docblock): string + { + $summary = $this->extractSummary($docblock); + if (!$summary) { + return Generator::UNDEFINED; + } + + $description = ''; + if (false !== ($substr = substr($this->extractContent($docblock), strlen($summary)))) { + $description = trim($substr); + } + + return $description ?: Generator::UNDEFINED; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php new file mode 100644 index 0000000..86748d8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php @@ -0,0 +1,65 @@ + update all $ref that might reference a property merged. + */ +trait MergePropertiesTrait +{ + protected function inheritFrom(Analysis $analysis, OA\Schema $schema, OA\Schema $from, string $refPath, Context $context): void + { + if (Generator::isDefault($schema->allOf)) { + $schema->allOf = []; + } + // merging other properties into allOf is done in the AugmentSchemas processor + $schema->allOf[] = $refSchema = new OA\Schema([ + 'ref' => OA\Components::ref($refPath), + '_context' => new Context(['generated' => true], $context), + ]); + $analysis->addAnnotation($refSchema, $refSchema->_context); + } + + protected function mergeProperties(OA\Schema $schema, array $from, array &$existing): void + { + foreach ($from['properties'] as $context) { + if (is_iterable($context->annotations)) { + foreach ($context->annotations as $annotation) { + if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { + $existing[] = $annotation->_context->property; + $schema->merge([$annotation], true); + } + } + } + } + } + + protected function mergeMethods(OA\Schema $schema, array $from, array &$existing): void + { + foreach ($from['methods'] as $context) { + if (is_iterable($context->annotations)) { + foreach ($context->annotations as $annotation) { + if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { + $existing[] = $annotation->_context->property; + $schema->merge([$annotation], true); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php new file mode 100644 index 0000000..feb6907 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php @@ -0,0 +1,54 @@ + 'array', + 'byte' => ['string', 'byte'], + 'boolean' => 'boolean', + 'bool' => 'boolean', + 'int' => 'integer', + 'integer' => 'integer', + 'long' => ['integer', 'long'], + 'float' => ['number', 'float'], + 'double' => ['number', 'double'], + 'string' => 'string', + 'date' => ['string', 'date'], + 'datetime' => ['string', 'date-time'], + '\\datetime' => ['string', 'date-time'], + 'datetimeimmutable' => ['string', 'date-time'], + '\\datetimeimmutable' => ['string', 'date-time'], + 'datetimeinterface' => ['string', 'date-time'], + '\\datetimeinterface' => ['string', 'date-time'], + 'number' => 'number', + 'object' => 'object', + ]; + + public function mapNativeType(OA\Schema $schema, string $type): bool + { + if (!array_key_exists($type, self::$NATIVE_TYPE_MAP)) { + return false; + } + + $type = self::$NATIVE_TYPE_MAP[$type]; + if (is_array($type)) { + if (Generator::isDefault($schema->format)) { + $schema->format = $type[1]; + } + $type = $type[0]; + } + + $schema->type = $type; + + return true; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php new file mode 100644 index 0000000..974bc24 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php @@ -0,0 +1,94 @@ +annotations as $annotation) { + if (property_exists($annotation, '_context') === false) { + // only annotations with context + continue; + } + + if (!$this->isRoot($annotation)) { + // only top-level annotations + continue; + } + + $hasSummary = property_exists($annotation, 'summary'); + $hasDescription = property_exists($annotation, 'description'); + if (!$hasSummary && !$hasDescription) { + continue; + } + + if ($hasSummary && $hasDescription) { + $this->summaryAndDescription($annotation); + } elseif ($hasDescription) { + $this->description($annotation); + } + } + } + + /** + * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation + */ + protected function description(OA\AbstractAnnotation $annotation): void + { + if (!Generator::isDefault($annotation->description)) { + if ($annotation->description === null) { + $annotation->description = Generator::UNDEFINED; + } + + return; + } + + $annotation->description = $this->extractContent($annotation->_context->comment); + } + + /** + * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation + */ + protected function summaryAndDescription(OA\AbstractAnnotation $annotation): void + { + $ignoreSummary = !Generator::isDefault($annotation->summary); + $ignoreDescription = !Generator::isDefault($annotation->description); + if ($annotation->summary === null) { + $ignoreSummary = true; + $annotation->summary = Generator::UNDEFINED; + } + if ($annotation->description === null) { + $annotation->description = Generator::UNDEFINED; + $ignoreDescription = true; + } + if ($ignoreSummary && $ignoreDescription) { + return; + } + if ($ignoreSummary) { + $annotation->description = $this->extractContent($annotation->_context->comment); + } elseif ($ignoreDescription) { + $annotation->summary = $this->extractContent($annotation->_context->comment); + } else { + $annotation->summary = $this->extractSummary($annotation->_context->comment); + $annotation->description = $this->extractDescription($annotation->_context->comment); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php new file mode 100644 index 0000000..b7bea86 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php @@ -0,0 +1,50 @@ + inherit from the ancestor if it has a schema (allOf) and stop. + * - else + * => merge ancestor properties into the schema. + */ +class ExpandClasses +{ + use Concerns\MergePropertiesTrait; + + public function __invoke(Analysis $analysis) + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('class')) { + $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); + $existing = []; + foreach ($ancestors as $ancestor) { + $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); + if ($ancestorSchema) { + $refPath = !Generator::isDefault($ancestorSchema->schema) ? $ancestorSchema->schema : $ancestor['class']; + $this->inheritFrom($analysis, $schema, $ancestorSchema, $refPath, $ancestor['context']); + + // one ancestor is enough + break; + } else { + $this->mergeMethods($schema, $ancestor, $existing); + $this->mergeProperties($schema, $ancestor, $existing); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php new file mode 100644 index 0000000..d31b712 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php @@ -0,0 +1,106 @@ +expandContextEnum($analysis); + $this->expandSchemaEnum($analysis); + } + + private function expandContextEnum(Analysis $analysis): void + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('enum')) { + $source = $schema->_context->enum; + $re = new \ReflectionEnum($schema->_context->fullyQualifiedName($source)); + $schema->schema = !Generator::isDefault($schema->schema) ? $schema->schema : $re->getShortName(); + $type = 'string'; + $schemaType = 'string'; + if ($re->isBacked() && ($backingType = $re->getBackingType()) && method_exists($backingType, 'getName')) { + if (Generator::isDefault($schema->type)) { + $type = $backingType->getName(); + } else { + $type = $schema->type; + $schemaType = $schema->type; + } + } + $schema->enum = array_map(function ($case) use ($re, $schemaType, $type) { + if ($re->isBacked() && $type === $schemaType) { + return $case->getBackingValue(); + } + + return $case->name; + }, $re->getCases()); + $this->mapNativeType($schema, $type); + } + } + } + + private function expandSchemaEnum(Analysis $analysis): void + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([ + OA\Schema::class, + OAT\Schema::class, + OA\ServerVariable::class, + OAT\ServerVariable::class, + ]); + + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->enum)) { + continue; + } + + if (is_string($schema->enum)) { + // might be enum class + if (is_a($schema->enum, \UnitEnum::class, true)) { + $source = $schema->enum::cases(); + } else { + throw new \InvalidArgumentException("Unexpected enum value, requires specifying the Enum class string: $schema->enum"); + } + } elseif (is_array($schema->enum)) { + // might be array of enum, string, int, etc... + $source = $schema->enum; + } else { + throw new \InvalidArgumentException('Unexpected enum value, requires Enum class string or array'); + } + + $enums = []; + foreach ($source as $enum) { + if (is_a($enum, \UnitEnum::class)) { + $enums[] = $enum->value ?? $enum->name; + } else { + $enums[] = $enum; + } + } + + $schema->enum = $enums; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php new file mode 100644 index 0000000..a4f5269 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php @@ -0,0 +1,56 @@ +getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('class')) { + $className = $schema->_context->fullyQualifiedName($schema->_context->class); + $interfaces = $analysis->getInterfacesOfClass($className, true); + + if (class_exists($className) && ($parent = get_parent_class($className)) && ($inherited = array_keys(class_implements($parent)))) { + // strip interfaces we inherit from ancestor + foreach (array_keys($interfaces) as $interface) { + if (in_array(ltrim($interface, '\\'), $inherited)) { + unset($interfaces[$interface]); + } + } + } + + $existing = []; + foreach ($interfaces as $interface) { + $interfaceName = $interface['context']->fullyQualifiedName($interface['interface']); + $interfaceSchema = $analysis->getSchemaForSource($interfaceName); + if ($interfaceSchema) { + $refPath = !Generator::isDefault($interfaceSchema->schema) ? $interfaceSchema->schema : $interface['interface']; + $this->inheritFrom($analysis, $schema, $interfaceSchema, $refPath, $interface['context']); + } else { + $this->mergeMethods($schema, $interface, $existing); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php new file mode 100644 index 0000000..a4313a6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php @@ -0,0 +1,81 @@ +getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + // do regular trait inheritance / merge + foreach ($schemas as $schema) { + if ($schema->_context->is('trait')) { + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->trait), true); + $existing = []; + foreach ($traits as $trait) { + $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); + if ($traitSchema) { + $refPath = !Generator::isDefault($traitSchema->schema) ? $traitSchema->schema : $trait['trait']; + $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); + } else { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + } + } + + foreach ($schemas as $schema) { + if ($schema->_context->is('class') && !$schema->_context->is('generated')) { + // look at class traits + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->class), true); + $existing = []; + foreach ($traits as $trait) { + $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); + if ($traitSchema) { + $refPath = !Generator::isDefault($traitSchema->schema) ? $traitSchema->schema : $trait['trait']; + $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); + } else { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + + // also merge ancestor traits of non schema parents + $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); + $existing = []; + foreach ($ancestors as $ancestor) { + $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); + if ($ancestorSchema) { + // stop here as we inherit everything above + break; + } else { + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($ancestor['class']), true); + foreach ($traits as $trait) { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php new file mode 100644 index 0000000..cefac23 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php @@ -0,0 +1,34 @@ +openapi->components; + if (Generator::isDefault($components)) { + $components = new OA\Components(['_context' => new Context(['generated' => true], $analysis->context)]); + } + + foreach ($analysis->annotations as $annotation) { + if (OA\Components::matchNested(get_class($annotation)) && $annotation->_context->is('nested') === false) { + // A top level annotation. + $components->merge([$annotation], true); + $analysis->openapi->components = $components; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php new file mode 100644 index 0000000..dcf2d39 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php @@ -0,0 +1,55 @@ +openapi) { + $context = new Context([], $analysis->context); + $analysis->addAnnotation(new OA\OpenApi(['_context' => $context]), $context); + } + $openapi = $analysis->openapi; + $openapi->_analysis = $analysis; + + // Merge annotations into the target openapi + $merge = []; + /** @var OA\AbstractAnnotation $annotation */ + foreach ($analysis->annotations as $annotation) { + if ($annotation === $openapi) { + continue; + } + if ($annotation instanceof OA\OpenApi) { + $paths = $annotation->paths; + unset($annotation->paths); + $openapi->mergeProperties($annotation); + if (!Generator::isDefault($paths)) { + foreach ($paths as $path) { + if (Generator::isDefault($openapi->paths)) { + $openapi->paths = []; + } + $openapi->paths[] = $path; + } + } + } elseif (OA\OpenApi::matchNested(get_class($annotation)) && property_exists($annotation, '_context') && $annotation->_context->is('nested') === false) { + // A top level annotation. + $merge[] = $annotation; + } + } + $openapi->merge($merge, true); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php new file mode 100644 index 0000000..ab8d804 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php @@ -0,0 +1,56 @@ +getAnnotationsOfType(OA\JsonContent::class); + + foreach ($annotations as $jsonContent) { + $parent = $jsonContent->_context->nested; + if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { + if ($parent) { + $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); + } else { + $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' must be nested'); + } + continue; + } + if (Generator::isDefault($parent->content)) { + $parent->content = []; + } + $parent->content['application/json'] = $mediaType = new OA\MediaType([ + 'schema' => $jsonContent, + 'example' => $jsonContent->example, + 'examples' => $jsonContent->examples, + '_context' => new Context(['generated' => true], $jsonContent->_context), + ]); + $analysis->addAnnotation($mediaType, $mediaType->_context); + if (!$parent instanceof OA\Parameter) { + $parent->content['application/json']->mediaType = 'application/json'; + } + $jsonContent->example = Generator::UNDEFINED; + $jsonContent->examples = Generator::UNDEFINED; + + $index = array_search($jsonContent, $parent->_unmerged, true); + if ($index !== false) { + array_splice($parent->_unmerged, $index, 1); + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php new file mode 100644 index 0000000..45590dc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php @@ -0,0 +1,56 @@ +getAnnotationsOfType(OA\XmlContent::class); + + foreach ($annotations as $xmlContent) { + $parent = $xmlContent->_context->nested; + if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { + if ($parent) { + $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); + } else { + $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' must be nested'); + } + continue; + } + if (Generator::isDefault($parent->content)) { + $parent->content = []; + } + $parent->content['application/xml'] = $mediaType = new OA\MediaType([ + 'schema' => $xmlContent, + 'example' => $xmlContent->example, + 'examples' => $xmlContent->examples, + '_context' => new Context(['generated' => true], $xmlContent->_context), + ]); + $analysis->addAnnotation($mediaType, $mediaType->_context); + if (!$parent instanceof OA\Parameter) { + $parent->content['application/xml']->mediaType = 'application/xml'; + } + $xmlContent->example = Generator::UNDEFINED; + $xmlContent->examples = Generator::UNDEFINED; + + $index = array_search($xmlContent, $parent->_unmerged, true); + if ($index !== false) { + array_splice($parent->_unmerged, $index, 1); + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php new file mode 100644 index 0000000..100a568 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php @@ -0,0 +1,74 @@ +hash = $hash; + } + + public function isHash(): bool + { + return $this->hash; + } + + /** + * If set to true generate ids (md5) instead of clear text operation ids. + * + * @param bool $hash + */ + public function setHash(bool $hash): OperationId + { + $this->hash = $hash; + + return $this; + } + + public function __invoke(Analysis $analysis) + { + $allOperations = $analysis->getAnnotationsOfType(OA\Operation::class); + + /** @var OA\Operation $operation */ + foreach ($allOperations as $operation) { + if (null === $operation->operationId) { + $operation->operationId = Generator::UNDEFINED; + } + + if (!Generator::isDefault($operation->operationId)) { + continue; + } + + $context = $operation->_context; + if ($context && $context->method) { + $source = $context->class ?? $context->interface ?? $context->trait; + $operationId = null; + if ($source) { + if ($context->namespace) { + $operationId = $context->namespace . '\\' . $source . '::' . $context->method; + } else { + $operationId = $source . '::' . $context->method; + } + } else { + $operationId = $context->method; + } + $operationId = strtoupper($operation->method) . '::' . $operation->path . '::' . $operationId; + $operation->operationId = $this->hash ? md5($operationId) : $operationId; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Serializer.php b/Sources/API/vendor/zircote/swagger-php/src/Serializer.php new file mode 100644 index 0000000..137a576 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Serializer.php @@ -0,0 +1,209 @@ +isValidAnnotationClass($className)) { + throw new \Exception($className . ' is not defined in OpenApi PHP Annotations'); + } + + return $this->doDeserialize(json_decode($jsonString), $className, new Context(['generated' => true])); + } + + /** + * Deserialize a file. + */ + public function deserializeFile(string $filename, string $format = 'json', string $className = OA\OpenApi::class): OA\AbstractAnnotation + { + if (!$this->isValidAnnotationClass($className)) { + throw new \Exception($className . ' is not a valid OpenApi PHP Annotations'); + } + + $contents = file_get_contents($filename); + + $ext = pathinfo($filename, PATHINFO_EXTENSION); + if ('yaml' == $format || in_array($ext, ['yml', 'yaml'])) { + $contents = json_encode(Yaml::parse($contents)); + } + + return $this->doDeserialize(json_decode($contents), $className, new Context(['generated' => true])); + } + + /** + * Do deserialization. + */ + protected function doDeserialize(\stdClass $c, string $class, Context $context): OA\AbstractAnnotation + { + $annotation = new $class(['_context' => $context]); + foreach ((array) $c as $property => $value) { + if ($property === '$ref') { + $property = 'ref'; + } + + if (substr($property, 0, 2) === 'x-') { + if (Generator::isDefault($annotation->x)) { + $annotation->x = []; + } + $custom = substr($property, 2); + $annotation->x[$custom] = $value; + } else { + $annotation->{$property} = $this->doDeserializeProperty($annotation, $property, $value, $context); + } + } + + if ($annotation instanceof OA\OpenApi) { + $context->root()->version = $annotation->openapi; + } + + return $annotation; + } + + /** + * Deserialize the annotation's property. + * + * @param mixed $value + * + * @return mixed + */ + protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, string $property, $value, Context $context) + { + // property is primitive type + if (array_key_exists($property, $annotation::$_types)) { + return $this->doDeserializeBaseProperty($annotation::$_types[$property], $value, $context); + } + + // property is embedded annotation + // note: this does not support custom nested annotation classes + foreach ($annotation::$_nested as $nestedClass => $declaration) { + // property is an annotation + if (is_string($declaration) && $declaration === $property) { + if (is_object($value)) { + return $this->doDeserialize($value, $nestedClass, $context); + } else { + return $value; + } + } + + // property is an annotation array + if (is_array($declaration) && count($declaration) === 1 && $declaration[0] === $property) { + $annotationArr = []; + foreach ($value as $v) { + $annotationArr[] = $this->doDeserialize($v, $nestedClass, $context); + } + + return $annotationArr; + } + + // property is an annotation hash map + if (is_array($declaration) && count($declaration) === 2 && $declaration[0] === $property) { + $key = $declaration[1]; + $annotationHash = []; + foreach ($value as $k => $v) { + $annotation = $this->doDeserialize($v, $nestedClass, $context); + $annotation->{$key} = $k; + $annotationHash[$k] = $annotation; + } + + return $annotationHash; + } + } + + return $value; + } + + /** + * Deserialize base annotation property. + * + * @param array|string $type The property type + * @param mixed $value The value to deserialization + * + * @return array|OA\AbstractAnnotation + */ + protected function doDeserializeBaseProperty($type, $value, Context $context) + { + $isAnnotationClass = is_string($type) && is_subclass_of(trim($type, '[]'), OA\AbstractAnnotation::class); + + if ($isAnnotationClass) { + $isArray = strpos($type, '[') === 0 && substr($type, -1) === ']'; + + if ($isArray) { + $annotationArr = []; + $class = trim($type, '[]'); + + foreach ($value as $v) { + $annotationArr[] = $this->doDeserialize($v, $class, $context); + } + + return $annotationArr; + } + + return $this->doDeserialize($value, $type, $context); + } + + return $value; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Util.php b/Sources/API/vendor/zircote/swagger-php/src/Util.php new file mode 100644 index 0000000..be427b4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Util.php @@ -0,0 +1,154 @@ +files()->followLinks(); + } else { + $finder = new Finder(); + $finder->sortByName(); + } + if ($pattern === null) { + $pattern = '*.php'; + } + + $finder->files()->followLinks()->name($pattern); + if (is_string($directory)) { + if (is_file($directory)) { // Scan a single file? + $finder->append([$directory]); + } else { // Scan a directory + $finder->in($directory); + } + } elseif (is_array($directory)) { + foreach ($directory as $path) { + if (is_file($path)) { // Scan a file? + $finder->append([$path]); + } else { + $finder->in($path); + } + } + } else { + throw new \InvalidArgumentException('Unexpected $directory value:' . gettype($directory)); + } + if ($exclude !== null) { + if (is_string($exclude)) { + $finder->notPath(Util::getRelativePath($exclude, $directory)); + } elseif (is_array($exclude)) { + foreach ($exclude as $path) { + $finder->notPath(Util::getRelativePath($path, $directory)); + } + } else { + throw new \InvalidArgumentException('Unexpected $exclude value:' . gettype($exclude)); + } + } + + return $finder; + } + + /** + * Escapes the special characters "/" and "~". + * + * https://swagger.io/docs/specification/using-ref/ + * https://tools.ietf.org/html/rfc6901#page-3 + */ + public static function refEncode(string $raw): string + { + return str_replace('/', '~1', str_replace('~', '~0', $raw)); + } + + /** + * Converted the escaped characters "~1" and "~" back to "/" and "~". + * + * https://swagger.io/docs/specification/using-ref/ + * https://tools.ietf.org/html/rfc6901#page-3 + */ + public static function refDecode(string $encoded): string + { + return str_replace('~1', '/', str_replace('~0', '~', $encoded)); + } + + /** + * Shorten class name(s). + * + * @param array|object|string $classes Class(es) to shorten + * + * @return string|string[] One or more shortened class names + */ + public static function shorten($classes) + { + $short = []; + foreach ((array) $classes as $class) { + $short[] = '@' . str_replace([ + 'OpenApi\\Annotations\\', + 'OpenApi\\Attributes\\', + ], 'OA\\', $class); + } + + return is_array($classes) ? $short : array_pop($short); + } +}