+ */
+ 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