From f10b2e4736bab306f05edf04e45c5e9244521b92 Mon Sep 17 00:00:00 2001 From: viastolfi Date: Sun, 11 Dec 2022 23:46:01 +0100 Subject: [PATCH] ajout de deux routes avec des requetes PUT (pas encore fonctionnelle) et test de documentation avec swagger --- Sources/API/composer.json | 3 +- Sources/API/composer.lock | 613 ++++++- Sources/API/documentation/api.php | 6 + Sources/API/routes/Inscrit.php | 83 +- .../API/vendor/composer/autoload_files.php | 4 +- Sources/API/vendor/composer/autoload_psr4.php | 8 + .../API/vendor/composer/autoload_static.php | 50 +- Sources/API/vendor/composer/installed.json | 638 +++++++ Sources/API/vendor/composer/installed.php | 85 +- .../API/vendor/composer/platform_check.php | 4 +- .../API/vendor/doctrine/annotations/LICENSE | 19 + .../API/vendor/doctrine/annotations/README.md | 24 + .../vendor/doctrine/annotations/composer.json | 72 + .../annotations/docs/en/annotations.rst | 252 +++ .../doctrine/annotations/docs/en/custom.rst | 443 +++++ .../doctrine/annotations/docs/en/index.rst | 110 ++ .../doctrine/annotations/docs/en/sidebar.rst | 6 + .../Common/Annotations/Annotation.php | 57 + .../Annotations/Annotation/Attribute.php | 21 + .../Annotations/Annotation/Attributes.php | 15 + .../Common/Annotations/Annotation/Enum.php | 69 + .../Annotation/IgnoreAnnotation.php | 43 + .../Annotation/NamedArgumentConstructor.php | 13 + .../Annotations/Annotation/Required.php | 13 + .../Common/Annotations/Annotation/Target.php | 101 ++ .../Annotations/AnnotationException.php | 167 ++ .../Common/Annotations/AnnotationReader.php | 389 +++++ .../Common/Annotations/AnnotationRegistry.php | 190 +++ .../Common/Annotations/CachedReader.php | 266 +++ .../Doctrine/Common/Annotations/DocLexer.php | 139 ++ .../Doctrine/Common/Annotations/DocParser.php | 1485 +++++++++++++++++ .../Common/Annotations/FileCacheReader.php | 315 ++++ .../ImplicitlyIgnoredAnnotationNames.php | 178 ++ .../Common/Annotations/IndexedReader.php | 100 ++ .../NamedArgumentConstructorAnnotation.php | 14 + .../Doctrine/Common/Annotations/PhpParser.php | 92 + .../Common/Annotations/PsrCachedReader.php | 232 +++ .../Doctrine/Common/Annotations/Reader.php | 80 + .../Annotations/SimpleAnnotationReader.php | 114 ++ .../Common/Annotations/TokenParser.php | 206 +++ .../API/vendor/doctrine/annotations/psalm.xml | 15 + .../API/vendor/doctrine/deprecations/LICENSE | 19 + .../vendor/doctrine/deprecations/README.md | 154 ++ .../doctrine/deprecations/composer.json | 32 + .../lib/Doctrine/Deprecations/Deprecation.php | 266 +++ .../PHPUnit/VerifyDeprecations.php | 66 + .../vendor/doctrine/deprecations/phpcs.xml | 22 + Sources/API/vendor/doctrine/lexer/LICENSE | 19 + Sources/API/vendor/doctrine/lexer/README.md | 9 + Sources/API/vendor/doctrine/lexer/UPGRADE.md | 14 + .../API/vendor/doctrine/lexer/composer.json | 56 + .../doctrine/lexer/src/AbstractLexer.php | 336 ++++ .../API/vendor/doctrine/lexer/src/Token.php | 129 ++ Sources/API/vendor/psr/cache/CHANGELOG.md | 16 + Sources/API/vendor/psr/cache/LICENSE.txt | 19 + Sources/API/vendor/psr/cache/README.md | 12 + Sources/API/vendor/psr/cache/composer.json | 25 + .../vendor/psr/cache/src/CacheException.php | 10 + .../psr/cache/src/CacheItemInterface.php | 105 ++ .../psr/cache/src/CacheItemPoolInterface.php | 138 ++ .../cache/src/InvalidArgumentException.php | 13 + .../deprecation-contracts/CHANGELOG.md | 5 + .../symfony/deprecation-contracts/LICENSE | 19 + .../symfony/deprecation-contracts/README.md | 26 + .../deprecation-contracts/composer.json | 35 + .../deprecation-contracts/function.php | 27 + .../API/vendor/symfony/finder/CHANGELOG.md | 98 ++ .../symfony/finder/Comparator/Comparator.php | 62 + .../finder/Comparator/DateComparator.php | 50 + .../finder/Comparator/NumberComparator.php | 78 + .../Exception/AccessDeniedException.php | 19 + .../Exception/DirectoryNotFoundException.php | 19 + Sources/API/vendor/symfony/finder/Finder.php | 846 ++++++++++ .../API/vendor/symfony/finder/Gitignore.php | 93 ++ Sources/API/vendor/symfony/finder/Glob.php | 109 ++ .../finder/Iterator/CustomFilterIterator.php | 61 + .../Iterator/DateRangeFilterIterator.php | 58 + .../Iterator/DepthRangeFilterIterator.php | 48 + .../ExcludeDirectoryFilterIterator.php | 88 + .../Iterator/FileTypeFilterIterator.php | 53 + .../Iterator/FilecontentFilterIterator.php | 58 + .../Iterator/FilenameFilterIterator.php | 45 + .../symfony/finder/Iterator/LazyIterator.php | 32 + .../Iterator/MultiplePcreFilterIterator.php | 111 ++ .../finder/Iterator/PathFilterIterator.php | 56 + .../Iterator/RecursiveDirectoryIterator.php | 146 ++ .../Iterator/SizeRangeFilterIterator.php | 57 + .../finder/Iterator/SortableIterator.php | 121 ++ .../Iterator/VcsIgnoredFilterIterator.php | 178 ++ Sources/API/vendor/symfony/finder/LICENSE | 19 + Sources/API/vendor/symfony/finder/README.md | 14 + .../API/vendor/symfony/finder/SplFileInfo.php | 82 + .../API/vendor/symfony/finder/composer.json | 31 + .../vendor/symfony/polyfill-ctype/Ctype.php | 232 +++ .../API/vendor/symfony/polyfill-ctype/LICENSE | 19 + .../vendor/symfony/polyfill-ctype/README.md | 12 + .../symfony/polyfill-ctype/bootstrap.php | 50 + .../symfony/polyfill-ctype/bootstrap80.php | 46 + .../symfony/polyfill-ctype/composer.json | 41 + Sources/API/vendor/symfony/yaml/CHANGELOG.md | 248 +++ .../symfony/yaml/Command/LintCommand.php | 276 +++ Sources/API/vendor/symfony/yaml/Dumper.php | 138 ++ Sources/API/vendor/symfony/yaml/Escaper.php | 95 ++ .../symfony/yaml/Exception/DumpException.php | 21 + .../yaml/Exception/ExceptionInterface.php | 21 + .../symfony/yaml/Exception/ParseException.php | 126 ++ .../yaml/Exception/RuntimeException.php | 21 + Sources/API/vendor/symfony/yaml/Inline.php | 816 +++++++++ Sources/API/vendor/symfony/yaml/LICENSE | 19 + Sources/API/vendor/symfony/yaml/Parser.php | 1246 ++++++++++++++ Sources/API/vendor/symfony/yaml/README.md | 13 + .../vendor/symfony/yaml/Tag/TaggedValue.php | 38 + Sources/API/vendor/symfony/yaml/Unescaper.php | 110 ++ Sources/API/vendor/symfony/yaml/Yaml.php | 96 ++ Sources/API/vendor/symfony/yaml/composer.json | 41 + .../API/vendor/zircote/swagger-php/LICENSE | 202 +++ .../API/vendor/zircote/swagger-php/README.md | 155 ++ .../vendor/zircote/swagger-php/composer.json | 118 ++ .../src/Analysers/AnalyserInterface.php | 18 + .../Analysers/AnnotationFactoryInterface.php | 21 + .../Analysers/AttributeAnnotationFactory.php | 142 ++ .../Analysers/ComposerAutoloaderScanner.php | 53 + .../Analysers/DocBlockAnnotationFactory.php | 58 + .../src/Analysers/DocBlockParser.php | 83 + .../src/Analysers/ReflectionAnalyser.php | 190 +++ .../src/Analysers/TokenAnalyser.php | 639 +++++++ .../src/Analysers/TokenScanner.php | 366 ++++ .../zircote/swagger-php/src/Analysis.php | 439 +++++ .../src/Annotations/AbstractAnnotation.php | 716 ++++++++ .../src/Annotations/AdditionalProperties.php | 38 + .../src/Annotations/Attachable.php | 73 + .../src/Annotations/Components.php | 146 ++ .../swagger-php/src/Annotations/Contact.php | 63 + .../swagger-php/src/Annotations/Delete.php | 25 + .../src/Annotations/Discriminator.php | 69 + .../swagger-php/src/Annotations/Examples.php | 96 ++ .../src/Annotations/ExternalDocumentation.php | 76 + .../swagger-php/src/Annotations/Flow.php | 106 ++ .../swagger-php/src/Annotations/Get.php | 25 + .../swagger-php/src/Annotations/Head.php | 25 + .../swagger-php/src/Annotations/Header.php | 99 ++ .../swagger-php/src/Annotations/Info.php | 98 ++ .../swagger-php/src/Annotations/Items.php | 62 + .../src/Annotations/JsonContent.php | 42 + .../swagger-php/src/Annotations/License.php | 102 ++ .../swagger-php/src/Annotations/Link.php | 114 ++ .../swagger-php/src/Annotations/MediaType.php | 88 + .../swagger-php/src/Annotations/OpenApi.php | 233 +++ .../swagger-php/src/Annotations/Operation.php | 248 +++ .../swagger-php/src/Annotations/Options.php | 25 + .../swagger-php/src/Annotations/Parameter.php | 299 ++++ .../swagger-php/src/Annotations/Patch.php | 25 + .../swagger-php/src/Annotations/PathItem.php | 151 ++ .../src/Annotations/PathParameter.php | 25 + .../swagger-php/src/Annotations/Post.php | 25 + .../swagger-php/src/Annotations/Property.php | 47 + .../swagger-php/src/Annotations/Put.php | 25 + .../src/Annotations/RequestBody.php | 94 ++ .../swagger-php/src/Annotations/Response.php | 128 ++ .../swagger-php/src/Annotations/Schema.php | 444 +++++ .../src/Annotations/SecurityScheme.php | 136 ++ .../swagger-php/src/Annotations/Server.php | 87 + .../src/Annotations/ServerVariable.php | 87 + .../swagger-php/src/Annotations/Tag.php | 66 + .../swagger-php/src/Annotations/Trace.php | 25 + .../swagger-php/src/Annotations/Xml.php | 95 ++ .../src/Annotations/XmlContent.php | 43 + .../src/Attributes/AdditionalProperties.php | 101 ++ .../swagger-php/src/Attributes/Attachable.php | 16 + .../swagger-php/src/Attributes/Components.php | 47 + .../swagger-php/src/Attributes/Contact.php | 34 + .../swagger-php/src/Attributes/Delete.php | 13 + .../src/Attributes/Discriminator.php | 33 + .../swagger-php/src/Attributes/Examples.php | 42 + .../src/Attributes/ExternalDocumentation.php | 32 + .../swagger-php/src/Attributes/Flow.php | 38 + .../swagger-php/src/Attributes/Get.php | 13 + .../swagger-php/src/Attributes/Head.php | 13 + .../swagger-php/src/Attributes/Header.php | 40 + .../swagger-php/src/Attributes/Info.php | 38 + .../swagger-php/src/Attributes/Items.php | 105 ++ .../src/Attributes/JsonContent.php | 108 ++ .../swagger-php/src/Attributes/License.php | 34 + .../swagger-php/src/Attributes/Link.php | 44 + .../swagger-php/src/Attributes/MediaType.php | 38 + .../swagger-php/src/Attributes/OpenApi.php | 41 + .../src/Attributes/OperationTrait.php | 54 + .../swagger-php/src/Attributes/Options.php | 13 + .../swagger-php/src/Attributes/Parameter.php | 13 + .../src/Attributes/ParameterTrait.php | 60 + .../swagger-php/src/Attributes/Patch.php | 13 + .../swagger-php/src/Attributes/PathItem.php | 38 + .../src/Attributes/PathParameter.php | 13 + .../swagger-php/src/Attributes/Post.php | 13 + .../swagger-php/src/Attributes/Property.php | 107 ++ .../swagger-php/src/Attributes/Put.php | 13 + .../src/Attributes/RequestBody.php | 38 + .../swagger-php/src/Attributes/Response.php | 41 + .../swagger-php/src/Attributes/Schema.php | 106 ++ .../src/Attributes/SecurityScheme.php | 48 + .../swagger-php/src/Attributes/Server.php | 34 + .../src/Attributes/ServerVariable.php | 39 + .../swagger-php/src/Attributes/Tag.php | 33 + .../swagger-php/src/Attributes/Trace.php | 13 + .../swagger-php/src/Attributes/Xml.php | 38 + .../swagger-php/src/Attributes/XmlContent.php | 108 ++ .../zircote/swagger-php/src/Context.php | 274 +++ .../zircote/swagger-php/src/Generator.php | 475 ++++++ .../swagger-php/src/Loggers/ConsoleLogger.php | 97 ++ .../swagger-php/src/Loggers/DefaultLogger.php | 33 + .../src/Processors/AugmentParameters.php | 96 ++ .../src/Processors/AugmentProperties.php | 191 +++ .../src/Processors/AugmentRefs.php | 49 + .../src/Processors/AugmentSchemas.php | 112 ++ .../swagger-php/src/Processors/BuildPaths.php | 60 + .../src/Processors/CleanUnmerged.php | 35 + .../src/Processors/CleanUnusedComponents.php | 96 ++ .../Processors/Concerns/CollectorTrait.php | 67 + .../src/Processors/Concerns/DocblockTrait.php | 171 ++ .../Concerns/MergePropertiesTrait.php | 65 + .../src/Processors/Concerns/TypesTrait.php | 54 + .../src/Processors/DocBlockDescriptions.php | 94 ++ .../src/Processors/ExpandClasses.php | 50 + .../src/Processors/ExpandEnums.php | 106 ++ .../src/Processors/ExpandInterfaces.php | 56 + .../src/Processors/ExpandTraits.php | 81 + .../src/Processors/MergeIntoComponents.php | 34 + .../src/Processors/MergeIntoOpenApi.php | 55 + .../src/Processors/MergeJsonContent.php | 56 + .../src/Processors/MergeXmlContent.php | 56 + .../src/Processors/OperationId.php | 74 + .../zircote/swagger-php/src/Serializer.php | 209 +++ .../vendor/zircote/swagger-php/src/Util.php | 154 ++ 233 files changed, 26073 insertions(+), 10 deletions(-) create mode 100644 Sources/API/documentation/api.php create mode 100644 Sources/API/vendor/doctrine/annotations/LICENSE create mode 100644 Sources/API/vendor/doctrine/annotations/README.md create mode 100644 Sources/API/vendor/doctrine/annotations/composer.json create mode 100644 Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst create mode 100644 Sources/API/vendor/doctrine/annotations/docs/en/custom.rst create mode 100644 Sources/API/vendor/doctrine/annotations/docs/en/index.rst create mode 100644 Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php create mode 100644 Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php create mode 100644 Sources/API/vendor/doctrine/annotations/psalm.xml create mode 100644 Sources/API/vendor/doctrine/deprecations/LICENSE create mode 100644 Sources/API/vendor/doctrine/deprecations/README.md create mode 100644 Sources/API/vendor/doctrine/deprecations/composer.json create mode 100644 Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php create mode 100644 Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php create mode 100644 Sources/API/vendor/doctrine/deprecations/phpcs.xml create mode 100644 Sources/API/vendor/doctrine/lexer/LICENSE create mode 100644 Sources/API/vendor/doctrine/lexer/README.md create mode 100644 Sources/API/vendor/doctrine/lexer/UPGRADE.md create mode 100644 Sources/API/vendor/doctrine/lexer/composer.json create mode 100644 Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php create mode 100644 Sources/API/vendor/doctrine/lexer/src/Token.php create mode 100644 Sources/API/vendor/psr/cache/CHANGELOG.md create mode 100644 Sources/API/vendor/psr/cache/LICENSE.txt create mode 100644 Sources/API/vendor/psr/cache/README.md create mode 100644 Sources/API/vendor/psr/cache/composer.json create mode 100644 Sources/API/vendor/psr/cache/src/CacheException.php create mode 100644 Sources/API/vendor/psr/cache/src/CacheItemInterface.php create mode 100644 Sources/API/vendor/psr/cache/src/CacheItemPoolInterface.php create mode 100644 Sources/API/vendor/psr/cache/src/InvalidArgumentException.php create mode 100644 Sources/API/vendor/symfony/deprecation-contracts/CHANGELOG.md create mode 100644 Sources/API/vendor/symfony/deprecation-contracts/LICENSE create mode 100644 Sources/API/vendor/symfony/deprecation-contracts/README.md create mode 100644 Sources/API/vendor/symfony/deprecation-contracts/composer.json create mode 100644 Sources/API/vendor/symfony/deprecation-contracts/function.php create mode 100644 Sources/API/vendor/symfony/finder/CHANGELOG.md create mode 100644 Sources/API/vendor/symfony/finder/Comparator/Comparator.php create mode 100644 Sources/API/vendor/symfony/finder/Comparator/DateComparator.php create mode 100644 Sources/API/vendor/symfony/finder/Comparator/NumberComparator.php create mode 100644 Sources/API/vendor/symfony/finder/Exception/AccessDeniedException.php create mode 100644 Sources/API/vendor/symfony/finder/Exception/DirectoryNotFoundException.php create mode 100644 Sources/API/vendor/symfony/finder/Finder.php create mode 100644 Sources/API/vendor/symfony/finder/Gitignore.php create mode 100644 Sources/API/vendor/symfony/finder/Glob.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/CustomFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/FilenameFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/LazyIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/PathFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/SortableIterator.php create mode 100644 Sources/API/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php create mode 100644 Sources/API/vendor/symfony/finder/LICENSE create mode 100644 Sources/API/vendor/symfony/finder/README.md create mode 100644 Sources/API/vendor/symfony/finder/SplFileInfo.php create mode 100644 Sources/API/vendor/symfony/finder/composer.json create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/Ctype.php create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/LICENSE create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/README.md create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/bootstrap.php create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/bootstrap80.php create mode 100644 Sources/API/vendor/symfony/polyfill-ctype/composer.json create mode 100644 Sources/API/vendor/symfony/yaml/CHANGELOG.md create mode 100644 Sources/API/vendor/symfony/yaml/Command/LintCommand.php create mode 100644 Sources/API/vendor/symfony/yaml/Dumper.php create mode 100644 Sources/API/vendor/symfony/yaml/Escaper.php create mode 100644 Sources/API/vendor/symfony/yaml/Exception/DumpException.php create mode 100644 Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php create mode 100644 Sources/API/vendor/symfony/yaml/Exception/ParseException.php create mode 100644 Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php create mode 100644 Sources/API/vendor/symfony/yaml/Inline.php create mode 100644 Sources/API/vendor/symfony/yaml/LICENSE create mode 100644 Sources/API/vendor/symfony/yaml/Parser.php create mode 100644 Sources/API/vendor/symfony/yaml/README.md create mode 100644 Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php create mode 100644 Sources/API/vendor/symfony/yaml/Unescaper.php create mode 100644 Sources/API/vendor/symfony/yaml/Yaml.php create mode 100644 Sources/API/vendor/symfony/yaml/composer.json create mode 100644 Sources/API/vendor/zircote/swagger-php/LICENSE create mode 100644 Sources/API/vendor/zircote/swagger-php/README.md create mode 100644 Sources/API/vendor/zircote/swagger-php/composer.json create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/AnnotationFactoryInterface.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Analysis.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Discriminator.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Head.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Header.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Parameter.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/PathItem.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Post.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Property.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/RequestBody.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/Xml.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Components.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Discriminator.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Head.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Header.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Parameter.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/ParameterTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/PathItem.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Post.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Property.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/RequestBody.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/Xml.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Context.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Generator.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Serializer.php create mode 100644 Sources/API/vendor/zircote/swagger-php/src/Util.php diff --git a/Sources/API/composer.json b/Sources/API/composer.json index 0158a81..6addf56 100644 --- a/Sources/API/composer.json +++ b/Sources/API/composer.json @@ -1,6 +1,7 @@ { "require": { "slim/slim": "4.*", - "slim/psr7": "^1.6" + "slim/psr7": "^1.6", + "zircote/swagger-php": "^4.5" } } diff --git a/Sources/API/composer.lock b/Sources/API/composer.lock index 8ff62b8..acf5477 100644 --- a/Sources/API/composer.lock +++ b/Sources/API/composer.lock @@ -4,8 +4,205 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3330638b65a325a8387769d50108b43a", + "content-hash": "07907c3d50700ee017ad7949856672cc", "packages": [ + { + "name": "doctrine/annotations", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/3587ab58646bc515b2e03bbd3cfcd3682e8df5bf", + "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.0" + }, + "time": "2022-12-11T18:25:48+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" + }, + { + "name": "doctrine/lexer", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/3cf140b81e55d5d640f73367d829db7e3023ef69", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-12-11T10:51:23+00:00" + }, { "name": "fig/http-message-util", "version": "1.1.5", @@ -112,6 +309,55 @@ }, "time": "2018-02-13T20:26:39+00:00" }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -678,6 +924,219 @@ ], "time": "2022-11-06T16:33:39+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-25T10:21:52+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-09T08:55:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.27.0", @@ -760,6 +1219,158 @@ } ], "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f2570f21bd4adc3589aa3133323273995109bae0", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-25T19:00:27+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.7", + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.5.1" + }, + "time": "2022-11-09T00:17:13+00:00" } ], "packages-dev": [], diff --git a/Sources/API/documentation/api.php b/Sources/API/documentation/api.php new file mode 100644 index 0000000..c2daa3d --- /dev/null +++ b/Sources/API/documentation/api.php @@ -0,0 +1,6 @@ +toJSON(); +?> \ No newline at end of file diff --git a/Sources/API/routes/Inscrit.php b/Sources/API/routes/Inscrit.php index e7d9ba8..9562879 100644 --- a/Sources/API/routes/Inscrit.php +++ b/Sources/API/routes/Inscrit.php @@ -1,11 +1,22 @@ get('/Inscrit', function(Request $request, Response $response){ +/** +* @OA\Get(path="/api/Inscrit", +* @OA\Response(response="200", description="Succes") +* @OA\Response(response="500", description="Bdd Error") +* ) +*/ +$app->get('/Inscrit/', function(Request $request, Response $response){ $query = "SELECT * FROM Inscrit"; try{ @@ -30,7 +41,7 @@ $app->get('/Inscrit', function(Request $request, Response $response){ } }); -$app->post('/Inscrit/one', function(Request $request, Response $response,array $args){ +$app->post('/Inscrit/FromMail/', function(Request $request, Response $response,array $args){ $mail = $request->getParsedBody()["email"]; $query = 'SELECT * FROM Inscrit WHERE mail=:mail'; @@ -58,4 +69,72 @@ $app->post('/Inscrit/one', function(Request $request, Response $response,array $ ->withStatus(500); } }); + +$app->put('/Inscrit/UpdatePassword/', function(Request $request, Response $response, array $args){ + $mail = $request->getParsedbody(); + $password = $request->getParsedBody()["password"]; + + var_dump($mail); + + $query = 'UPDATE Inscrit SET mdp=:password WHERE mail=:mail'; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->prepare($query); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); + $stmt->bindValue(':password', $password, PDO::PARAM_STR); + + $result = $stmt->execute(); + + $db = null; + $response->getBody()->write(json_encode($result)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}); + +$app->post('/Inscrit/add/', function(Request $request, Response $response, array $args){ + $nom = $request->getParsedBody()["nom"]; + $prenom = $request->getParsedBody()["prenom"]; + $mail = $request->getParsedbody()["email"]; + $password = $request->getParsedBody()["password"]; + + $query = "INSERT INTO Inscrit (nom, prenom, mail, mdp) VALUES (:nom, :prenom, :mail, :password);"; + + try{ + $db = new Database(); + $conn = $db->connect(); + + $stmt = $conn->prepare($query); + $stmt->bindValue(':nom', $nom, PDO::PARAM_STR); + $stmt->bindValue(':prenom', $prenom, PDO::PARAM_STR); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); + $stmt->bindValue(':password', $password, PDO::PARAM_STR); + + $result = $stmt->execute(); + + $db = null; + $response->getBody()->write(json_encode($result)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(200); + } catch(PDOException $e){ + $error = array("message" => $e->getMessage()); + + $response->getBody()->write(json_encode($error)); + return $response + ->withHeader('content-type', 'application/json') + ->withStatus(500); + } +}) ?> \ No newline at end of file diff --git a/Sources/API/vendor/composer/autoload_files.php b/Sources/API/vendor/composer/autoload_files.php index 33dd33f..a9948aa 100644 --- a/Sources/API/vendor/composer/autoload_files.php +++ b/Sources/API/vendor/composer/autoload_files.php @@ -6,7 +6,9 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', ); diff --git a/Sources/API/vendor/composer/autoload_psr4.php b/Sources/API/vendor/composer/autoload_psr4.php index 5bba2b0..7bd4ca5 100644 --- a/Sources/API/vendor/composer/autoload_psr4.php +++ b/Sources/API/vendor/composer/autoload_psr4.php @@ -7,12 +7,20 @@ $baseDir = dirname($vendorDir); return array( 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'), 'Slim\\' => array($vendorDir . '/slim/slim/Slim'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'OpenApi\\' => array($vendorDir . '/zircote/swagger-php/src'), 'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'), 'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'), + 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), ); diff --git a/Sources/API/vendor/composer/autoload_static.php b/Sources/API/vendor/composer/autoload_static.php index 2d0493e..b5ab53d 100644 --- a/Sources/API/vendor/composer/autoload_static.php +++ b/Sources/API/vendor/composer/autoload_static.php @@ -7,15 +7,20 @@ namespace Composer\Autoload; class ComposerStaticInita934429c0ea4f4482346c5d296943a81 { public static $files = array ( - 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\Finder\\' => 25, 'Slim\\Psr7\\' => 10, 'Slim\\' => 5, ), @@ -25,12 +30,23 @@ class ComposerStaticInita934429c0ea4f4482346c5d296943a81 'Psr\\Http\\Server\\' => 16, 'Psr\\Http\\Message\\' => 17, 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + ), + 'O' => + array ( + 'OpenApi\\' => 8, ), 'F' => array ( 'Fig\\Http\\Message\\' => 17, 'FastRoute\\' => 10, ), + 'D' => + array ( + 'Doctrine\\Deprecations\\' => 22, + 'Doctrine\\Common\\Lexer\\' => 22, + 'Doctrine\\Common\\Annotations\\' => 28, + ), ); public static $prefixDirsPsr4 = array ( @@ -38,6 +54,18 @@ class ComposerStaticInita934429c0ea4f4482346c5d296943a81 array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), 'Slim\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/slim/psr7/src', @@ -64,6 +92,14 @@ class ComposerStaticInita934429c0ea4f4482346c5d296943a81 array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'OpenApi\\' => + array ( + 0 => __DIR__ . '/..' . '/zircote/swagger-php/src', + ), 'Fig\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/fig/http-message-util/src', @@ -72,6 +108,18 @@ class ComposerStaticInita934429c0ea4f4482346c5d296943a81 array ( 0 => __DIR__ . '/..' . '/nikic/fast-route/src', ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/src', + ), + 'Doctrine\\Common\\Annotations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', + ), ); public static $classMap = array ( diff --git a/Sources/API/vendor/composer/installed.json b/Sources/API/vendor/composer/installed.json index b69dd50..34e0075 100644 --- a/Sources/API/vendor/composer/installed.json +++ b/Sources/API/vendor/composer/installed.json @@ -1,5 +1,211 @@ { "packages": [ + { + "name": "doctrine/annotations", + "version": "1.14.0", + "version_normalized": "1.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/3587ab58646bc515b2e03bbd3cfcd3682e8df5bf", + "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "time": "2022-12-11T18:25:48+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.0" + }, + "install-path": "../doctrine/annotations" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2022-05-02T15:47:09+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "doctrine/lexer", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/3cf140b81e55d5d640f73367d829db7e3023ef69", + "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "time": "2022-12-11T10:51:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "install-path": "../doctrine/lexer" + }, { "name": "fig/http-message-util", "version": "1.1.5", @@ -112,6 +318,58 @@ }, "install-path": "../nikic/fast-route" }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, { "name": "psr/container", "version": "2.0.2", @@ -705,6 +963,228 @@ ], "install-path": "../slim/slim" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2022-11-25T10:21:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/finder", + "version": "v6.2.0", + "version_normalized": "6.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "time": "2022-10-09T08:55:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, { "name": "symfony/polyfill-php80", "version": "v1.27.0", @@ -790,6 +1270,164 @@ } ], "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/yaml", + "version": "v6.2.0", + "version_normalized": "6.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f2570f21bd4adc3589aa3133323273995109bae0", + "reference": "f2570f21bd4adc3589aa3133323273995109bae0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "time": "2022-11-25T19:00:27+00:00", + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/yaml" + }, + { + "name": "zircote/swagger-php", + "version": "4.5.1", + "version_normalized": "4.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "reference": "eb84fb4d65a327e604812fbddc6c27f69b9ed6e2", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.7", + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "time": "2022-11-09T00:17:13+00:00", + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.5.1" + }, + "install-path": "../zircote/swagger-php" } ], "dev": true, diff --git a/Sources/API/vendor/composer/installed.php b/Sources/API/vendor/composer/installed.php index e795d48..60cf54c 100644 --- a/Sources/API/vendor/composer/installed.php +++ b/Sources/API/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '7167670b929c4f3fec015404893ade05128b1ea4', + 'reference' => 'f8caddc10d70975a9f687fac5d007b340a7a8699', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,34 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '7167670b929c4f3fec015404893ade05128b1ea4', + 'reference' => 'f8caddc10d70975a9f687fac5d007b340a7a8699', + 'dev_requirement' => false, + ), + 'doctrine/annotations' => array( + 'pretty_version' => '1.14.0', + 'version' => '1.14.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/annotations', + 'aliases' => array(), + 'reference' => '3587ab58646bc515b2e03bbd3cfcd3682e8df5bf', + 'dev_requirement' => false, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de', + 'dev_requirement' => false, + ), + 'doctrine/lexer' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/lexer', + 'aliases' => array(), + 'reference' => '3cf140b81e55d5d640f73367d829db7e3023ef69', 'dev_requirement' => false, ), 'fig/http-message-util' => array( @@ -37,6 +64,15 @@ 'reference' => '181d480e08d9476e61381e04a71b34dc0432e812', 'dev_requirement' => false, ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'dev_requirement' => false, + ), 'psr/container' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', @@ -130,6 +166,33 @@ 'reference' => 'b0f4ca393ea037be9ac7292ba7d0a34d18bac0c7', 'dev_requirement' => false, ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.2.0', + 'version' => '3.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => '1ee04c65529dea5d8744774d474e7cbd2f1206d3', + 'dev_requirement' => false, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v6.2.0', + 'version' => '6.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'reference' => 'eb2355f69519e4ef33f1835bca4c935f5d42e570', + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a', + 'dev_requirement' => false, + ), 'symfony/polyfill-php80' => array( 'pretty_version' => 'v1.27.0', 'version' => '1.27.0.0', @@ -139,5 +202,23 @@ 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', 'dev_requirement' => false, ), + 'symfony/yaml' => array( + 'pretty_version' => 'v6.2.0', + 'version' => '6.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/yaml', + 'aliases' => array(), + 'reference' => 'f2570f21bd4adc3589aa3133323273995109bae0', + 'dev_requirement' => false, + ), + 'zircote/swagger-php' => array( + 'pretty_version' => '4.5.1', + 'version' => '4.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../zircote/swagger-php', + 'aliases' => array(), + 'reference' => 'eb84fb4d65a327e604812fbddc6c27f69b9ed6e2', + 'dev_requirement' => false, + ), ), ); diff --git a/Sources/API/vendor/composer/platform_check.php b/Sources/API/vendor/composer/platform_check.php index adfb472..4c3a5d6 100644 --- a/Sources/API/vendor/composer/platform_check.php +++ b/Sources/API/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 80000)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/Sources/API/vendor/doctrine/annotations/LICENSE b/Sources/API/vendor/doctrine/annotations/LICENSE new file mode 100644 index 0000000..5e781fc --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/annotations/README.md b/Sources/API/vendor/doctrine/annotations/README.md new file mode 100644 index 0000000..6b8c035 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/README.md @@ -0,0 +1,24 @@ +⚠️ PHP 8 introduced +[attributes](https://www.php.net/manual/en/language.attributes.overview.php), +which are a native replacement for annotations. As such, this library is +considered feature complete, and should receive exclusively bugfixes and +security fixes. + +# Doctrine Annotations + +[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) +[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) +[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) +[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations) + +Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). + +## Documentation + +See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). + +## Contributing + +When making a pull request, make sure your changes follow the +[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). diff --git a/Sources/API/vendor/doctrine/annotations/composer.json b/Sources/API/vendor/doctrine/annotations/composer.json new file mode 100644 index 0000000..e322d82 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/composer.json @@ -0,0 +1,72 @@ +{ + "name": "doctrine/annotations", + "description": "Docblock Annotations Parser", + "license": "MIT", + "type": "library", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "require": { + "php": "^7.1 || ^8.0", + "ext-tokenizer": "*", + "doctrine/lexer": "^1 || ^2", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", + "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" + }, + "files": [ + "tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php", + "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst b/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst new file mode 100644 index 0000000..2c3c428 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/annotations.rst @@ -0,0 +1,252 @@ +Handling Annotations +==================== + +There are several different approaches to handling annotations in PHP. +Doctrine Annotations maps docblock annotations to PHP classes. Because +not all docblock annotations are used for metadata purposes a filter is +applied to ignore or skip classes that are not Doctrine annotations. + +Take a look at the following code snippet: + +.. code-block:: php + + namespace MyProject\Entities; + + use Doctrine\ORM\Mapping AS ORM; + use Symfony\Component\Validator\Constraints AS Assert; + + /** + * @author Benjamin Eberlei + * @ORM\Entity + * @MyProject\Annotations\Foobarable + */ + class User + { + /** + * @ORM\Id @ORM\Column @ORM\GeneratedValue + * @dummy + * @var int + */ + private $id; + + /** + * @ORM\Column(type="string") + * @Assert\NotEmpty + * @Assert\Email + * @var string + */ + private $email; + } + +In this snippet you can see a variety of different docblock annotations: + +- Documentation annotations such as ``@var`` and ``@author``. These + annotations are ignored and never considered for throwing an + exception due to wrongly used annotations. +- Annotations imported through use statements. The statement ``use + Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace + available as ``@ORM\ClassName``. Same goes for the import of + ``@Assert``. +- The ``@dummy`` annotation. It is not a documentation annotation and + not ignored. For Doctrine Annotations it is not entirely clear how + to handle this annotation. Depending on the configuration an exception + (unknown annotation) will be thrown when parsing this annotation. +- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. + This is transformed directly into the given class name. + +How are these annotations loaded? From looking at the code you could +guess that the ORM Mapping, Assert Validation and the fully qualified +annotation can just be loaded using +the defined PHP autoloaders. This is not the case however: For error +handling reasons every check for class existence inside the +``AnnotationReader`` sets the second parameter $autoload +of ``class_exists($name, $autoload)`` to false. To work flawlessly the +``AnnotationReader`` requires silent autoloaders which many autoloaders are +not. Silent autoloading is NOT part of the `PSR-0 specification +`_ +for autoloading. + +This is why Doctrine Annotations uses its own autoloading mechanism +through a global registry. If you are wondering about the annotation +registry being global, there is no other way to solve the architectural +problems of autoloading annotation classes in a straightforward fashion. +Additionally if you think about PHP autoloading then you recognize it is +a global as well. + +To anticipate the configuration section, making the above PHP class work +with Doctrine Annotations requires this setup: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); + AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); + AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); + + $reader = new AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('dummy'); + +The second block with the annotation registry calls registers all the +three different annotation namespaces that are used. +Doctrine Annotations saves all its annotations in a single file, that is +why ``AnnotationRegistry#registerFile`` is used in contrast to +``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 +compatible loading mechanism for class to file names. + +In the third block, we create the actual ``AnnotationReader`` instance. +Note that we also add ``dummy`` to the global list of ignored +annotations for which we do not throw exceptions. Setting this is +necessary in our example case, otherwise ``@dummy`` would trigger an +exception to be thrown during the parsing of the docblock of +``MyProject\Entities\User#id``. + +Setup and Configuration +----------------------- + +To use the annotations library is simple, you just need to create a new +``AnnotationReader`` instance: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + +This creates a simple annotation reader with no caching other than in +memory (in php arrays). Since parsing docblocks can be expensive you +should cache this process by using a caching reader. + +To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``. +This reader decorates the original reader and stores all annotations in a PSR-6 +cache: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\PsrCachedReader; + + $cache = ... // instantiate a PSR-6 Cache pool + + $reader = new PsrCachedReader( + new AnnotationReader(), + $cache, + $debug = true + ); + +The ``debug`` flag is used here as well to invalidate the cache files +when the PHP class with annotations changed and should be used during +development. + +.. warning :: + + The ``AnnotationReader`` works and caches under the + assumption that all annotations of a doc-block are processed at + once. That means that annotation classes that do not exist and + aren't loaded and cannot be autoloaded (using the + AnnotationRegistry) would never be visible and not accessible if a + cache is used unless the cache is cleared and the annotations + requested again, this time with all annotations defined. + +By default the annotation reader returns a list of annotations with +numeric indexes. If you want your annotations to be indexed by their +class name you can wrap the reader in an ``IndexedReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\IndexedReader; + + $reader = new IndexedReader(new AnnotationReader()); + +.. warning:: + + You should never wrap the indexed reader inside a cached reader, + only the other way around. This way you can re-use the cache with + indexed or numeric keys, otherwise your code may experience failures + due to caching in a numerical or indexed format. + +Registering Annotations +~~~~~~~~~~~~~~~~~~~~~~~ + +As explained in the introduction, Doctrine Annotations uses its own +autoloading mechanism to determine if a given annotation has a +corresponding PHP class that can be autoloaded. For annotation +autoloading you have to configure the +``Doctrine\Common\Annotations\AnnotationRegistry``. There are three +different mechanisms to configure annotation autoloading: + +- Calling ``AnnotationRegistry#registerFile($file)`` to register a file + that contains one or more annotation classes. +- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = + null)`` to register that the given namespace contains annotations and + that their base directory is located at the given $dirs or in the + include path if ``NULL`` is passed. The given directories should *NOT* + be the directory where classes of the namespace are in, but the base + directory of the root namespace. The AnnotationRegistry uses a + namespace to directory separator approach to resolve the correct path. +- Calling ``AnnotationRegistry#registerLoader($callable)`` to register + an autoloader callback. The callback accepts the class as first and + only parameter and has to return ``true`` if the corresponding file + was found and included. + +.. note:: + + Loaders have to fail silently, if a class is not found even if it + matches for example the namespace prefix of that loader. Never is a + loader to throw a warning or exception if the loading failed + otherwise parsing doc block annotations will become a huge pain. + +A sample loader callback could look like: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationRegistry; + use Symfony\Component\ClassLoader\UniversalClassLoader; + + AnnotationRegistry::registerLoader(function($class) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + + if (file_exists("/my/base/path/" . $file)) { + // file_exists() makes sure that the loader fails silently + require "/my/base/path/" . $file; + } + }); + + $loader = new UniversalClassLoader(); + AnnotationRegistry::registerLoader(array($loader, "loadClass")); + + +Ignoring missing exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default an exception is thrown from the ``AnnotationReader`` if an +annotation was found that: + +- is not part of the list of ignored "documentation annotations"; +- was not imported through a use statement; +- is not a fully qualified class that exists. + +You can disable this behavior for specific names if your docblocks do +not follow strict requirements: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('foo'); + +PHP Imports +~~~~~~~~~~~ + +By default the annotation reader parses the use-statement of a php file +to gain access to the import rules and register them for the annotation +processing. Only if you are using PHP Imports can you validate the +correct usage of annotations and throw exceptions if you misspelled an +annotation. This mechanism is enabled by default. + +To ease the upgrade path, we still allow you to disable this mechanism. +Note however that we will remove this in future versions: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setEnabledPhpImports(false); diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst b/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst new file mode 100644 index 0000000..e8f79af --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/custom.rst @@ -0,0 +1,443 @@ +Custom Annotation Classes +========================= + +If you want to define your own annotations, you just have to group them +in a namespace and register this namespace in the ``AnnotationRegistry``. +Annotation classes have to contain a class-level docblock with the text +``@Annotation``: + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** @Annotation */ + class Bar + { + // some code + } + +Inject annotation values +------------------------ + +The annotation parser checks if the annotation constructor has arguments, +if so then it will pass the value array, otherwise it will try to inject +values into public properties directly: + + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * + * Some Annotation using a constructor + */ + class Bar + { + private $foo; + + public function __construct(array $values) + { + $this->foo = $values['foo']; + } + } + + /** + * @Annotation + * + * Some Annotation without a constructor + */ + class Foo + { + public $bar; + } + +Optional: Constructors with Named Parameters +-------------------------------------------- + +Starting with Annotations v1.11 a new annotation instantiation strategy +is available that aims at compatibility of Annotation classes with the PHP 8 +attribute feature. You need to declare a constructor with regular parameter +names that match the named arguments in the annotation syntax. + +To enable this feature, you can tag your annotation class with +``@NamedArgumentConstructor`` (available from v1.12) or implement the +``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface +(available from v1.11 and deprecated as of v1.12). +When using the ``@NamedArgumentConstructor`` tag, the first argument of the +constructor is considered as the default one. + + +Usage with the ``@NamedArgumentConstructor`` tag + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @NamedArgumentConstructor + */ + class Bar implements NamedArgumentConstructorAnnotation + { + private $foo; + + public function __construct(string $foo) + { + $this->foo = $foo; + } + } + + /** Usable with @Bar(foo="baz") */ + /** Usable with @Bar("baz") */ + +In combination with PHP 8's constructor property promotion feature +you can simplify this to: + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @NamedArgumentConstructor + */ + class Bar implements NamedArgumentConstructorAnnotation + { + public function __construct(private string $foo) {} + } + + +Usage with the +``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` +interface (v1.11, deprecated as of v1.12): +.. code-block:: php + + namespace MyCompany\Annotations; + + use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; + + /** @Annotation */ + class Bar implements NamedArgumentConstructorAnnotation + { + private $foo; + + public function __construct(private string $foo) {} + } + + /** Usable with @Bar(foo="baz") */ + +Annotation Target +----------------- + +``@Target`` indicates the kinds of class elements to which an annotation +type is applicable. Then you could define one or more targets: + +- ``CLASS`` Allowed in class docblocks +- ``PROPERTY`` Allowed in property docblocks +- ``METHOD`` Allowed in the method docblocks +- ``FUNCTION`` Allowed in function dockblocks +- ``ALL`` Allowed in class, property, method and function docblocks +- ``ANNOTATION`` Allowed inside other annotations + +If the annotations is not allowed in the current context, an +``AnnotationException`` is thrown. + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + // some code + } + + /** + * @Annotation + * @Target("CLASS") + */ + class Foo + { + // some code + } + +Attribute types +--------------- + +The annotation parser checks the given parameters using the phpdoc +annotation ``@var``, The data type could be validated using the ``@var`` +annotation on the annotation properties or using the ``@Attributes`` and +``@Attribute`` annotations. + +If the data type does not match you get an ``AnnotationException`` + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + /** @var mixed */ + public $mixed; + + /** @var boolean */ + public $boolean; + + /** @var bool */ + public $bool; + + /** @var float */ + public $float; + + /** @var string */ + public $string; + + /** @var integer */ + public $integer; + + /** @var array */ + public $array; + + /** @var SomeAnnotationClass */ + public $annotation; + + /** @var array */ + public $arrayOfIntegers; + + /** @var array */ + public $arrayOfAnnotations; + } + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + * @Attributes({ + * @Attribute("stringProperty", type = "string"), + * @Attribute("annotProperty", type = "SomeAnnotationClass"), + * }) + */ + class Foo + { + public function __construct(array $values) + { + $this->stringProperty = $values['stringProperty']; + $this->annotProperty = $values['annotProperty']; + } + + // some code + } + +Annotation Required +------------------- + +``@Required`` indicates that the field must be specified when the +annotation is used. If it is not used you get an ``AnnotationException`` +stating that this value can not be null. + +Declaring a required field: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Foo + { + /** @Required */ + public $requiredField; + } + +Usage: + +.. code-block:: php + + /** @Foo(requiredField="value") */ + public $direction; // Valid + + /** @Foo */ + public $direction; // Required field missing, throws an AnnotationException + + +Enumerated values +----------------- + +- An annotation property marked with ``@Enum`` is a field that accepts a + fixed set of scalar values. +- You should use ``@Enum`` fields any time you need to represent fixed + values. +- The annotation parser checks the given value and throws an + ``AnnotationException`` if the value does not match. + + +Declaring an enumerated property: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Direction + { + /** + * @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) + */ + public $value; + } + +Annotation usage: + +.. code-block:: php + + /** @Direction("NORTH") */ + public $direction; // Valid value + + /** @Direction("NORTHEAST") */ + public $direction; // Invalid value, throws an AnnotationException + + +Constants +--------- + +The use of constants and class constants is available on the annotations +parser. + +The following usages are allowed: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + use MyCompany\Entity\SomeClass; + + /** + * @Foo(PHP_EOL) + * @Bar(Bar::FOO) + * @Foo({SomeClass::FOO, SomeClass::BAR}) + * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) + */ + class User + { + } + + +Be careful with constants and the cache ! + +.. note:: + + The cached reader will not re-evaluate each time an annotation is + loaded from cache. When a constant is changed the cache must be + cleaned. + + +Usage +----- + +Using the library API is simple. Using the annotations described in the +previous section, you can now annotate other classes with your +annotations: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + + /** + * @Foo(bar="foo") + * @Bar(foo="bar") + */ + class User + { + } + +Now we can write a script to get the annotations above: + +.. code-block:: php + + $reflClass = new ReflectionClass('MyCompany\Entity\User'); + $classAnnotations = $reader->getClassAnnotations($reflClass); + + foreach ($classAnnotations AS $annot) { + if ($annot instanceof \MyCompany\Annotations\Foo) { + echo $annot->bar; // prints "foo"; + } else if ($annot instanceof \MyCompany\Annotations\Bar) { + echo $annot->foo; // prints "bar"; + } + } + +You have a complete API for retrieving annotation class instances from a +class, property or method docblock: + + +Reader API +~~~~~~~~~~ + +Access all annotations of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotations(\ReflectionClass $class); + +Access one annotation of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotation(\ReflectionClass $class, $annotationName); + +Access all annotations of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotations(\ReflectionMethod $method); + +Access one annotation of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + +Access all annotations of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotations(\ReflectionProperty $property); + +Access one annotation of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); + +Access all annotations of a function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getFunctionAnnotations(\ReflectionFunction $property); + +Access one annotation of a function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName); diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/index.rst b/Sources/API/vendor/doctrine/annotations/docs/en/index.rst new file mode 100644 index 0000000..7caffb5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/index.rst @@ -0,0 +1,110 @@ +Deprecation notice +================== + +PHP 8 introduced `attributes +`_, +which are a native replacement for annotations. As such, this library is +considered feature complete, and should receive exclusively bugfixes and +security fixes. + +Introduction +============ + +Doctrine Annotations allows to implement custom annotation +functionality for PHP classes and functions. + +.. code-block:: php + + class Foo + { + /** + * @MyAnnotation(myProperty="value") + */ + private $bar; + } + +Annotations aren't implemented in PHP itself which is why this component +offers a way to use the PHP doc-blocks as a place for the well known +annotation syntax using the ``@`` char. + +Annotations in Doctrine are used for the ORM configuration to build the +class mapping, but it can be used in other projects for other purposes +too. + +Installation +============ + +You can install the Annotation component with composer: + +.. code-block:: + +   $ composer require doctrine/annotations + +Create an annotation class +========================== + +An annotation class is a representation of the later used annotation +configuration in classes. The annotation class of the previous example +looks like this: + +.. code-block:: php + + /** + * @Annotation + */ + final class MyAnnotation + { + public $myProperty; + } + +The annotation class is declared as an annotation by ``@Annotation``. + +:ref:`Read more about custom annotations. ` + +Reading annotations +=================== + +The access to the annotations happens by reflection of the class or function +containing them. There are multiple reader-classes implementing the +``Doctrine\Common\Annotations\Reader`` interface, that can access the +annotations of a class. A common one is +``Doctrine\Common\Annotations\AnnotationReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + // Deprecated and will be removed in 2.0 but currently needed + AnnotationRegistry::registerLoader('class_exists'); + + $reflectionClass = new ReflectionClass(Foo::class); + $property = $reflectionClass->getProperty('bar'); + + $reader = new AnnotationReader(); + $myAnnotation = $reader->getPropertyAnnotation( + $property, + MyAnnotation::class + ); + + echo $myAnnotation->myProperty; // result: "value" + +Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works +if you already have an autoloader configured (i.e. composer autoloader). +Otherwise, :ref:`please take a look to the other annotation autoload mechanisms `. + +A reader has multiple methods to access the annotations of a class or +function. + +:ref:`Read more about handling annotations. ` + +IDE Support +----------- + +Some IDEs already provide support for annotations: + +- Eclipse via the `Symfony2 Plugin `_ +- PhpStorm via the `PHP Annotations Plugin `_ or the `Symfony Plugin `_ + +.. _Read more about handling annotations.: annotations +.. _Read more about custom annotations.: custom diff --git a/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst b/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst new file mode 100644 index 0000000..6f5d13c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/docs/en/sidebar.rst @@ -0,0 +1,6 @@ +.. toctree:: + :depth: 3 + + index + annotations + custom diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 0000000..9cae3da --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,57 @@ + $data Key-value for properties to be defined in this class. */ + final public function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name. + * + * @throws BadMethodCallException + */ + public function __get($name) + { + throw new BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + ); + } + + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unknown property name. + * @param mixed $value Property value. + * + * @throws BadMethodCallException + */ + public function __set($name, $value) + { + throw new BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + ); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php new file mode 100644 index 0000000..b1f8514 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php @@ -0,0 +1,21 @@ + */ + public $value; +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php new file mode 100644 index 0000000..6f24d9f --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php @@ -0,0 +1,69 @@ + */ + public $value; + + /** + * Literal target declaration. + * + * @var mixed[] + */ + public $literal; + + /** + * @phpstan-param array{literal?: mixed[], value: list} $values + * + * @throws InvalidArgumentException + */ + public function __construct(array $values) + { + if (! isset($values['literal'])) { + $values['literal'] = []; + } + + foreach ($values['value'] as $var) { + if (! is_scalar($var)) { + throw new InvalidArgumentException(sprintf( + '@Enum supports only scalar values "%s" given.', + is_object($var) ? get_class($var) : gettype($var) + )); + } + } + + foreach ($values['literal'] as $key => $var) { + if (! in_array($key, $values['value'])) { + throw new InvalidArgumentException(sprintf( + 'Undefined enumerator value "%s" for literal "%s".', + $key, + $var + )); + } + } + + $this->value = $values['value']; + $this->literal = $values['literal']; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php new file mode 100644 index 0000000..97a15c2 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php @@ -0,0 +1,43 @@ + */ + public $names; + + /** + * @phpstan-param array{value: string|list} $values + * + * @throws RuntimeException + */ + public function __construct(array $values) + { + if (is_string($values['value'])) { + $values['value'] = [$values['value']]; + } + + if (! is_array($values['value'])) { + throw new RuntimeException(sprintf( + '@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', + json_encode($values['value']) + )); + } + + $this->names = $values['value']; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php new file mode 100644 index 0000000..1690601 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php @@ -0,0 +1,13 @@ + */ + private static $map = [ + 'ALL' => self::TARGET_ALL, + 'CLASS' => self::TARGET_CLASS, + 'METHOD' => self::TARGET_METHOD, + 'PROPERTY' => self::TARGET_PROPERTY, + 'FUNCTION' => self::TARGET_FUNCTION, + 'ANNOTATION' => self::TARGET_ANNOTATION, + ]; + + /** @phpstan-var list */ + public $value; + + /** + * Targets as bitmask. + * + * @var int + */ + public $targets; + + /** + * Literal target declaration. + * + * @var string + */ + public $literal; + + /** + * @phpstan-param array{value?: string|list} $values + * + * @throws InvalidArgumentException + */ + public function __construct(array $values) + { + if (! isset($values['value'])) { + $values['value'] = null; + } + + if (is_string($values['value'])) { + $values['value'] = [$values['value']]; + } + + if (! is_array($values['value'])) { + throw new InvalidArgumentException( + sprintf( + '@Target expects either a string value, or an array of strings, "%s" given.', + is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) + ) + ); + } + + $bitmask = 0; + foreach ($values['value'] as $literal) { + if (! isset(self::$map[$literal])) { + throw new InvalidArgumentException( + sprintf( + 'Invalid Target "%s". Available targets: [%s]', + $literal, + implode(', ', array_keys(self::$map)) + ) + ); + } + + $bitmask |= self::$map[$literal]; + } + + $this->targets = $bitmask; + $this->value = $values['value']; + $this->literal = implode(', ', $this->value); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php new file mode 100644 index 0000000..dcdfe4d --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -0,0 +1,167 @@ + $available + * + * @return AnnotationException + */ + public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) + { + return new self(sprintf( + '[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', + $attributeName, + $annotationName, + $context, + implode(', ', $available), + is_object($given) ? get_class($given) : $given + )); + } + + /** @return AnnotationException */ + public static function optimizerPlusSaveComments() + { + return new self( + 'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.' + ); + } + + /** @return AnnotationException */ + public static function optimizerPlusLoadComments() + { + return new self( + 'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.' + ); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 0000000..1f538ee --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,389 @@ + + */ + private static $globalImports = [ + 'ignoreannotation' => Annotation\IgnoreAnnotation::class, + ]; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNamespaces = []; + + /** + * Add a new annotation to the globally ignored annotation names with regard to exception handling. + * + * @param string $name + */ + public static function addGlobalIgnoredName($name) + { + self::$globalIgnoredNames[$name] = true; + } + + /** + * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. + * + * @param string $namespace + */ + public static function addGlobalIgnoredNamespace($namespace) + { + self::$globalIgnoredNamespaces[$namespace] = true; + } + + /** + * Annotations parser. + * + * @var DocParser + */ + private $parser; + + /** + * Annotations parser used to collect parsing metadata. + * + * @var DocParser + */ + private $preParser; + + /** + * PHP parser used to collect imports. + * + * @var PhpParser + */ + private $phpParser; + + /** + * In-memory cache mechanism to store imported annotations per class. + * + * @psalm-var array<'class'|'function', array>> + */ + private $imports = []; + + /** + * In-memory cache mechanism to store ignored annotations per class. + * + * @psalm-var array<'class'|'function', array>> + */ + private $ignoredAnnotationNames = []; + + /** + * Initializes a new AnnotationReader. + * + * @throws AnnotationException + */ + public function __construct(?DocParser $parser = null) + { + if ( + extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || + ini_get('opcache.save_comments') === '0') + ) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + // Make sure that the IgnoreAnnotation annotation is loaded + class_exists(IgnoreAnnotation::class); + + $this->parser = $parser ?: new DocParser(); + + $this->preParser = new DocParser(); + + $this->preParser->setImports(self::$globalImports); + $this->preParser->setIgnoreNotImportedAnnotations(true); + $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); + + $this->phpParser = new PhpParser(); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $this->parser->setTarget(Target::TARGET_CLASS); + $this->parser->setImports($this->getImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $context = 'property ' . $class->getName() . '::$' . $property->getName(); + + $this->parser->setTarget(Target::TARGET_PROPERTY); + $this->parser->setImports($this->getPropertyImports($property)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($property->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $this->parser->setTarget(Target::TARGET_METHOD); + $this->parser->setImports($this->getMethodImports($method)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($method->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets the annotations applied to a function. + * + * @phpstan-return list An array of Annotations. + */ + public function getFunctionAnnotations(ReflectionFunction $function): array + { + $context = 'function ' . $function->getName(); + + $this->parser->setTarget(Target::TARGET_FUNCTION); + $this->parser->setImports($this->getImports($function)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($function->getDocComment(), $context); + } + + /** + * Gets a function annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) + { + $annotations = $this->getFunctionAnnotations($function); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Returns the ignored annotations for the given class or function. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @return array + */ + private function getIgnoredAnnotationNames($reflection): array + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + if (isset($this->ignoredAnnotationNames[$type][$name])) { + return $this->ignoredAnnotationNames[$type][$name]; + } + + $this->collectParsingMetadata($reflection); + + return $this->ignoredAnnotationNames[$type][$name]; + } + + /** + * Retrieves imports for a class or a function. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @return array + */ + private function getImports($reflection): array + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + if (isset($this->imports[$type][$name])) { + return $this->imports[$type][$name]; + } + + $this->collectParsingMetadata($reflection); + + return $this->imports[$type][$name]; + } + + /** + * Retrieves imports for methods. + * + * @return array + */ + private function getMethodImports(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $classImports = $this->getImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if ( + ! $trait->hasMethod($method->getName()) + || $trait->getFileName() !== $method->getFileName() + ) { + continue; + } + + $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); + } + + return array_merge($classImports, $traitImports); + } + + /** + * Retrieves imports for properties. + * + * @return array + */ + private function getPropertyImports(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $classImports = $this->getImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if (! $trait->hasProperty($property->getName())) { + continue; + } + + $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); + } + + return array_merge($classImports, $traitImports); + } + + /** + * Collects parsing metadata for a given class or function. + * + * @param ReflectionClass|ReflectionFunction $reflection + */ + private function collectParsingMetadata($reflection): void + { + $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; + $name = $reflection->getName(); + + $ignoredAnnotationNames = self::$globalIgnoredNames; + $annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); + + foreach ($annotations as $annotation) { + if (! ($annotation instanceof IgnoreAnnotation)) { + continue; + } + + foreach ($annotation->names as $annot) { + $ignoredAnnotationNames[$annot] = true; + } + } + + $this->imports[$type][$name] = array_merge( + self::$globalImports, + $this->phpParser->parseUseStatements($reflection), + [ + '__NAMESPACE__' => $reflection->getNamespaceName(), + 'self' => $name, + ] + ); + + $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php new file mode 100644 index 0000000..259d497 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -0,0 +1,190 @@ +|null $dirs + */ + public static function registerAutoloadNamespace(string $namespace, $dirs = null): void + { + self::$autoloadNamespaces[$namespace] = $dirs; + } + + /** + * Registers multiple namespaces. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + * + * @param string[][]|string[]|null[] $namespaces indexed by namespace name + */ + public static function registerAutoloadNamespaces(array $namespaces): void + { + self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); + } + + /** + * Registers an autoloading callable for annotations, much like spl_autoload_register(). + * + * NOTE: These class loaders HAVE to be silent when a class was not found! + * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerLoader(callable $callable): void + { + // Reset our static cache now that we have a new loader to work with + self::$failedToAutoload = []; + self::$loaders[] = $callable; + } + + /** + * Registers an autoloading callable for annotations, if it is not already registered + * + * @deprecated This method is deprecated and will be removed in + * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerUniqueLoader(callable $callable): void + { + if (in_array($callable, self::$loaders, true)) { + return; + } + + self::registerLoader($callable); + } + + /** + * Autoloads an annotation class silently. + */ + public static function loadAnnotationClass(string $class): bool + { + if (class_exists($class, false)) { + return true; + } + + if (array_key_exists($class, self::$failedToAutoload)) { + return false; + } + + foreach (self::$autoloadNamespaces as $namespace => $dirs) { + if (strpos($class, $namespace) !== 0) { + continue; + } + + $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; + + if ($dirs === null) { + $path = stream_resolve_include_path($file); + if ($path) { + require $path; + + return true; + } + } else { + foreach ((array) $dirs as $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { + require $dir . DIRECTORY_SEPARATOR . $file; + + return true; + } + } + } + } + + foreach (self::$loaders as $loader) { + if ($loader($class) === true) { + return true; + } + } + + if ( + self::$loaders === [] && + self::$autoloadNamespaces === [] && + self::$registerFileUsed === false && + class_exists($class) + ) { + return true; + } + + self::$failedToAutoload[$class] = null; + + return false; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php new file mode 100644 index 0000000..85dbefa --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php @@ -0,0 +1,266 @@ +> */ + private $loadedAnnotations = []; + + /** @var int[] */ + private $loadedFilemtimes = []; + + /** @param bool $debug */ + public function __construct(Reader $reader, Cache $cache, $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (bool) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getClassAnnotations($class); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName() . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getPropertyAnnotations($property); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName() . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class); + if ($annots === false) { + $annots = $this->delegate->getMethodAnnotations($method); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + $this->loadedFilemtimes = []; + } + + /** + * Fetches a value from the cache. + * + * @param string $cacheKey The cache key. + * + * @return mixed The cached value or false when the value is not in cache. + */ + private function fetchFromCache($cacheKey, ReflectionClass $class) + { + $data = $this->cache->fetch($cacheKey); + if ($data !== false) { + if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { + return $data; + } + } + + return false; + } + + /** + * Saves a value to the cache. + * + * @param string $cacheKey The cache key. + * @param mixed $value The value. + * + * @return void + */ + private function saveToCache($cacheKey, $value) + { + $this->cache->save($cacheKey, $value); + if (! $this->debug) { + return; + } + + $this->cache->save('[C]' . $cacheKey, time()); + } + + /** + * Checks if the cache is fresh. + * + * @param string $cacheKey + * + * @return bool + */ + private function isCacheFresh($cacheKey, ReflectionClass $class) + { + $lastModification = $this->getLastModification($class); + if ($lastModification === 0) { + return true; + } + + return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; + } + + /** + * Returns the time the class was last modified, testing traits and parents + */ + private function getLastModification(ReflectionClass $class): int + { + $filename = $class->getFileName(); + + if (isset($this->loadedFilemtimes[$filename])) { + return $this->loadedFilemtimes[$filename]; + } + + $parent = $class->getParentClass(); + + $lastModification = max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $class->getTraits()), + array_map(function (ReflectionClass $class): int { + return $this->getLastModification($class); + }, $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + + assert($lastModification !== false); + + return $this->loadedFilemtimes[$filename] = $lastModification; + } + + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int + { + $fileName = $reflectionTrait->getFileName(); + + if (isset($this->loadedFilemtimes[$fileName])) { + return $this->loadedFilemtimes[$fileName]; + } + + $lastModificationTime = max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $reflectionTrait->getTraits()) + )); + + assert($lastModificationTime !== false); + + return $this->loadedFilemtimes[$fileName] = $lastModificationTime; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php new file mode 100644 index 0000000..7c0284b --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php @@ -0,0 +1,139 @@ + + */ +final class DocLexer extends AbstractLexer +{ + public const T_NONE = 1; + public const T_INTEGER = 2; + public const T_STRING = 3; + public const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + public const T_IDENTIFIER = 100; + public const T_AT = 101; + public const T_CLOSE_CURLY_BRACES = 102; + public const T_CLOSE_PARENTHESIS = 103; + public const T_COMMA = 104; + public const T_EQUALS = 105; + public const T_FALSE = 106; + public const T_NAMESPACE_SEPARATOR = 107; + public const T_OPEN_CURLY_BRACES = 108; + public const T_OPEN_PARENTHESIS = 109; + public const T_TRUE = 110; + public const T_NULL = 111; + public const T_COLON = 112; + public const T_MINUS = 113; + + /** @var array */ + protected $noCase = [ + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '-' => self::T_MINUS, + '\\' => self::T_NAMESPACE_SEPARATOR, + ]; + + /** @var array */ + protected $withCase = [ + 'true' => self::T_TRUE, + 'false' => self::T_FALSE, + 'null' => self::T_NULL, + ]; + + /** + * Whether the next token starts immediately, or if there were + * non-captured symbols before that + */ + public function nextTokenIsAdjacent(): bool + { + return $this->token === null + || ($this->lookahead !== null + && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); + } + + /** + * {@inheritdoc} + */ + protected function getCatchablePatterns() + { + return [ + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNonCatchablePatterns() + { + return ['\s+', '\*+', '(.)']; + } + + /** + * {@inheritdoc} + */ + protected function getType(&$value) + { + $type = self::T_NONE; + + if ($value[0] === '"') { + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { + return self::T_IDENTIFIER; + } + + $lowerValue = strtolower($value); + + if (isset($this->withCase[$lowerValue])) { + return $this->withCase[$lowerValue]; + } + + // Checking numeric value + if (is_numeric($value)) { + return strpos($value, '.') !== false || stripos($value, 'e') !== false + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } + + /** @return array{value: int|string, type:self::T_*|null, position:int} */ + public function peek(): array + { + $token = parent::peek(); + + return (array) $token; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php new file mode 100644 index 0000000..4133fe5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php @@ -0,0 +1,1485 @@ + + */ + private static $classIdentifiers = [ + DocLexer::T_IDENTIFIER, + DocLexer::T_TRUE, + DocLexer::T_FALSE, + DocLexer::T_NULL, + ]; + + /** + * The lexer. + * + * @var DocLexer + */ + private $lexer; + + /** + * Current target context. + * + * @var int + */ + private $target; + + /** + * Doc parser used to collect annotation target. + * + * @var DocParser + */ + private static $metadataParser; + + /** + * Flag to control if the current annotation is nested or not. + * + * @var bool + */ + private $isNestedAnnotation = false; + + /** + * Hashmap containing all use-statements that are to be used when parsing + * the given doc block. + * + * @var array + */ + private $imports = []; + + /** + * This hashmap is used internally to cache results of class_exists() + * look-ups. + * + * @var array + */ + private $classExists = []; + + /** + * Whether annotations that have not been imported should be ignored. + * + * @var bool + */ + private $ignoreNotImportedAnnotations = false; + + /** + * An array of default namespaces if operating in simple mode. + * + * @var string[] + */ + private $namespaces = []; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names must be the raw names as used in the class, not the fully qualified + * + * @var bool[] indexed by annotation name + */ + private $ignoredAnnotationNames = []; + + /** + * A list with annotations in namespaced format + * that are not causing exceptions when not resolved to an annotation class. + * + * @var bool[] indexed by namespace name + */ + private $ignoredAnnotationNamespaces = []; + + /** @var string */ + private $context = ''; + + /** + * Hash-map for caching annotation metadata. + * + * @var array + */ + private static $annotationMetadata = [ + Annotation\Target::class => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'has_named_argument_constructor' => false, + 'properties' => [], + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'attribute_types' => [ + 'value' => [ + 'required' => false, + 'type' => 'array', + 'array_type' => 'string', + 'value' => 'array', + ], + ], + ], + Annotation\Attribute::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_ANNOTATION', + 'targets' => Target::TARGET_ANNOTATION, + 'default_property' => 'name', + 'properties' => [ + 'name' => 'name', + 'type' => 'type', + 'required' => 'required', + ], + 'attribute_types' => [ + 'value' => [ + 'required' => true, + 'type' => 'string', + 'value' => 'string', + ], + 'type' => [ + 'required' => true, + 'type' => 'string', + 'value' => 'string', + ], + 'required' => [ + 'required' => false, + 'type' => 'boolean', + 'value' => 'boolean', + ], + ], + ], + Annotation\Attributes::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'properties' => ['value' => 'value'], + 'attribute_types' => [ + 'value' => [ + 'type' => 'array', + 'required' => true, + 'array_type' => Annotation\Attribute::class, + 'value' => 'array<' . Annotation\Attribute::class . '>', + ], + ], + ], + Annotation\Enum::class => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_PROPERTY', + 'targets' => Target::TARGET_PROPERTY, + 'default_property' => 'value', + 'properties' => ['value' => 'value'], + 'attribute_types' => [ + 'value' => [ + 'type' => 'array', + 'required' => true, + ], + 'literal' => [ + 'type' => 'array', + 'required' => false, + ], + ], + ], + Annotation\NamedArgumentConstructor::class => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'has_named_argument_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => null, + 'properties' => [], + 'attribute_types' => [], + ], + ]; + + /** + * Hash-map for handle types declaration. + * + * @var array + */ + private static $typeMap = [ + 'float' => 'double', + 'bool' => 'boolean', + // allow uppercase Boolean in honor of George Boole + 'Boolean' => 'boolean', + 'int' => 'integer', + ]; + + /** + * Constructs a new DocParser. + */ + public function __construct() + { + $this->lexer = new DocLexer(); + } + + /** + * Sets the annotation names that are ignored during the parsing process. + * + * The names are supposed to be the raw names as used in the class, not the + * fully qualified class names. + * + * @param bool[] $names indexed by annotation name + * + * @return void + */ + public function setIgnoredAnnotationNames(array $names) + { + $this->ignoredAnnotationNames = $names; + } + + /** + * Sets the annotation namespaces that are ignored during the parsing process. + * + * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name + * + * @return void + */ + public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) + { + $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; + } + + /** + * Sets ignore on not-imported annotations. + * + * @param bool $bool + * + * @return void + */ + public function setIgnoreNotImportedAnnotations($bool) + { + $this->ignoreNotImportedAnnotations = (bool) $bool; + } + + /** + * Sets the default namespaces. + * + * @param string $namespace + * + * @return void + * + * @throws RuntimeException + */ + public function addNamespace($namespace) + { + if ($this->imports) { + throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->namespaces[] = $namespace; + } + + /** + * Sets the imports. + * + * @param array $imports + * + * @return void + * + * @throws RuntimeException + */ + public function setImports(array $imports) + { + if ($this->namespaces) { + throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->imports = $imports; + } + + /** + * Sets current target context as bitmask. + * + * @param int $target + * + * @return void + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Parses the given docblock string for annotations. + * + * @param string $input The docblock string to parse. + * @param string $context The parsing context. + * + * @phpstan-return list Array of annotations. If no annotations are found, an empty array is returned. + * + * @throws AnnotationException + * @throws ReflectionException + */ + public function parse($input, $context = '') + { + $pos = $this->findInitialTokenPosition($input); + if ($pos === null) { + return []; + } + + $this->context = $context; + + $this->lexer->setInput(trim(substr($input, $pos), '* /')); + $this->lexer->moveNext(); + + return $this->Annotations(); + } + + /** + * Finds the first valid annotation + * + * @param string $input The docblock string to parse + */ + private function findInitialTokenPosition($input): ?int + { + $pos = 0; + + // search for first valid annotation + while (($pos = strpos($input, '@', $pos)) !== false) { + $preceding = substr($input, $pos - 1, 1); + + // if the @ is preceded by a space, a tab or * it is valid + if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { + return $pos; + } + + $pos++; + } + + return null; + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param int $token Type of token. + * + * @return bool True if tokens match; false otherwise. + * + * @throws AnnotationException + */ + private function match(int $token): bool + { + if (! $this->lexer->isNextToken($token)) { + throw $this->syntaxError($this->lexer->getLiteral($token)); + } + + return $this->lexer->moveNext(); + } + + /** + * Attempts to match the current lookahead token with any of the given tokens. + * + * If any of them matches, this method updates the lookahead token; otherwise + * a syntax error is raised. + * + * @phpstan-param list $tokens + * + * @throws AnnotationException + */ + private function matchAny(array $tokens): bool + { + if (! $this->lexer->isNextTokenAny($tokens)) { + throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); + } + + return $this->lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param mixed[]|null $token Optional token. + */ + private function syntaxError(string $expected, ?array $token = null): AnnotationException + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $message = sprintf('Expected %s, got ', $expected); + $message .= $this->lexer->lookahead === null + ? 'end of string' + : sprintf("'%s' at position %s", $token['value'], $token['position']); + + if (strlen($this->context)) { + $message .= ' in ' . $this->context; + } + + $message .= '.'; + + return AnnotationException::syntaxError($message); + } + + /** + * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism + * but uses the {@link AnnotationRegistry} to load classes. + * + * @param class-string $fqcn + */ + private function classExists(string $fqcn): bool + { + if (isset($this->classExists[$fqcn])) { + return $this->classExists[$fqcn]; + } + + // first check if the class already exists, maybe loaded through another AnnotationReader + if (class_exists($fqcn, false)) { + return $this->classExists[$fqcn] = true; + } + + // final check, does this class exist? + return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); + } + + /** + * Collects parsing metadata for a given annotation class + * + * @param class-string $name The annotation name + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function collectAnnotationMetadata(string $name): void + { + if (self::$metadataParser === null) { + self::$metadataParser = new self(); + + self::$metadataParser->setIgnoreNotImportedAnnotations(true); + self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); + self::$metadataParser->setImports([ + 'enum' => Enum::class, + 'target' => Target::class, + 'attribute' => Attribute::class, + 'attributes' => Attributes::class, + 'namedargumentconstructor' => NamedArgumentConstructor::class, + ]); + + // Make sure that annotations from metadata are loaded + class_exists(Enum::class); + class_exists(Target::class); + class_exists(Attribute::class); + class_exists(Attributes::class); + class_exists(NamedArgumentConstructor::class); + } + + $class = new ReflectionClass($name); + $docComment = $class->getDocComment(); + + // Sets default values for annotation metadata + $constructor = $class->getConstructor(); + $metadata = [ + 'default_property' => null, + 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0, + 'constructor_args' => [], + 'properties' => [], + 'property_types' => [], + 'attribute_types' => [], + 'targets_literal' => null, + 'targets' => Target::TARGET_ALL, + 'is_annotation' => strpos($docComment, '@Annotation') !== false, + ]; + + $metadata['has_named_argument_constructor'] = $metadata['has_constructor'] + && $class->implementsInterface(NamedArgumentConstructorAnnotation::class); + + // verify that the class is really meant to be an annotation + if ($metadata['is_annotation']) { + self::$metadataParser->setTarget(Target::TARGET_CLASS); + + foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { + if ($annotation instanceof Target) { + $metadata['targets'] = $annotation->targets; + $metadata['targets_literal'] = $annotation->literal; + + continue; + } + + if ($annotation instanceof NamedArgumentConstructor) { + $metadata['has_named_argument_constructor'] = $metadata['has_constructor']; + if ($metadata['has_named_argument_constructor']) { + // choose the first argument as the default property + $metadata['default_property'] = $constructor->getParameters()[0]->getName(); + } + } + + if (! ($annotation instanceof Attributes)) { + continue; + } + + foreach ($annotation->value as $attribute) { + $this->collectAttributeTypeMetadata($metadata, $attribute); + } + } + + // if not has a constructor will inject values into public properties + if ($metadata['has_constructor'] === false) { + // collect all public properties + foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $metadata['properties'][$property->name] = $property->name; + + $propertyComment = $property->getDocComment(); + if ($propertyComment === false) { + continue; + } + + $attribute = new Attribute(); + + $attribute->required = (strpos($propertyComment, '@Required') !== false); + $attribute->name = $property->name; + $attribute->type = (strpos($propertyComment, '@var') !== false && + preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches)) + ? $matches[1] + : 'mixed'; + + $this->collectAttributeTypeMetadata($metadata, $attribute); + + // checks if the property has @Enum + if (strpos($propertyComment, '@Enum') === false) { + continue; + } + + $context = 'property ' . $class->name . '::$' . $property->name; + + self::$metadataParser->setTarget(Target::TARGET_PROPERTY); + + foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { + if (! $annotation instanceof Enum) { + continue; + } + + $metadata['enum'][$property->name]['value'] = $annotation->value; + $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal)) + ? $annotation->literal + : $annotation->value; + } + } + + // choose the first property as default property + $metadata['default_property'] = reset($metadata['properties']); + } elseif ($metadata['has_named_argument_constructor']) { + foreach ($constructor->getParameters() as $parameter) { + $metadata['constructor_args'][$parameter->getName()] = [ + 'position' => $parameter->getPosition(), + 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null, + ]; + } + } + } + + self::$annotationMetadata[$name] = $metadata; + } + + /** + * Collects parsing metadata for a given attribute. + * + * @param mixed[] $metadata + */ + private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void + { + // handle internal type declaration + $type = self::$typeMap[$attribute->type] ?? $attribute->type; + + // handle the case if the property type is mixed + if ($type === 'mixed') { + return; + } + + // Evaluate type + $pos = strpos($type, '<'); + if ($pos !== false) { + // Checks if the property has array + $arrayType = substr($type, $pos + 1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + } else { + // Checks if the property has type[] + $pos = strrpos($type, '['); + if ($pos !== false) { + $arrayType = substr($type, 0, $pos); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + } + } + + $metadata['attribute_types'][$attribute->name]['type'] = $type; + $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; + $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; + } + + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @phpstan-return list + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Annotations(): array + { + $annotations = []; + + while ($this->lexer->lookahead !== null) { + if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is preceded by non-catchable pattern + if ( + $this->lexer->token !== null && + $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen( + $this->lexer->token['value'] + ) + ) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is followed by either a namespace separator, or + // an identifier token + $peek = $this->lexer->glimpse(); + if ( + ($peek === null) + || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array( + $peek['type'], + self::$classIdentifiers, + true + )) + || $peek['position'] !== $this->lexer->lookahead['position'] + 1 + ) { + $this->lexer->moveNext(); + continue; + } + + $this->isNestedAnnotation = false; + $annot = $this->Annotation(); + if ($annot === false) { + continue; + } + + $annotations[] = $annot; + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName MethodCall + * AnnotationName ::= QualifiedName | SimpleName + * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName + * NameSpacePart ::= identifier | null | false | true + * SimpleName ::= identifier | null | false | true + * + * @return object|false False if it is not a valid annotation. + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Annotation() + { + $this->match(DocLexer::T_AT); + + // check if we have an annotation + $name = $this->Identifier(); + + if ( + $this->lexer->isNextToken(DocLexer::T_MINUS) + && $this->lexer->nextTokenIsAdjacent() + ) { + // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded + return false; + } + + // only process names which are not fully qualified, yet + // fully qualified names must start with a \ + $originalName = $name; + + if ($name[0] !== '\\') { + $pos = strpos($name, '\\'); + $alias = ($pos === false) ? $name : substr($name, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + if ($this->namespaces) { + foreach ($this->namespaces as $namespace) { + if ($this->classExists($namespace . '\\' . $name)) { + $name = $namespace . '\\' . $name; + $found = true; + break; + } + } + } elseif (isset($this->imports[$loweredAlias])) { + $namespace = ltrim($this->imports[$loweredAlias], '\\'); + $name = ($pos !== false) + ? $namespace . substr($name, $pos) + : $namespace; + $found = $this->classExists($name); + } elseif ( + ! isset($this->ignoredAnnotationNames[$name]) + && isset($this->imports['__NAMESPACE__']) + && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) + ) { + $name = $this->imports['__NAMESPACE__'] . '\\' . $name; + $found = true; + } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { + $found = true; + } + + if (! $found) { + if ($this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf( + <<<'EXCEPTION' +The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation? +EXCEPTION + , + $name, + $this->context + )); + } + } + + $name = ltrim($name, '\\'); + + if (! $this->classExists($name)) { + throw AnnotationException::semanticalError(sprintf( + 'The annotation "@%s" in %s does not exist, or could not be auto-loaded.', + $name, + $this->context + )); + } + + // at this point, $name contains the fully qualified class name of the + // annotation, and it is also guaranteed that this class exists, and + // that it is loaded + + // collects the metadata annotation only if there is not yet + if (! isset(self::$annotationMetadata[$name])) { + $this->collectAnnotationMetadata($name); + } + + // verify that the class is really meant to be an annotation and not just any ordinary class + if (self::$annotationMetadata[$name]['is_annotation'] === false) { + if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf( + <<<'EXCEPTION' +The class "%s" is not annotated with @Annotation. +Are you sure this class can be used as annotation? +If so, then you need to add @Annotation to the _class_ doc comment of "%s". +If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s. +EXCEPTION + , + $name, + $name, + $originalName, + $this->context + )); + } + + //if target is nested annotation + $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; + + // Next will be nested + $this->isNestedAnnotation = true; + + //if annotation does not support current target + if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) { + throw AnnotationException::semanticalError( + sprintf( + <<<'EXCEPTION' +Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s. +EXCEPTION + , + $originalName, + $this->context, + self::$annotationMetadata[$name]['targets_literal'] + ) + ); + } + + $arguments = $this->MethodCall(); + $values = $this->resolvePositionalValues($arguments, $name); + + if (isset(self::$annotationMetadata[$name]['enum'])) { + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { + // checks if the attribute is a valid enumerator + if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { + throw AnnotationException::enumeratorError( + $property, + $name, + $this->context, + $enum['literal'], + $values[$property] + ); + } + } + } + + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { + if ( + $property === self::$annotationMetadata[$name]['default_property'] + && ! isset($values[$property]) && isset($values['value']) + ) { + $property = 'value'; + } + + // handle a not given attribute or null value + if (! isset($values[$property])) { + if ($type['required']) { + throw AnnotationException::requiredError( + $property, + $originalName, + $this->context, + 'a(n) ' . $type['value'] + ); + } + + continue; + } + + if ($type['type'] === 'array') { + // handle the case of a single value + if (! is_array($values[$property])) { + $values[$property] = [$values[$property]]; + } + + // checks if the attribute has array type declaration, such as "array" + if (isset($type['array_type'])) { + foreach ($values[$property] as $item) { + if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) { + throw AnnotationException::attributeTypeError( + $property, + $originalName, + $this->context, + 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', + $item + ); + } + } + } + } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) { + throw AnnotationException::attributeTypeError( + $property, + $originalName, + $this->context, + 'a(n) ' . $type['value'], + $values[$property] + ); + } + } + + if (self::$annotationMetadata[$name]['has_named_argument_constructor']) { + if (PHP_VERSION_ID >= 80000) { + return $this->instantiateAnnotiation($originalName, $this->context, $name, $values); + } + + $positionalValues = []; + foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { + $positionalValues[$parameter['position']] = $parameter['default']; + } + + foreach ($values as $property => $value) { + if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { + throw AnnotationException::creationError(sprintf( + <<<'EXCEPTION' +The annotation @%s declared on %s does not have a property named "%s" +that can be set through its named arguments constructor. +Available named arguments: %s +EXCEPTION + , + $originalName, + $this->context, + $property, + implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])) + )); + } + + $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value; + } + + return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues); + } + + // check if the annotation expects values via the constructor, + // or directly injected into public properties + if (self::$annotationMetadata[$name]['has_constructor'] === true) { + return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]); + } + + $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []); + + foreach ($values as $property => $value) { + if (! isset(self::$annotationMetadata[$name]['properties'][$property])) { + if ($property !== 'value') { + throw AnnotationException::creationError(sprintf( + <<<'EXCEPTION' +The annotation @%s declared on %s does not have a property named "%s". +Available properties: %s +EXCEPTION + , + $originalName, + $this->context, + $property, + implode(', ', self::$annotationMetadata[$name]['properties']) + )); + } + + // handle the case if the property has no annotations + $property = self::$annotationMetadata[$name]['default_property']; + if (! $property) { + throw AnnotationException::creationError(sprintf( + 'The annotation @%s declared on %s does not accept any values, but got %s.', + $originalName, + $this->context, + json_encode($values) + )); + } + } + + $instance->{$property} = $value; + } + + return $instance; + } + + /** + * MethodCall ::= ["(" [Values] ")"] + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function MethodCall(): array + { + $values = []; + + if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { + return $values; + } + + $this->match(DocLexer::T_OPEN_PARENTHESIS); + + if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + $values = $this->Values(); + } + + $this->match(DocLexer::T_CLOSE_PARENTHESIS); + + return $values; + } + + /** + * Values ::= Array | Value {"," Value}* [","] + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Values(): array + { + $values = [$this->Value()]; + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + break; + } + + $token = $this->lexer->lookahead; + $value = $this->Value(); + + $values[] = $value; + } + + $namedArguments = []; + $positionalArguments = []; + foreach ($values as $k => $value) { + if (is_object($value) && $value instanceof stdClass) { + $namedArguments[$value->name] = $value->value; + } else { + $positionalArguments[$k] = $value; + } + } + + return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments]; + } + + /** + * Constant ::= integer | string | float | boolean + * + * @return mixed + * + * @throws AnnotationException + */ + private function Constant() + { + $identifier = $this->Identifier(); + + if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') { + [$className, $const] = explode('::', $identifier); + + $pos = strpos($className, '\\'); + $alias = ($pos === false) ? $className : substr($className, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + switch (true) { + case ! empty($this->namespaces): + foreach ($this->namespaces as $ns) { + if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { + $className = $ns . '\\' . $className; + $found = true; + break; + } + } + + break; + + case isset($this->imports[$loweredAlias]): + $found = true; + $className = ($pos !== false) + ? $this->imports[$loweredAlias] . substr($className, $pos) + : $this->imports[$loweredAlias]; + break; + + default: + if (isset($this->imports['__NAMESPACE__'])) { + $ns = $this->imports['__NAMESPACE__']; + + if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { + $className = $ns . '\\' . $className; + $found = true; + } + } + + break; + } + + if ($found) { + $identifier = $className . '::' . $const; + } + } + + /** + * Checks if identifier ends with ::class and remove the leading backslash if it exists. + */ + if ( + $this->identifierEndsWithClassConstant($identifier) && + ! $this->identifierStartsWithBackslash($identifier) + ) { + return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); + } + + if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { + return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); + } + + if (! defined($identifier)) { + throw AnnotationException::semanticalErrorConstants($identifier, $this->context); + } + + return constant($identifier); + } + + private function identifierStartsWithBackslash(string $identifier): bool + { + return $identifier[0] === '\\'; + } + + private function identifierEndsWithClassConstant(string $identifier): bool + { + return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); + } + + /** @return int|false */ + private function getClassConstantPositionInIdentifier(string $identifier) + { + return stripos($identifier, '::class'); + } + + /** + * Identifier ::= string + * + * @throws AnnotationException + */ + private function Identifier(): string + { + // check if we have an annotation + if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { + throw $this->syntaxError('namespace separator or identifier'); + } + + $this->lexer->moveNext(); + + $className = $this->lexer->token['value']; + + while ( + $this->lexer->lookahead !== null && + $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + + strlen($this->lexer->token['value'])) && + $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR) + ) { + $this->match(DocLexer::T_NAMESPACE_SEPARATOR); + $this->matchAny(self::$classIdentifiers); + + $className .= '\\' . $this->lexer->token['value']; + } + + return $className; + } + + /** + * Value ::= PlainValue | FieldAssignment + * + * @return mixed + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Value() + { + $peek = $this->lexer->glimpse(); + + if ($peek['type'] === DocLexer::T_EQUALS) { + return $this->FieldAssignment(); + } + + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | boolean | Array | Annotation + * + * @return mixed + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function PlainValue() + { + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + return $this->Arrayx(); + } + + if ($this->lexer->isNextToken(DocLexer::T_AT)) { + return $this->Annotation(); + } + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + return $this->Constant(); + } + + switch ($this->lexer->lookahead['type']) { + case DocLexer::T_STRING: + $this->match(DocLexer::T_STRING); + + return $this->lexer->token['value']; + + case DocLexer::T_INTEGER: + $this->match(DocLexer::T_INTEGER); + + return (int) $this->lexer->token['value']; + + case DocLexer::T_FLOAT: + $this->match(DocLexer::T_FLOAT); + + return (float) $this->lexer->token['value']; + + case DocLexer::T_TRUE: + $this->match(DocLexer::T_TRUE); + + return true; + + case DocLexer::T_FALSE: + $this->match(DocLexer::T_FALSE); + + return false; + + case DocLexer::T_NULL: + $this->match(DocLexer::T_NULL); + + return null; + + default: + throw $this->syntaxError('PlainValue'); + } + } + + /** + * FieldAssignment ::= FieldName "=" PlainValue + * FieldName ::= identifier + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function FieldAssignment(): stdClass + { + $this->match(DocLexer::T_IDENTIFIER); + $fieldName = $this->lexer->token['value']; + + $this->match(DocLexer::T_EQUALS); + + $item = new stdClass(); + $item->name = $fieldName; + $item->value = $this->PlainValue(); + + return $item; + } + + /** + * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" + * + * @return mixed[] + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function Arrayx(): array + { + $array = $values = []; + + $this->match(DocLexer::T_OPEN_CURLY_BRACES); + + // If the array is empty, stop parsing and return. + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + return $array; + } + + $values[] = $this->ArrayEntry(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + // optional trailing comma + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + break; + } + + $values[] = $this->ArrayEntry(); + } + + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + foreach ($values as $value) { + [$key, $val] = $value; + + if ($key !== null) { + $array[$key] = $val; + } else { + $array[] = $val; + } + } + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant + * Key ::= string | integer | Constant + * + * @phpstan-return array{mixed, mixed} + * + * @throws AnnotationException + * @throws ReflectionException + */ + private function ArrayEntry(): array + { + $peek = $this->lexer->glimpse(); + + if ( + $peek['type'] === DocLexer::T_EQUALS + || $peek['type'] === DocLexer::T_COLON + ) { + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + $key = $this->Constant(); + } else { + $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); + $key = $this->lexer->token['value']; + } + + $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); + + return [$key, $this->PlainValue()]; + } + + return [null, $this->Value()]; + } + + /** + * Checks whether the given $name matches any ignored annotation name or namespace + */ + private function isIgnoredAnnotation(string $name): bool + { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { + return true; + } + + foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { + $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; + + if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) { + return true; + } + } + + return false; + } + + /** + * Resolve positional arguments (without name) to named ones + * + * @param array $arguments + * + * @return array + */ + private function resolvePositionalValues(array $arguments, string $name): array + { + $positionalArguments = $arguments['positional_arguments'] ?? []; + $values = $arguments['named_arguments'] ?? []; + + if ( + self::$annotationMetadata[$name]['has_named_argument_constructor'] + && self::$annotationMetadata[$name]['default_property'] !== null + ) { + // We must ensure that we don't have positional arguments after named ones + $positions = array_keys($positionalArguments); + $lastPosition = null; + foreach ($positions as $position) { + if ( + ($lastPosition === null && $position !== 0) || + ($lastPosition !== null && $position !== $lastPosition + 1) + ) { + throw $this->syntaxError('Positional arguments after named arguments is not allowed'); + } + + $lastPosition = $position; + } + + foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { + $position = $parameter['position']; + if (isset($values[$property]) || ! isset($positionalArguments[$position])) { + continue; + } + + $values[$property] = $positionalArguments[$position]; + } + } else { + if (count($positionalArguments) > 0 && ! isset($values['value'])) { + if (count($positionalArguments) === 1) { + $value = array_pop($positionalArguments); + } else { + $value = array_values($positionalArguments); + } + + $values['value'] = $value; + } + } + + return $values; + } + + /** + * Try to instantiate the annotation and catch and process any exceptions related to failure + * + * @param class-string $name + * @param array $arguments + * + * @return object + * + * @throws AnnotationException + */ + private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments) + { + try { + return new $name(...$arguments); + } catch (Throwable $exception) { + throw AnnotationException::creationError( + sprintf( + 'An error occurred while instantiating the annotation @%s declared on %s: "%s".', + $originalName, + $context, + $exception->getMessage() + ), + $exception + ); + } + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php new file mode 100644 index 0000000..6c6c22c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php @@ -0,0 +1,315 @@ +> */ + private $loadedAnnotations = []; + + /** @var array */ + private $classNameHashes = []; + + /** @var int */ + private $umask; + + /** + * @param string $cacheDir + * @param bool $debug + * @param int $umask + * + * @throws InvalidArgumentException + */ + public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) + { + if (! is_int($umask)) { + throw new InvalidArgumentException(sprintf( + 'The parameter umask must be an integer, was: %s', + gettype($umask) + )); + } + + $this->reader = $reader; + $this->umask = $umask; + + if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { + throw new InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $cacheDir + )); + } + + $this->dir = rtrim($cacheDir, '\\/'); + $this->debug = $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name]; + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + if (! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + + $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; + if (! is_file($path)) { + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + $filename = $class->getFilename(); + if ( + $this->debug + && $filename !== false + && filemtime($path) < filemtime($filename) + ) { + @unlink($path); + + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Saves the cache file. + * + * @param string $path + * @param mixed $data + * + * @return void + */ + private function saveCacheFile($path, $data) + { + if (! is_writable($this->dir)) { + throw new InvalidArgumentException(sprintf( + <<<'EXCEPTION' +The directory "%s" is not writable. Both the webserver and the console user need access. +You can manage access rights for multiple users with "chmod +a". +If your system does not support this, check out the acl package., +EXCEPTION + , + $this->dir + )); + } + + $tempfile = tempnam($this->dir, uniqid('', true)); + + if ($tempfile === false) { + throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); + } + + @chmod($tempfile, 0666 & (~$this->umask)); + + $written = file_put_contents( + $tempfile, + 'umask)); + + if (rename($tempfile, $path) === false) { + @unlink($tempfile); + + throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); + } + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php new file mode 100644 index 0000000..ab27f8a --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php @@ -0,0 +1,178 @@ + true, + 'Attribute' => true, + 'Attributes' => true, + /* Can we enable this? 'Enum' => true, */ + 'Required' => true, + 'Target' => true, + 'NamedArgumentConstructor' => true, + ]; + + private const WidelyUsedNonStandard = [ + 'fix' => true, + 'fixme' => true, + 'override' => true, + ]; + + private const PhpDocumentor1 = [ + 'abstract' => true, + 'access' => true, + 'code' => true, + 'deprec' => true, + 'endcode' => true, + 'exception' => true, + 'final' => true, + 'ingroup' => true, + 'inheritdoc' => true, + 'inheritDoc' => true, + 'magic' => true, + 'name' => true, + 'private' => true, + 'static' => true, + 'staticvar' => true, + 'staticVar' => true, + 'toc' => true, + 'tutorial' => true, + 'throw' => true, + ]; + + private const PhpDocumentor2 = [ + 'api' => true, + 'author' => true, + 'category' => true, + 'copyright' => true, + 'deprecated' => true, + 'example' => true, + 'filesource' => true, + 'global' => true, + 'ignore' => true, + /* Can we enable this? 'index' => true, */ + 'internal' => true, + 'license' => true, + 'link' => true, + 'method' => true, + 'package' => true, + 'param' => true, + 'property' => true, + 'property-read' => true, + 'property-write' => true, + 'return' => true, + 'see' => true, + 'since' => true, + 'source' => true, + 'subpackage' => true, + 'throws' => true, + 'todo' => true, + 'TODO' => true, + 'usedby' => true, + 'uses' => true, + 'var' => true, + 'version' => true, + ]; + + private const PHPUnit = [ + 'author' => true, + 'after' => true, + 'afterClass' => true, + 'backupGlobals' => true, + 'backupStaticAttributes' => true, + 'before' => true, + 'beforeClass' => true, + 'codeCoverageIgnore' => true, + 'codeCoverageIgnoreStart' => true, + 'codeCoverageIgnoreEnd' => true, + 'covers' => true, + 'coversDefaultClass' => true, + 'coversNothing' => true, + 'dataProvider' => true, + 'depends' => true, + 'doesNotPerformAssertions' => true, + 'expectedException' => true, + 'expectedExceptionCode' => true, + 'expectedExceptionMessage' => true, + 'expectedExceptionMessageRegExp' => true, + 'group' => true, + 'large' => true, + 'medium' => true, + 'preserveGlobalState' => true, + 'requires' => true, + 'runTestsInSeparateProcesses' => true, + 'runInSeparateProcess' => true, + 'small' => true, + 'test' => true, + 'testdox' => true, + 'testWith' => true, + 'ticket' => true, + 'uses' => true, + ]; + + private const PhpCheckStyle = ['SuppressWarnings' => true]; + + private const PhpStorm = ['noinspection' => true]; + + private const PEAR = ['package_version' => true]; + + private const PlainUML = [ + 'startuml' => true, + 'enduml' => true, + ]; + + private const Symfony = ['experimental' => true]; + + private const PhpCodeSniffer = [ + 'codingStandardsIgnoreStart' => true, + 'codingStandardsIgnoreEnd' => true, + ]; + + private const SlevomatCodingStandard = ['phpcsSuppress' => true]; + + private const Phan = ['suppress' => true]; + + private const Rector = ['noRector' => true]; + + private const StaticAnalysis = [ + // PHPStan, Psalm + 'extends' => true, + 'implements' => true, + 'readonly' => true, + 'template' => true, + 'use' => true, + + // Psalm + 'pure' => true, + 'immutable' => true, + ]; + + public const LIST = self::Reserved + + self::WidelyUsedNonStandard + + self::PhpDocumentor1 + + self::PhpDocumentor2 + + self::PHPUnit + + self::PhpCheckStyle + + self::PhpStorm + + self::PEAR + + self::PlainUML + + self::Symfony + + self::SlevomatCodingStandard + + self::PhpCodeSniffer + + self::Phan + + self::Rector + + self::StaticAnalysis; + + private function __construct() + { + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php new file mode 100644 index 0000000..62dcf74 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php @@ -0,0 +1,100 @@ +delegate = $reader; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $annotations = []; + foreach ($this->delegate->getClassAnnotations($class) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + return $this->delegate->getClassAnnotation($class, $annotationName); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $annotations = []; + foreach ($this->delegate->getMethodAnnotations($method) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + return $this->delegate->getMethodAnnotation($method, $annotationName); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $annotations = []; + foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + return $this->delegate->getPropertyAnnotation($property, $annotationName); + } + + /** + * Proxies all methods to the delegate. + * + * @param string $method + * @param mixed[] $args + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->delegate, $method], $args); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php new file mode 100644 index 0000000..8af224c --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php @@ -0,0 +1,14 @@ +ReflectionClass object. + * + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseClass(ReflectionClass $class) + { + return $this->parseUseStatements($class); + } + + /** + * Parse a class or function for use statements. + * + * @param ReflectionClass|ReflectionFunction $reflection + * + * @psalm-return array a list with use statements in the form (Alias => FQN). + */ + public function parseUseStatements($reflection): array + { + if (method_exists($reflection, 'getUseStatements')) { + return $reflection->getUseStatements(); + } + + $filename = $reflection->getFileName(); + + if ($filename === false) { + return []; + } + + $content = $this->getFileContent($filename, $reflection->getStartLine()); + + if ($content === null) { + return []; + } + + $namespace = preg_quote($reflection->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($reflection->getNamespaceName()); + } + + /** + * Gets the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param int $lineNumber The number of lines to read from file. + * + * @return string|null The content of the file or null if the file does not exist. + */ + private function getFileContent($filename, $lineNumber) + { + if (! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (! $file->eof()) { + if ($lineCnt++ === $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php new file mode 100644 index 0000000..a7099d5 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php @@ -0,0 +1,232 @@ +> */ + private $loadedAnnotations = []; + + /** @var int[] */ + private $loadedFilemtimes = []; + + public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (bool) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName() . '$' . $property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName() . '#' . $method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method); + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + public function clearLoadedAnnotations(): void + { + $this->loadedAnnotations = []; + $this->loadedFilemtimes = []; + } + + /** @return mixed[] */ + private function fetchFromCache( + string $cacheKey, + ReflectionClass $class, + string $method, + Reflector $reflector + ): array { + $cacheKey = rawurlencode($cacheKey); + + $item = $this->cache->getItem($cacheKey); + if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) { + $this->cache->save($item->set($this->delegate->{$method}($reflector))); + } + + return $item->get(); + } + + /** + * Used in debug mode to check if the cache is fresh. + * + * @return bool Returns true if the cache was fresh, or false if the class + * being read was modified since writing to the cache. + */ + private function refresh(string $cacheKey, ReflectionClass $class): bool + { + $lastModification = $this->getLastModification($class); + if ($lastModification === 0) { + return true; + } + + $item = $this->cache->getItem('[C]' . $cacheKey); + if ($item->isHit() && $item->get() >= $lastModification) { + return true; + } + + $this->cache->save($item->set(time())); + + return false; + } + + /** + * Returns the time the class was last modified, testing traits and parents + */ + private function getLastModification(ReflectionClass $class): int + { + $filename = $class->getFileName(); + + if (isset($this->loadedFilemtimes[$filename])) { + return $this->loadedFilemtimes[$filename]; + } + + $parent = $class->getParentClass(); + + $lastModification = max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $class->getTraits()), + array_map(function (ReflectionClass $class): int { + return $this->getLastModification($class); + }, $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + + assert($lastModification !== false); + + return $this->loadedFilemtimes[$filename] = $lastModification; + } + + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int + { + $fileName = $reflectionTrait->getFileName(); + + if (isset($this->loadedFilemtimes[$fileName])) { + return $this->loadedFilemtimes[$fileName]; + } + + $lastModificationTime = max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map(function (ReflectionClass $reflectionTrait): int { + return $this->getTraitLastModificationTime($reflectionTrait); + }, $reflectionTrait->getTraits()) + )); + + assert($lastModificationTime !== false); + + return $this->loadedFilemtimes[$fileName] = $lastModificationTime; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php new file mode 100644 index 0000000..0663ffd --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php @@ -0,0 +1,80 @@ + An array of Annotations. + */ + public function getClassAnnotations(ReflectionClass $class); + + /** + * Gets a class annotation. + * + * @param ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName); + + /** + * Gets the annotations applied to a method. + * + * @param ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getMethodAnnotations(ReflectionMethod $method); + + /** + * Gets a method annotation. + * + * @param ReflectionMethod $method The ReflectionMethod to read the annotations from. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName); + + /** + * Gets the annotations applied to a property. + * + * @param ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getPropertyAnnotations(ReflectionProperty $property); + + /** + * Gets a property annotation. + * + * @param ReflectionProperty $property The ReflectionProperty to read the annotations from. + * @param class-string $annotationName The name of the annotation. + * + * @return T|null The Annotation or NULL, if the requested annotation does not exist. + * + * @template T + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php new file mode 100644 index 0000000..8a78c11 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php @@ -0,0 +1,114 @@ +parser = new DocParser(); + $this->parser->setIgnoreNotImportedAnnotations(true); + } + + /** + * Adds a namespace in which we will look for annotations. + * + * @param string $namespace + * + * @return void + */ + public function addNamespace($namespace) + { + $this->parser->addNamespace($namespace); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + return $this->parser->parse( + $method->getDocComment(), + 'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()' + ); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + return $this->parser->parse( + $property->getDocComment(), + 'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName() + ); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } +} diff --git a/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php new file mode 100644 index 0000000..69259fc --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php @@ -0,0 +1,206 @@ + + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + /** @param string $contents */ + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + + // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it + // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored + // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a + // docblock. If the first thing in the file is a class without a doc block this would cause calls to + // getDocBlock() on said class to return our long lost doc_comment. Argh. + // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least + // it's harmless to us. + token_get_all("numTokens = count($this->tokens); + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return mixed[]|string|null The token if exists, null otherwise. + */ + public function next($docCommentIsComment = true) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ( + $this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) + ) { + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parses a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + public function parseUseStatement() + { + $groupRoot = ''; + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + while (($token = $this->next())) { + if (! $explicitAlias && $token[0] === T_STRING) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $token[0] === T_STRING) { + $alias = $token[1]; + } elseif ( + PHP_VERSION_ID >= 80000 && + ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) + ) { + $class .= $token[1]; + + $classSplit = explode('\\', $token[1]); + $alias = $classSplit[count($classSplit) - 1]; + } elseif ($token[0] === T_NS_SEPARATOR) { + $class .= '\\'; + $alias = ''; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token === ',') { + $statements[strtolower($alias)] = $groupRoot . $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[strtolower($alias)] = $groupRoot . $class; + break; + } elseif ($token === '{') { + $groupRoot = $class; + $class = ''; + } elseif ($token === '}') { + continue; + } else { + break; + } + } + + return $statements; + } + + /** + * Gets all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = []; + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = []; + } + + return $statements; + } + + /** + * Gets the namespace. + * + * @return string The found namespace. + */ + public function parseNamespace() + { + $name = ''; + while ( + ($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || ( + PHP_VERSION_ID >= 80000 && + ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) + )) + ) { + $name .= $token[1]; + } + + return $name; + } + + /** + * Gets the class name. + * + * @return string The found class name. + */ + public function parseClass() + { + // Namespaces and class names are tokenized the same: T_STRINGs + // separated by T_NS_SEPARATOR so we can use one function to provide + // both. + return $this->parseNamespace(); + } +} diff --git a/Sources/API/vendor/doctrine/annotations/psalm.xml b/Sources/API/vendor/doctrine/annotations/psalm.xml new file mode 100644 index 0000000..e6af389 --- /dev/null +++ b/Sources/API/vendor/doctrine/annotations/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/Sources/API/vendor/doctrine/deprecations/LICENSE b/Sources/API/vendor/doctrine/deprecations/LICENSE new file mode 100644 index 0000000..156905c --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2021 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/deprecations/README.md b/Sources/API/vendor/doctrine/deprecations/README.md new file mode 100644 index 0000000..22f0cce --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/README.md @@ -0,0 +1,154 @@ +# Doctrine Deprecations + +A small (side-effect free by default) layer on top of +`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. + +- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under +- options to avoid having to rely on error handlers global state by using PSR-3 logging +- deduplicate deprecation messages to avoid excessive triggering and reduce overhead + +We recommend to collect Deprecations using a PSR logger instead of relying on +the global error handler. + +## Usage from consumer perspective: + +Enable Doctrine deprecations to be sent to a PSR3 logger: + +```php +\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); +``` + +Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` +messages. + +```php +\Doctrine\Deprecations\Deprecation::enableWithTriggerError(); +``` + +If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call: + +```php +\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); +``` + +Tracking is enabled with all three modes and provides access to all triggered +deprecations and their individual count: + +```php +$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); + +foreach ($deprecations as $identifier => $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/Sources/API/vendor/doctrine/deprecations/composer.json b/Sources/API/vendor/doctrine/deprecations/composer.json new file mode 100644 index 0000000..c79e38c --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,32 @@ +{ + "name": "doctrine/deprecations", + "type": "library", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "license": "MIT", + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3", + "doctrine/coding-standard": "^9" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"} + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php new file mode 100644 index 0000000..1029372 --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php @@ -0,0 +1,266 @@ + */ + private static $ignoredPackages = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param mixed $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param mixed $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * @param array $backtrace + */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + if ((self::$type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'], + 'line' => $backtrace[0]['line'], + 'package' => $package, + 'link' => $link, + ]; + + self::$logger->notice($message, $context); + } + + if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file']), + $backtrace[0]['line'], + self::basename($backtrace[1]['file']), + $backtrace[1]['line'], + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + + foreach (self::$ignoredLinks as $link => $count) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$ignoredLinks; + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php new file mode 100644 index 0000000..4c3366a --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** + * @before + */ + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** + * @after + */ + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/Sources/API/vendor/doctrine/deprecations/phpcs.xml b/Sources/API/vendor/doctrine/deprecations/phpcs.xml new file mode 100644 index 0000000..f115e43 --- /dev/null +++ b/Sources/API/vendor/doctrine/deprecations/phpcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + lib + tests + + + + + + diff --git a/Sources/API/vendor/doctrine/lexer/LICENSE b/Sources/API/vendor/doctrine/lexer/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/API/vendor/doctrine/lexer/README.md b/Sources/API/vendor/doctrine/lexer/README.md new file mode 100644 index 0000000..784f2a2 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/README.md @@ -0,0 +1,9 @@ +# Doctrine Lexer + +[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions) + +Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. + +This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). + +https://www.doctrine-project.org/projects/lexer.html diff --git a/Sources/API/vendor/doctrine/lexer/UPGRADE.md b/Sources/API/vendor/doctrine/lexer/UPGRADE.md new file mode 100644 index 0000000..42b85b3 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/UPGRADE.md @@ -0,0 +1,14 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 2.0.0 + +`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return +instances of `Doctrine\Common\Lexer\Token`, which is an array-like class +Using it as an array is deprecated in favor of using properties of that class. +Using `count()` on it is deprecated with no replacement. diff --git a/Sources/API/vendor/doctrine/lexer/composer.json b/Sources/API/vendor/doctrine/lexer/composer.json new file mode 100644 index 0000000..be3013c --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/composer.json @@ -0,0 +1,56 @@ +{ + "name": "doctrine/lexer", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "license": "MIT", + "type": "library", + "keywords": [ + "php", + "parser", + "lexer", + "annotations", + "docblock" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "require": { + "php": "^7.1 || ^8.0", + "doctrine/deprecations": "^1.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\Common\\Lexer\\": "tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php b/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php new file mode 100644 index 0000000..7d33e41 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/src/AbstractLexer.php @@ -0,0 +1,336 @@ +> + */ + private $tokens = []; + + /** + * Current lexer position in input string. + * + * @var int + */ + private $position = 0; + + /** + * Current peek of current lexer position. + * + * @var int + */ + private $peek = 0; + + /** + * The next token in the input. + * + * @var mixed[]|null + * @psalm-var Token|null + */ + public $lookahead; + + /** + * The last matched/seen token. + * + * @var mixed[]|null + * @psalm-var Token|null + */ + public $token; + + /** + * Composed regex for input parsing. + * + * @var string|null + */ + private $regex; + + /** + * Sets the input data to be tokenized. + * + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. + * + * @return void + */ + public function setInput($input) + { + $this->input = $input; + $this->tokens = []; + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param int $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @param int $position + * + * @return string + */ + public function getInputUntilPosition($position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param T $type + * + * @return bool + */ + public function isNextToken($type) + { + return $this->lookahead !== null && $this->lookahead->isA($type); + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param list $types + * + * @return bool + */ + public function isNextTokenAny(array $types) + { + return $this->lookahead !== null && $this->lookahead->isA(...$types); + } + + /** + * Moves to the next token in the input string. + * + * @return bool + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = isset($this->tokens[$this->position]) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param T $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && ! $this->lookahead->isA($type)) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param string $value + * @param int|string $token + * + * @return bool + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return mixed[]|null The next token or NULL if there are no more tokens ahead. + * @psalm-return Token|null + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return mixed[]|null The next token or NULL if there are no more tokens ahead. + * @psalm-return Token|null + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + if (! isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers() + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($this->regex, $input, -1, $flags); + + if ($matches === false) { + // Work around https://bugs.php.net/78122 + $matches = [[$input, 0]]; + } + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = new Token( + $match[0], + $type, + $match[1] + ); + } + } + + /** + * Gets the literal for a given token. + * + * @param T $token + * + * @return int|string + */ + public function getLiteral($token) + { + if ($token instanceof UnitEnum) { + return get_class($token) . '::' . $token->name; + } + + $className = static::class; + + $reflClass = new ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } + + /** + * Lexical catchable patterns. + * + * @return string[] + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return string[] + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return T|null + */ + abstract protected function getType(&$value); +} diff --git a/Sources/API/vendor/doctrine/lexer/src/Token.php b/Sources/API/vendor/doctrine/lexer/src/Token.php new file mode 100644 index 0000000..e2e0f55 --- /dev/null +++ b/Sources/API/vendor/doctrine/lexer/src/Token.php @@ -0,0 +1,129 @@ + + */ +final class Token implements ArrayAccess +{ + /** + * The string value of the token in the input string + * + * @readonly + * @var string|int + */ + public $value; + + /** + * The type of the token (identifier, numeric, string, input parameter, none) + * + * @readonly + * @var T|null + */ + public $type; + + /** + * The position of the token in the input string + * + * @readonly + * @var int + */ + public $position; + + /** + * @param string|int $value + * @param T|null $type + */ + public function __construct($value, $type, int $position) + { + $this->value = $value; + $this->type = $type; + $this->position = $position; + } + + /** @param T ...$types */ + public function isA(...$types): bool + { + return in_array($this->type, $types, true); + } + + /** + * @deprecated Use the value, type or position property instead + * {@inheritDoc} + */ + public function offsetExists($offset): bool + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', + self::class + ); + + return in_array($offset, ['value', 'type', 'position'], true); + } + + /** + * @deprecated Use the value, type or position property instead + * {@inheritDoc} + * + * @param array-key $offset + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($offset) + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', + self::class + ); + + return $this->$offset; + } + + /** + * @deprecated no replacement planned + * {@inheritDoc} + */ + public function offsetSet($offset, $value): void + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Setting %s properties via ArrayAccess is deprecated', + self::class + ); + + $this->$offset = $value; + } + + /** + * @deprecated no replacement planned + * {@inheritDoc} + */ + public function offsetUnset($offset): void + { + Deprecation::trigger( + 'doctrine/lexer', + 'https://github.com/doctrine/lexer/pull/79', + 'Setting %s properties via ArrayAccess is deprecated', + self::class + ); + + $this->$offset = null; + } +} diff --git a/Sources/API/vendor/psr/cache/CHANGELOG.md b/Sources/API/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/Sources/API/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/Sources/API/vendor/psr/cache/LICENSE.txt b/Sources/API/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/Sources/API/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/psr/cache/README.md b/Sources/API/vendor/psr/cache/README.md new file mode 100644 index 0000000..9855a31 --- /dev/null +++ b/Sources/API/vendor/psr/cache/README.md @@ -0,0 +1,12 @@ +Caching Interface +============== + +This repository holds all interfaces related to [PSR-6 (Caching Interface)][psr-url]. + +Note that this is not a Caching implementation of its own. It is merely interfaces that describe the components of a Caching mechanism. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-6/ +[package-url]: https://packagist.org/packages/psr/cache +[implementation-url]: https://packagist.org/providers/psr/cache-implementation diff --git a/Sources/API/vendor/psr/cache/composer.json b/Sources/API/vendor/psr/cache/composer.json new file mode 100644 index 0000000..4b68797 --- /dev/null +++ b/Sources/API/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/Sources/API/vendor/psr/cache/src/CacheException.php b/Sources/API/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..bb785f4 --- /dev/null +++ b/Sources/API/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=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/yaml/CHANGELOG.md b/Sources/API/vendor/symfony/yaml/CHANGELOG.md new file mode 100644 index 0000000..50852cb --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/CHANGELOG.md @@ -0,0 +1,248 @@ +CHANGELOG +========= + +6.2 +--- + + * Add support for `!php/enum` and `!php/enum *->value` + * Deprecate the `!php/const:` tag in key which will be replaced by the `!php/const` tag (without the colon) since 3.4 + +6.1 +--- + + * In cases where it will likely improve readability, strings containing single quotes will be double-quoted + +5.4 +--- + + * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` + option to exclude one or more specific files from multiple file list + * Allow negatable for the parse tags option with `--no-parse-tags` + +5.3 +--- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + +5.1.0 +----- + + * Added support for parsing numbers prefixed with `0o` as octal numbers. + * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` + so that they are parsed as octal numbers. + + Before: + + ```yaml + Yaml::parse('072'); + ``` + + After: + + ```yaml + Yaml::parse('0o72'); + ``` + + * Added `yaml-lint` binary. + * Deprecated using the `!php/object` and `!php/const` tags without a value. + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/Sources/API/vendor/symfony/yaml/Command/LintCommand.php b/Sources/API/vendor/symfony/yaml/Command/LintCommand.php new file mode 100644 index 0000000..19a0af0 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Command/LintCommand.php @@ -0,0 +1,276 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] +class LintCommand extends Command +{ + private Parser $parser; + private ?string $format = null; + private bool $displayCorrectFiles; + private ?\Closure $directoryIteratorProvider; + private ?\Closure $isReadableProvider; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); + $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); + } + + protected function configure() + { + $this + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') + ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +You can also exclude one or more specific files: + + php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $excludes = $input->getOption('exclude'); + $this->format = $input->getOption('format'); + $flags = $input->getOption('parse-tags'); + + if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { + throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; + + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + if (!\in_array($file->getPathname(), $excludes, true)) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($io, $files), + 'json' => $this->displayJson($io, $files), + 'github' => $this->displayTxt($io, $files, true), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)), + }; + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + + if (str_contains($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && str_contains($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + return $this->parser ??= new Parser(); + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } +} diff --git a/Sources/API/vendor/symfony/yaml/Dumper.php b/Sources/API/vendor/symfony/yaml/Dumper.php new file mode 100644 index 0000000..c5024ff --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + */ + private int $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { + $output .= $prefix.Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value, ' ') ? (string) $this->indentation : ''; + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value->getValue(), ' ') ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/Sources/API/vendor/symfony/yaml/Escaper.php b/Sources/API/vendor/symfony/yaml/Escaper.php new file mode 100644 index 0000000..e8090d8 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/DumpException.php b/Sources/API/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 0000000..cce972f --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php b/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000..9091316 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/ParseException.php b/Sources/API/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 0000000..07c59b9 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private ?string $parsedFile; + private int $parsedLine; + private ?string $snippet; + private string $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + */ + public function getSnippet(): string + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + */ + public function setSnippet(string $snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + */ + public function getParsedFile(): string + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + */ + public function setParsedFile(string $parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + */ + public function getParsedLine(): int + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + */ + public function setParsedLine(int $parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if (str_ends_with($this->message, '.')) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php b/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 0000000..3f36b73 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/Sources/API/vendor/symfony/yaml/Inline.php b/Sources/API/vendor/symfony/yaml/Inline.php new file mode 100644 index 0000000..02cbe8c --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Inline.php @@ -0,0 +1,816 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + * + * @internal + */ +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static int $parsedLineNumber = -1; + public static ?string $parsedFilename = null; + + private static bool $exceptionOnInvalidType = false; + private static bool $objectSupport = false; + private static bool $objectForMap = false; + private static bool $constantSupport = false; + + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array &$references = []): mixed + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump(mixed $value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', $value::class, $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + return self::dumpHashArray($value, $flags); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + if (!str_contains($repr, 'E')) { + $repr = $repr.'.0'; + } + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + $singleQuoted = Escaper::escapeWithSingleQuotes($value); + if (!str_contains($value, "'")) { + return $singleQuoted; + } + // Attempt double-quoting the string instead to see if it's more efficient. + $doubleQuoted = Escaper::escapeWithDoubleQuotes($value); + + return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted; + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + */ + public static function isHash(array|\ArrayObject|\stdClass $value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + return self::dumpHashArray($value, $flags); + } + + /** + * Dumps hash array to a YAML string. + * + * @param array|\ArrayObject|\stdClass $value The hash array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $flags): string + { + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null): mixed + { + if (\in_array($scalar[$i], ['"', "'"], true)) { + // quoted scalar + $isQuoted = true; + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + $isQuoted = false; + + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, -1); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException) { + // no, it's not + } + } + + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if ('!php/const' === $key || '!php/enum' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null): mixed + { + $isQuotedString = false; + $scalar = trim($scalar); + + if (str_starts_with($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + $scalarLower = strtolower($scalar); + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case str_starts_with($scalar, '!!str '): + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; + case str_starts_with($scalar, '! '): + return substr($scalar, 2); + case str_starts_with($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/enum'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + $enum = self::parseScalar(substr($scalar, 10), 0, null, $i, false); + if ($useValue = str_ends_with($enum, '->value')) { + $enum = substr($enum, 0, -7); + } + if (!\defined($enum)) { + throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $value = \constant($enum); + + if (!$value instanceof \UnitEnum) { + throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!$useValue) { + return $value; + } + if (!$value instanceof \BackedEnum) { + throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return $value->value; + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!!float '): + return (float) substr($scalar, 8); + case str_starts_with($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + } + + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + case preg_match('/^(?:\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): + $value = str_replace('_', '', $matches['value']); + + if ('-' === $scalar[0]) { + return -octdec($value); + } + + return octdec($value); + case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + + switch (true) { + case ctype_digit($scalar): + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC')); + + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError) { + // no-op + } + + return $time->format('U'); + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value): bool + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/Sources/API/vendor/symfony/yaml/LICENSE b/Sources/API/vendor/symfony/yaml/LICENSE new file mode 100644 index 0000000..88bf75b --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Sources/API/vendor/symfony/yaml/Parser.php b/Sources/API/vendor/symfony/yaml/Parser.php new file mode 100644 index 0000000..146a645 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Parser.php @@ -0,0 +1,1246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + * + * @final + */ +class Parser +{ + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; + + private ?string $filename = null; + private int $offset = 0; + private int $numberOfParsedLines = 0; + private ?int $totalNumberOfLines = null; + private array $lines = []; + private int $currentLineNb = -1; + private string $currentLine = ''; + private array $refs = []; + private array $skippedLineNumbers = []; + private array $locallySkippedLineNumbers = []; + private array $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0): mixed + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0): mixed + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + try { + $data = $this->doParse($value, $flags); + } finally { + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->numberOfParsedLines = 0; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + + return $data; + } + + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->numberOfParsedLines = \count($this->lines); + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines ??= $this->numberOfParsedLines; + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if ( + isset($values['leadspaces']) + && ( + '!' === $values['value'][0] + || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + // @todo in 7.0 remove legacy "(?:!?!php/const:)?" + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) + && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if (str_starts_with($values['key'], '!php/const:')) { + trigger_deprecation('symfony/yaml', '6.2', 'YAML syntax for key "%s" is deprecated and replaced by "!php/const %s".', $values['key'], substr($values['key'], 11)); + } + + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + $trimmedLine = trim($line); + if ('#' === ($trimmedLine[0] ?? '')) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (str_contains($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === $trimmedLine) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== $trimmedLine && str_ends_with($line, '\\')) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== $trimmedLine) { + $value .= $trimmedLine; + } + + if ('' === $trimmedLine) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif (str_ends_with($line, '\\')) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + private function getCurrentLineIndentation(): int + { + if (' ' !== ($this->currentLine[0] ?? '')) { + return 0; + } + + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context): mixed + { + if (str_starts_with($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + + return $parsedValue; + default: + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && str_contains($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + private function isCurrentLineBlank(): bool + { + return '' === $this->currentLine || '' === trim($this->currentLine, ' '); + } + + private function isCurrentLineComment(): bool + { + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine; + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || str_starts_with($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + throw new ParseException(preg_last_error_msg()); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + + return substr($this->currentLine, $offset, $cursor - $offset); + } + + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; + } +} diff --git a/Sources/API/vendor/symfony/yaml/README.md b/Sources/API/vendor/symfony/yaml/README.md new file mode 100644 index 0000000..ac25024 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php b/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php new file mode 100644 index 0000000..c7946c2 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Tag/TaggedValue.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tag; + +/** + * @author Nicolas Grekas + * @author Guilhem N. + */ +final class TaggedValue +{ + private string $tag; + private mixed $value; + + public function __construct(string $tag, mixed $value) + { + $this->tag = $tag; + $this->value = $value; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/Sources/API/vendor/symfony/yaml/Unescaper.php b/Sources/API/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 0000000..2238210 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + */ + private function unescapeCharacter(string $value): string + { + return match ($value[1]) { + '0' => "\x0", + 'a' => "\x7", + 'b' => "\x8", + 't' => "\t", + "\t" => "\t", + 'n' => "\n", + 'v' => "\xB", + 'f' => "\xC", + 'r' => "\r", + 'e' => "\x1B", + ' ' => ' ', + '"' => '"', + '/' => '/', + '\\' => '\\', + // U+0085 NEXT LINE + 'N' => "\xC2\x85", + // U+00A0 NO-BREAK SPACE + '_' => "\xC2\xA0", + // U+2028 LINE SEPARATOR + 'L' => "\xE2\x80\xA8", + // U+2029 PARAGRAPH SEPARATOR + 'P' => "\xE2\x80\xA9", + 'x' => self::utf8chr(hexdec(substr($value, 2, 2))), + 'u' => self::utf8chr(hexdec(substr($value, 2, 4))), + 'U' => self::utf8chr(hexdec(substr($value, 2, 8))), + default => throw new ParseException(sprintf('Found unknown escape character "%s".', $value)), + }; + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/Sources/API/vendor/symfony/yaml/Yaml.php b/Sources/API/vendor/symfony/yaml/Yaml.php new file mode 100644 index 0000000..4978421 --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @final + */ +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + */ + public static function dump(mixed $input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/Sources/API/vendor/symfony/yaml/composer.json b/Sources/API/vendor/symfony/yaml/composer.json new file mode 100644 index 0000000..839314b --- /dev/null +++ b/Sources/API/vendor/symfony/yaml/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Loads and dumps YAML files", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "minimum-stability": "dev" +} diff --git a/Sources/API/vendor/zircote/swagger-php/LICENSE b/Sources/API/vendor/zircote/swagger-php/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Sources/API/vendor/zircote/swagger-php/README.md b/Sources/API/vendor/zircote/swagger-php/README.md new file mode 100644 index 0000000..dd58fba --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/README.md @@ -0,0 +1,155 @@ +[![Build Status](https://img.shields.io/github/workflow/status/zircote/swagger-php/build?style=flat-square)](https://github.com/zircote/swagger-php/actions?query=workflow:build) +[![Total Downloads](https://img.shields.io/packagist/dt/zircote/swagger-php.svg?style=flat-square)](https://packagist.org/packages/zircote/swagger-php) +[![License](https://img.shields.io/badge/license-Apache2.0-blue.svg?style=flat-square)](LICENSE) + +# swagger-php + +Generate interactive [OpenAPI](https://www.openapis.org) documentation for your RESTful API using [doctrine annotations](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). + +For a full list of supported annotations, please have look at the [`OpenApi\Annotations` namespace](src/Annotations) or the [documentation website](https://zircote.github.io/swagger-php/guide/annotations.html). + +## Features + +- Compatible with the OpenAPI **3.0** and **3.1** specification. +- Extracts information from code & existing phpdoc annotations. +- Command-line interface available. +- [Documentation site](https://zircote.github.io/swagger-php/) with a getting started guide. +- Exceptional error reporting (with hints, context) +- As of PHP 8.1 all annotations are also available as PHP attributes + +## OpenAPI version support + +`swagger-php` allows to generate specs either for **OpenAPI 3.0.0** or **OpenAPI 3.1.0**. +By default the spec will be in version `3.0.0`. The command line option `--version` may be used to change this +to `3.1.0`. + +Programmatically, the method `Generator::setVersion()` can be used to change the version. + +## Requirements + +`swagger-php` requires at least PHP 7.2 for annotations and PHP 8.1 for using attributes. + +## Installation (with [Composer](https://getcomposer.org)) + +```bash +composer require zircote/swagger-php +``` + +For cli usage from anywhere install swagger-php globally and make sure to place the `~/.composer/vendor/bin` directory in your PATH so the `openapi` executable can be located by your system. + +```bash +composer global require zircote/swagger-php +``` + +## Usage + +Add annotations to your php files. + +```php +/** + * @OA\Info(title="My First API", version="0.1") + */ + +/** + * @OA\Get( + * path="/api/resource.json", + * @OA\Response(response="200", description="An example resource") + * ) + */ +``` + +Visit the [Documentation website](https://zircote.github.io/swagger-php/) for the [Getting started guide](https://zircote.github.io/swagger-php/guide) or look at the [Examples directory](Examples/) for more examples. + +### Usage from php + +Generate always-up-to-date documentation. + +```php +toYaml(); +``` +Documentation of how to use the `Generator` class can be found in the [Generator reference](https://zircote.github.io/swagger-php/reference/generator). + +### Usage from the Command Line Interface + +The `openapi` command line interface can be used to generate the documentation to a static yaml/json file. + +```bash +./vendor/bin/openapi --help +``` +Starting with version 4 the default analyser used on the command line is the new `ReflectionAnalyser`. + +Using the `--legacy` flag (`-l`) the legacy `TokenAnalyser` can still be used. + +### Usage from the Deserializer + +Generate the OpenApi annotation object from a json string, which makes it easier to manipulate objects programmatically. + +```php +deserialize($jsonString, 'OpenApi\Annotations\OpenApi'); +echo $openapi->toJson(); +``` + +### Usage from [docker](https://docker.com) + +Generate the swagger documentation to a static json file. + +``` +docker run -v "$PWD":/app -it tico/swagger-php --help +``` + +## More on OpenApi & Swagger + +- https://swagger.io +- https://www.openapis.org +- [OpenApi Documentation](https://swagger.io/docs/) +- [OpenApi Specification](http://swagger.io/specification/) +- [Related projects](docs/related-projects.md) + +## Contributing + +Feel free to submit [Github Issues](https://github.com/zircote/swagger-php/issues) +or pull requests. + +The documentation website is build from the [docs](docs/) folder with [vitepress](https://vitepress.vuejs.org). + +Make sure pull requests pass [PHPUnit](https://phpunit.de/) +and [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (PSR-2) tests. + +### To run both unit tests and linting execute: +```bash +composer test +``` + +### To run static-analysis execute: +```bash +composer analyse +``` + +### Running unit tests only: +```bash +./bin/phpunit +``` + +### Regenerate reference markup docs +```bash +composer docs:gen +``` + +### Running linting only: +```bash +composer lint +``` + +### To make `php-cs-fixer` fix linting errors: +```bash +composer cs +``` diff --git a/Sources/API/vendor/zircote/swagger-php/composer.json b/Sources/API/vendor/zircote/swagger-php/composer.json new file mode 100644 index 0000000..a68b5f4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/composer.json @@ -0,0 +1,118 @@ +{ + "name": "zircote/swagger-php", + "type": "library", + "license": "Apache-2.0", + "bin": [ + "bin/openapi" + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "keywords": [ + "json", + "rest", + "api", + "service discovery" + ], + "homepage": "https://github.com/zircote/swagger-php/", + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "config": { + "bin-dir": "bin", + "optimize-autoloader": true, + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true + } + }, + "minimum-stability": "stable", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "require": { + "php": ">=7.2", + "ext-json": "*", + "doctrine/annotations": "^1.7", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "autoload-dev": { + "exclude-from-classmap": [ + "/tests/Fixtures" + ], + "psr-4": { + "OpenApi\\Tools\\": "tools/src/", + "OpenApi\\Tests\\": "tests/", + "AnotherNamespace\\": "tests/Fixtures/AnotherNamespace" + } + }, + "scripts-descriptions": { + "cs": "Fix all codestyle issues", + "lint": "Test codestyle", + "test": "Run all non-legacy and codestyle tests", + "testlegacy": "Run tests using the legacy TokenAnalyser", + "testall": "Run all tests (test + testlegacy)", + "analyse": "Run static analysis (phpstan/psalm)", + "spectral": "Run spectral lint over all .yaml files in the Examples folder", + "docs:refgen": "Rebuild the annotations/attributes reference markup files", + "docs:dev": "Run dev server for local development of gh-pages", + "docs:build": "Re-build static gh-pages" + }, + "scripts": { + "cs": "export XDEBUG_MODE=off && php-cs-fixer fix --allow-risky=yes", + "lint": "@cs --dry-run", + "test": [ + "export XDEBUG_MODE=off && phpunit", + "@lint" + ], + "testlegacy": "export XDEBUG_MODE=off && export PHPUNIT_ANALYSER=legacy && phpunit", + "testall": [ + "@test", + "@testlegacy" + ], + "analyse": [ + "export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G", + "export XDEBUG_MODE=off && psalm" + ], + "spectral": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done", + "docs:refgen": "php tools/refgen.php", + "docs:procgen": "php tools/procgen.php", + "docs:gen": [ + "@docs:refgen", + "@docs:procgen" + ], + "docs:dev": "cd docs && npm run dev", + "docs:build": [ + "@docs:gen", + "cd docs && npm run build" + ] + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php new file mode 100644 index 0000000..864d226 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AnalyserInterface.php @@ -0,0 +1,18 @@ + top level annotations + */ + public function build(\Reflector $reflector, Context $context): array; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php new file mode 100644 index 0000000..a8a1d84 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/AttributeAnnotationFactory.php @@ -0,0 +1,142 @@ +generator = $generator; + } + + public function build(\Reflector $reflector, Context $context): array + { + if (\PHP_VERSION_ID < 80100 || !method_exists($reflector, 'getAttributes')) { + return []; + } + + if ($reflector instanceof \ReflectionProperty && method_exists($reflector, 'isPromoted') && $reflector->isPromoted()) { + // handled via __construct() parameter + return []; + } + + // no proper way to inject + Generator::$context = $context; + + /** @var OA\AbstractAnnotation[] $annotations */ + $annotations = []; + try { + foreach ($reflector->getAttributes() as $attribute) { + try { + $instance = $attribute->newInstance(); + if ($instance instanceof OA\AbstractAnnotation) { + $annotations[] = $instance; + } + } catch (\Error $e) { + $context->logger->debug('Could not instantiate attribute: ' . $e->getMessage(), ['exception' => $e]); + } + } + + if ($reflector instanceof \ReflectionMethod) { + // also look at parameter attributes + foreach ($reflector->getParameters() as $rp) { + foreach ([OAT\Property::class, OAT\Parameter::class, OAT\PathParameter::class] as $attributeName) { + foreach ($rp->getAttributes($attributeName) as $attribute) { + $instance = $attribute->newInstance(); + $type = (($rnt = $rp->getType()) && $rnt instanceof \ReflectionNamedType) ? $rnt->getName() : Generator::UNDEFINED; + $nullable = $rnt ? $rnt->allowsNull() : true; + + if ($instance instanceof OAT\Property) { + $instance->property = $rp->getName(); + if (Generator::isDefault($instance->type)) { + $instance->type = $type; + } + $instance->nullable = $nullable; + } else { + $instance->name = $rp->getName(); + $instance->required = !$nullable; + $context = new Context(['nested' => $this], $context); + $context->comment = null; + $instance->merge([new OA\Schema(['type' => $type, '_context' => $context])]); + } + $annotations[] = $instance; + } + } + } + + if (($rrt = $reflector->getReturnType()) && $rrt instanceof \ReflectionNamedType) { + foreach ($annotations as $annotation) { + if ($annotation instanceof OAT\Property && Generator::isDefault($annotation->type)) { + // pick up simple return types + $annotation->type = $rrt->getName(); + } + } + } + } + } finally { + Generator::$context = null; + } + + $annotations = array_values(array_filter($annotations, function ($a) { + return $a !== null && $a instanceof OA\AbstractAnnotation; + })); + + // merge backwards into parents... + $isParent = function (OA\AbstractAnnotation $annotation, OA\AbstractAnnotation $possibleParent): bool { + // regular annotation hierarchy + $explicitParent = null !== $possibleParent::matchNested(get_class($annotation)); + + $isParentAllowed = false; + // support Attachable subclasses + if ($isAttachable = $annotation instanceof OAT\Attachable) { + if (!$isParentAllowed = (null === $annotation->allowedParents())) { + // check for allowed parents + foreach ($annotation->allowedParents() as $allowedParent) { + if ($possibleParent instanceof $allowedParent) { + $isParentAllowed = true; + break; + } + } + } + } + + // Property can be nested... + return get_class($annotation) != get_class($possibleParent) + && ($explicitParent || ($isAttachable && $isParentAllowed)); + }; + + $annotationsWithoutParent = []; + foreach ($annotations as $index => $annotation) { + $mergedIntoParent = false; + + for ($ii = 0; $ii < count($annotations); ++$ii) { + if ($ii === $index) { + continue; + } + $possibleParent = $annotations[$ii]; + if ($isParent($annotation, $possibleParent)) { + $mergedIntoParent = true; // + $possibleParent->merge([$annotation]); + } + } + + if (!$mergedIntoParent) { + $annotationsWithoutParent[] = $annotation; + } + } + + return $annotationsWithoutParent; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php new file mode 100644 index 0000000..572c264 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ComposerAutoloaderScanner.php @@ -0,0 +1,53 @@ + $namespaces + * + * @return array + */ + public function scan(array $namespaces): array + { + $units = []; + if ($autoloader = $this->getComposerAutoloader()) { + foreach (array_keys($autoloader->getClassMap()) as $unit) { + foreach ($namespaces as $namespace) { + if (0 === strpos($unit, $namespace)) { + $units[] = $unit; + break; + } + } + } + } + + return $units; + } + + public static function getComposerAutoloader(): ?ClassLoader + { + foreach (spl_autoload_functions() as $fkt) { + if (is_array($fkt) && $fkt[0] instanceof ClassLoader) { + return $fkt[0]; + } + } + + return null; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php new file mode 100644 index 0000000..21d1768 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php @@ -0,0 +1,58 @@ +docBlockParser = $docBlockParser ?: new DocBlockParser(); + } + + public function setGenerator(Generator $generator): void + { + $this->generator = $generator; + + $this->docBlockParser->setAliases($generator->getAliases()); + } + + public function build(\Reflector $reflector, Context $context): array + { + $aliases = $this->generator ? $this->generator->getAliases() : []; + + if (method_exists($reflector, 'getShortName') && method_exists($reflector, 'getName')) { + $aliases[strtolower($reflector->getShortName())] = $reflector->getName(); + } + + if ($context->with('scanned')) { + $details = $context->scanned; + foreach ($details['uses'] as $alias => $name) { + $aliasKey = strtolower($alias); + if ($name != $alias && !array_key_exists($aliasKey, $aliases)) { + // real aliases only + $aliases[strtolower($alias)] = $name; + } + } + } + $this->docBlockParser->setAliases($aliases); + + if (method_exists($reflector, 'getDocComment') && ($comment = $reflector->getDocComment())) { + return $this->docBlockParser->fromComment($comment, $context); + } + + return []; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php new file mode 100644 index 0000000..fa89047 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php @@ -0,0 +1,83 @@ + $aliases + */ + public function __construct(array $aliases = []) + { + $docParser = new DocParser(); + $docParser->setIgnoreNotImportedAnnotations(true); + $docParser->setImports($aliases); + $this->docParser = $docParser; + } + + /** + * @param array $aliases + */ + public function setAliases(array $aliases): void + { + $this->docParser->setImports($aliases); + } + + /** + * Use doctrine to parse the comment block and return the detected annotations. + * + * @param string $comment a T_DOC_COMMENT + * @param Context $context + * + * @return array + */ + public function fromComment(string $comment, Context $context): array + { + $context->comment = $comment; + + try { + Generator::$context = $context; + if ($context->is('annotations') === false) { + $context->annotations = []; + } + + return $this->docParser->parse($comment, $context->getDebugLocation()); + } catch (\Exception $e) { + if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string) $context, '/') . '\.$/', $e->getMessage(), $matches)) { + $errorMessage = $matches[1]; + $errorPos = (int) $matches[2]; + $atPos = strpos($comment, '@'); + $context->line += substr_count($comment, "\n", 0, $atPos + $errorPos); + $lines = explode("\n", substr($comment, $atPos, $errorPos)); + $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 + $context->logger->error($errorMessage . ' in ' . $context, ['exception' => $e]); + } else { + $context->logger->error( + $e->getMessage() . ($context->filename ? ('; file=' . $context->filename) : ''), + ['exception' => $e] + ); + } + + return []; + } finally { + Generator::$context = null; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php new file mode 100644 index 0000000..bcd18f1 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php @@ -0,0 +1,190 @@ + $annotationFactories + */ + public function __construct(array $annotationFactories = []) + { + $this->annotationFactories = $annotationFactories; + if (!$this->annotationFactories) { + throw new \InvalidArgumentException('Need at least one annotation factory'); + } + } + + public function setGenerator(Generator $generator): void + { + $this->generator = $generator; + + foreach ($this->annotationFactories as $annotationFactory) { + $annotationFactory->setGenerator($generator); + } + } + + public function fromFile(string $filename, Context $context): Analysis + { + $scanner = new TokenScanner(); + $fileDetails = $scanner->scanFile($filename); + + $analysis = new Analysis([], $context); + foreach ($fileDetails as $fqdn => $details) { + $this->analyzeFqdn($fqdn, $analysis, $details); + } + + return $analysis; + } + + public function fromFqdn(string $fqdn, Analysis $analysis): Analysis + { + $fqdn = ltrim($fqdn, '\\'); + + $rc = new \ReflectionClass($fqdn); + if (!$filename = $rc->getFileName()) { + return $analysis; + } + + $scanner = new TokenScanner(); + $fileDetails = $scanner->scanFile($filename); + + $this->analyzeFqdn($fqdn, $analysis, $fileDetails[$fqdn]); + + return $analysis; + } + + protected function analyzeFqdn(string $fqdn, Analysis $analysis, array $details): Analysis + { + if (!class_exists($fqdn) && !interface_exists($fqdn) && !trait_exists($fqdn) && (!function_exists('enum_exists') || !enum_exists($fqdn))) { + $analysis->context->logger->warning('Skipping unknown ' . $fqdn); + + return $analysis; + } + + $rc = new \ReflectionClass($fqdn); + $contextType = $rc->isInterface() ? 'interface' : ($rc->isTrait() ? 'trait' : ((method_exists($rc, 'isEnum') && $rc->isEnum()) ? 'enum' : 'class')); + $context = new Context([ + $contextType => $rc->getShortName(), + 'namespace' => $rc->getNamespaceName() ?: null, + 'uses' => $details['uses'], + 'comment' => $rc->getDocComment() ?: null, + 'filename' => $rc->getFileName() ?: null, + 'line' => $rc->getStartLine(), + 'annotations' => [], + 'scanned' => $details, + ], $analysis->context); + + $definition = [ + $contextType => $rc->getShortName(), + 'extends' => null, + 'implements' => [], + 'traits' => [], + 'properties' => [], + 'methods' => [], + 'context' => $context, + ]; + $normaliseClass = function (string $name): string { + return '\\' . ltrim($name, '\\'); + }; + if ($parentClass = $rc->getParentClass()) { + $definition['extends'] = $normaliseClass($parentClass->getName()); + } + $definition[$contextType == 'class' ? 'implements' : 'extends'] = array_map($normaliseClass, $details['interfaces']); + $definition['traits'] = array_map($normaliseClass, $details['traits']); + + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($rc, $context), $context); + } + + foreach ($rc->getMethods() as $method) { + if (in_array($method->name, $details['methods'])) { + $definition['methods'][$method->getName()] = $ctx = new Context([ + 'method' => $method->getName(), + 'comment' => $method->getDocComment() ?: null, + 'filename' => $method->getFileName() ?: null, + 'line' => $method->getStartLine(), + 'annotations' => [], + ], $context); + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($method, $ctx), $ctx); + } + } + } + + foreach ($rc->getProperties() as $property) { + if (in_array($property->name, $details['properties'])) { + $definition['properties'][$property->getName()] = $ctx = new Context([ + 'property' => $property->getName(), + 'comment' => $property->getDocComment() ?: null, + 'annotations' => [], + ], $context); + if ($property->isStatic()) { + $ctx->static = true; + } + if (\PHP_VERSION_ID >= 70400 && ($type = $property->getType())) { + $ctx->nullable = $type->allowsNull(); + if ($type instanceof \ReflectionNamedType) { + $ctx->type = $type->getName(); + // Context::fullyQualifiedName(...) expects this + if (class_exists($absFqn = '\\' . $ctx->type)) { + $ctx->type = $absFqn; + } + } + } + foreach ($this->annotationFactories as $annotationFactory) { + $analysis->addAnnotations($annotationFactory->build($property, $ctx), $ctx); + } + } + } + + foreach ($rc->getReflectionConstants() as $constant) { + foreach ($this->annotationFactories as $annotationFactory) { + $definition['constants'][$constant->getName()] = $ctx = new Context([ + 'constant' => $constant->getName(), + 'comment' => $constant->getDocComment() ?: null, + 'annotations' => [], + ], $context); + foreach ($annotationFactory->build($constant, $ctx) as $annotation) { + if ($annotation instanceof OA\Property) { + if (Generator::isDefault($annotation->property)) { + $annotation->property = $constant->getName(); + } + if (Generator::isDefault($annotation->const)) { + $annotation->const = $constant->getValue(); + } + $analysis->addAnnotation($annotation, $ctx); + } + } + } + } + + $addDefinition = 'add' . ucfirst($contextType) . 'Definition'; + $analysis->{$addDefinition}($definition); + + return $analysis; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php new file mode 100644 index 0000000..07e252c --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenAnalyser.php @@ -0,0 +1,639 @@ +generator = $generator; + } + + /** + * Extract and process all doc-comments from a file. + * + * @param string $filename path to a php file + */ + public function fromFile(string $filename, Context $context): Analysis + { + if (function_exists('opcache_get_status') && function_exists('opcache_get_configuration')) { + if (empty($GLOBALS['openapi_opcache_warning'])) { + $GLOBALS['openapi_opcache_warning'] = true; + $status = opcache_get_status(); + $config = opcache_get_configuration(); + if (is_array($status) && $status['opcache_enabled'] && $config['directives']['opcache.save_comments'] == false) { + $context->logger->error("php.ini \"opcache.save_comments = 0\" interferes with extracting annotations.\n[LINK] https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments"); + } + } + } + $tokens = token_get_all(file_get_contents($filename)); + + return $this->fromTokens($tokens, new Context(['filename' => $filename], $context)); + } + + /** + * Extract and process all doc-comments from the contents. + * + * @param string $code PHP code. (including fromTokens($tokens, $context); + } + + /** + * Shared implementation for parseFile() & parseContents(). + * + * @param array $tokens The result of a token_get_all() + */ + protected function fromTokens(array $tokens, Context $parseContext): Analysis + { + $generator = $this->generator ?: new Generator(); + $analysis = new Analysis([], $parseContext); + $docBlockParser = new DocBlockParser($generator->getAliases()); + + reset($tokens); + $token = ''; + + $aliases = $generator->getAliases(); + + $parseContext->uses = []; + // default to parse context to start with + $schemaContext = $parseContext; + + $classDefinition = false; + $interfaceDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + $comment = false; + + $line = 0; + $lineOffset = $parseContext->line ?: 0; + + while ($token !== false) { + $previousToken = $token; + $token = $this->nextToken($tokens, $parseContext); + + if (is_array($token) === false) { + // Ignore tokens like "{", "}", etc + continue; + } + + if (defined('T_ATTRIBUTE') && $token[0] === T_ATTRIBUTE) { + // consume + $this->parseAttribute($tokens, $token, $parseContext); + continue; + } + + if ($token[0] === T_DOC_COMMENT) { + if ($comment) { + // 2 Doc-comments in succession? + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + } + $comment = $token[1]; + $line = $token[2] + $lineOffset; + continue; + } + + if (in_array($token[0], [T_ABSTRACT, T_FINAL])) { + // skip + $token = $this->nextToken($tokens, $parseContext); + } + + if ($token[0] === T_CLASS) { + // Doc-comment before a class? + if (is_array($previousToken) && $previousToken[0] === T_DOUBLE_COLON) { + // php 5.5 class name resolution (i.e. ClassName::class) + continue; + } + + $token = $this->nextToken($tokens, $parseContext); + + if (is_string($token) && ($token === '(' || $token === '{')) { + // php7 anonymous classes (i.e. new class() { public function foo() {} };) + continue; + } + + if (is_array($token) && ($token[1] === 'extends' || $token[1] === 'implements')) { + // php7 anonymous classes with extends (i.e. new class() extends { public function foo() {} };) + continue; + } + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $interfaceDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + + $schemaContext = new Context(['class' => $token[1], 'line' => $token[2]], $parseContext); + if ($classDefinition) { + $analysis->addClassDefinition($classDefinition); + } + $classDefinition = [ + 'class' => $token[1], + 'extends' => null, + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_EXTENDS) { + $schemaContext->extends = $this->parseNamespace($tokens, $token, $parseContext); + $classDefinition['extends'] = $schemaContext->fullyQualifiedName($schemaContext->extends); + } + + if ($token[0] === T_IMPLEMENTS) { + $schemaContext->implements = $this->parseNamespaceList($tokens, $token, $parseContext); + $classDefinition['implements'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->implements); + } + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-class and reset $schemaContext + } + + if ($token[0] === T_INTERFACE) { // Doc-comment before an interface? + $classDefinition = false; + $traitDefinition = false; + $enumDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['interface' => $token[1], 'line' => $token[2]], $parseContext); + if ($interfaceDefinition) { + $analysis->addInterfaceDefinition($interfaceDefinition); + } + $interfaceDefinition = [ + 'interface' => $token[1], + 'extends' => null, + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_EXTENDS) { + $schemaContext->extends = $this->parseNamespaceList($tokens, $token, $parseContext); + $interfaceDefinition['extends'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->extends); + } + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-interface and reset $schemaContext + } + + if ($token[0] === T_TRAIT) { + $classDefinition = false; + $interfaceDefinition = false; + $enumDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['trait' => $token[1], 'line' => $token[2]], $parseContext); + if ($traitDefinition) { + $analysis->addTraitDefinition($traitDefinition); + } + $traitDefinition = [ + 'trait' => $token[1], + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-trait and reset $schemaContext + } + + if (defined('T_ENUM') && $token[0] === T_ENUM) { + $classDefinition = false; + $interfaceDefinition = false; + $traitDefinition = false; + + $token = $this->nextToken($tokens, $parseContext); + + if (!is_array($token)) { + // PHP 8 named argument + continue; + } + + $schemaContext = new Context(['enum' => $token[1], 'line' => $token[2]], $parseContext); + if ($enumDefinition) { + $analysis->addEnumDefinition($enumDefinition); + } + $enumDefinition = [ + 'enum' => $token[1], + 'properties' => [], + 'methods' => [], + 'context' => $schemaContext, + ]; + + if ($comment) { + $schemaContext->line = $line; + $this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext); + $comment = false; + continue; + } + + // @todo detect end-of-trait and reset $schemaContext + } + + if ($token[0] === T_STATIC) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_VARIABLE) { + // static property + $propertyContext = new Context( + [ + 'property' => substr($token[1], 1), + 'static' => true, + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($traitDefinition) { + $traitDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext); + $comment = false; + } + continue; + } + } + + if (in_array($token[0], [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR])) { // Scope + [$type, $nullable, $token] = $this->parseTypeAndNextToken($tokens, $parseContext); + if ($token[0] === T_VARIABLE) { + // instance property + $propertyContext = new Context( + [ + 'property' => substr($token[1], 1), + 'type' => $type, + 'nullable' => $nullable, + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($traitDefinition) { + $traitDefinition['properties'][$propertyContext->property] = $propertyContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext); + $comment = false; + } + } elseif ($token[0] === T_FUNCTION) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_STRING) { + $methodContext = new Context( + [ + 'method' => $token[1], + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['methods'][$token[1]] = $methodContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['methods'][$token[1]] = $methodContext; + } + if ($traitDefinition) { + $traitDefinition['methods'][$token[1]] = $methodContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $methodContext); + $comment = false; + } + } + } + continue; + } elseif ($token[0] === T_FUNCTION) { + $token = $this->nextToken($tokens, $parseContext); + if ($token[0] === T_STRING) { + $methodContext = new Context( + [ + 'method' => $token[1], + 'line' => $line, + ], + $schemaContext + ); + + if ($classDefinition) { + $classDefinition['methods'][$token[1]] = $methodContext; + } + if ($interfaceDefinition) { + $interfaceDefinition['methods'][$token[1]] = $methodContext; + } + if ($traitDefinition) { + $traitDefinition['methods'][$token[1]] = $methodContext; + } + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, $methodContext); + $comment = false; + } + } + } + + if (in_array($token[0], [T_NAMESPACE, T_USE]) === false) { + // Skip "use" & "namespace" to prevent "never imported" warnings) + if ($comment) { + // Not a doc-comment for a class, property or method? + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + $comment = false; + } + } + + if ($token[0] === T_NAMESPACE) { + $parseContext->namespace = $this->parseNamespace($tokens, $token, $parseContext); + $aliases['__NAMESPACE__'] = $parseContext->namespace; + $docBlockParser->setAliases($aliases); + continue; + } + + if ($token[0] === T_USE) { + $statements = $this->parseUseStatement($tokens, $token, $parseContext); + foreach ($statements as $alias => $target) { + if ($classDefinition) { + // class traits + $classDefinition['traits'][] = $schemaContext->fullyQualifiedName($target); + } elseif ($traitDefinition) { + // trait traits + $traitDefinition['traits'][] = $schemaContext->fullyQualifiedName($target); + } else { + // not a trait use + $parseContext->uses[$alias] = $target; + + $namespaces = $generator->getNamespaces(); + if (null === $namespaces) { + $aliases[strtolower($alias)] = $target; + } else { + foreach ($namespaces as $namespace) { + if (strcasecmp(substr($target . '\\', 0, strlen($namespace)), $namespace) === 0) { + $aliases[strtolower($alias)] = $target; + break; + } + } + } + $docBlockParser->setAliases($aliases); + } + } + } + } + + // cleanup final comment and definition + if ($comment) { + $this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext)); + } + if ($classDefinition) { + $analysis->addClassDefinition($classDefinition); + } + if ($interfaceDefinition) { + $analysis->addInterfaceDefinition($interfaceDefinition); + } + if ($traitDefinition) { + $analysis->addTraitDefinition($traitDefinition); + } + if ($enumDefinition) { + $analysis->addEnumDefinition($enumDefinition); + } + + return $analysis; + } + + /** + * Parse comment and add annotations to analysis. + */ + private function analyseComment(Analysis $analysis, DocBlockParser $docBlockParser, string $comment, Context $context): void + { + $analysis->addAnnotations($docBlockParser->fromComment($comment, $context), $context); + } + + /** + * The next non-whitespace, non-comment token. + * + * + * @return array|string The next token (or false) + */ + private function nextToken(array &$tokens, Context $context) + { + while (true) { + $token = next($tokens); + if (is_array($token)) { + if ($token[0] === T_WHITESPACE) { + continue; + } + if ($token[0] === T_COMMENT) { + $pos = strpos($token[1], '@OA\\'); + if ($pos) { + $line = $context->line ? $context->line + $token[2] : $token[2]; + $commentContext = new Context(['line' => $line], $context); + $context->logger->warning('Annotations are only parsed inside `/**` DocBlocks, skipping ' . $commentContext); + } + continue; + } + } + + return $token; + } + } + + private function parseAttribute(array &$tokens, &$token, Context $parseContext): void + { + $nesting = 1; + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + if (!is_array($token) && '[' === $token) { + ++$nesting; + continue; + } + + if (!is_array($token) && ']' === $token) { + --$nesting; + if (!$nesting) { + break; + } + } + } + } + + /** + * @return int[] + */ + private function php8NamespaceToken(): array + { + return defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : []; + } + + /** + * Parse namespaced string. + * + * @param array|string $token + */ + private function parseNamespace(array &$tokens, &$token, Context $parseContext): string + { + $namespace = ''; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken()); + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + if (!in_array($token[0], $nsToken)) { + break; + } + $namespace .= $token[1]; + } + + return $namespace; + } + + /** + * Parse comma separated list of namespaced strings. + * + * @param array|string $token + */ + private function parseNamespaceList(array &$tokens, &$token, Context $parseContext): array + { + $namespaces = []; + while ($namespace = $this->parseNamespace($tokens, $token, $parseContext)) { + $namespaces[] = $namespace; + if ($token != ',') { + break; + } + } + + return $namespaces; + } + + /** + * Parse a use statement. + */ + private function parseUseStatement(array &$tokens, &$token, Context $parseContext): array + { + $normalizeAlias = function ($alias): string { + $alias = ltrim($alias, '\\'); + $elements = explode('\\', $alias); + + return array_pop($elements); + }; + + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken()); + while ($token !== false) { + $token = $this->nextToken($tokens, $parseContext); + $isNameToken = in_array($token[0], $nsToken); + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token === ',') { + $statements[$normalizeAlias($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[$normalizeAlias($alias)] = $class; + break; + } else { + break; + } + } + + return $statements; + } + + /** + * Parse type of variable (if it exists). + */ + private function parseTypeAndNextToken(array &$tokens, Context $parseContext): array + { + $type = Generator::UNDEFINED; + $nullable = false; + $token = $this->nextToken($tokens, $parseContext); + + if ($token[0] === T_STATIC) { + $token = $this->nextToken($tokens, $parseContext); + } + + if ($token === '?') { // nullable type + $nullable = true; + $token = $this->nextToken($tokens, $parseContext); + } + + $qualifiedToken = array_merge([T_NS_SEPARATOR, T_STRING, T_ARRAY], $this->php8NamespaceToken()); + $typeToken = array_merge([T_STRING], $this->php8NamespaceToken()); + // drill down namespace segments to basename property type declaration + while (in_array($token[0], $qualifiedToken)) { + if (in_array($token[0], $typeToken)) { + $type = $token[1]; + } + $token = $this->nextToken($tokens, $parseContext); + } + + return [$type, $nullable, $token]; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php new file mode 100644 index 0000000..1df38be --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php @@ -0,0 +1,366 @@ +scanTokens(token_get_all(file_get_contents($filename))); + } + + /** + * Scan file for all classes, interfaces and traits. + * + * @return array> File details + */ + protected function scanTokens(array $tokens): array + { + $units = []; + $uses = []; + $isInterface = false; + $namespace = ''; + $currentName = null; + $unitLevel = 0; + $lastToken = null; + $stack = []; + + $initUnit = function ($uses): array { + return [ + 'uses' => $uses, + 'interfaces' => [], + 'traits' => [], + 'enums' => [], + 'methods' => [], + 'properties' => [], + ]; + }; + + while (false !== ($token = $this->nextToken($tokens))) { + if (!is_array($token)) { + switch ($token) { + case '{': + $stack[] = $token; + break; + case '}': + array_pop($stack); + if (count($stack) == $unitLevel) { + $currentName = null; + } + break; + } + continue; + } + + switch ($token[0]) { + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + $stack[] = $token[1]; + break; + + case T_NAMESPACE: + $namespace = $this->nextWord($tokens); + break; + + case T_USE: + if (!$stack) { + $uses = array_merge($uses, $this->parseFQNStatement($tokens, $token)); + } elseif ($currentName) { + $traits = $this->resolveFQN($this->parseFQNStatement($tokens, $token), $namespace, $uses); + $units[$currentName]['traits'] = array_merge($units[$currentName]['traits'], $traits); + } + break; + + case T_CLASS: + if ($currentName) { + break; + } + + if ($lastToken && is_array($lastToken) && $lastToken[0] === T_DOUBLE_COLON) { + // ::class + break; + } + + // class name + $token = $this->nextToken($tokens); + + // unless ... + if (is_string($token) && ($token === '(' || $token === '{')) { + // new class[()] { ... } + if ('{' == $token) { + prev($tokens); + } + break; + } elseif (is_array($token) && in_array($token[1], ['extends', 'implements'])) { + // new class[()] extends { ... } + break; + } + + $isInterface = false; + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $units[$currentName] = $initUnit($uses); + break; + + case T_INTERFACE: + if ($currentName) { + break; + } + + $isInterface = true; + $token = $this->nextToken($tokens); + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $units[$currentName] = $initUnit($uses); + break; + + case T_EXTENDS: + $fqns = $this->parseFQNStatement($tokens, $token); + if ($isInterface && $currentName) { + $units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses); + } + if (!is_array($token) || T_IMPLEMENTS !== $token[0]) { + break; + } + // no break + case T_IMPLEMENTS: + $fqns = $this->parseFQNStatement($tokens, $token); + if ($currentName) { + $units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses); + } + break; + + case T_FUNCTION: + $token = $this->nextToken($tokens); + if ((!is_array($token) && '&' == $token) + || (defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') && T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG == $token[0])) { + $token = $this->nextToken($tokens); + } + + if (($unitLevel + 1) == count($stack) && $currentName) { + $units[$currentName]['methods'][] = $token[1]; + if (!$isInterface) { + // more nesting + $units[$currentName]['properties'] = array_merge( + $units[$currentName]['properties'], + $this->parsePromotedProperties($tokens) + ); + $this->skipTo($tokens, '{', true); + } else { + // no function body + $this->skipTo($tokens, ';'); + } + } + break; + + case T_VARIABLE: + if (($unitLevel + 1) == count($stack) && $currentName) { + $units[$currentName]['properties'][] = substr($token[1], 1); + } + break; + default: + // handle trait here too to avoid duplication + if (T_TRAIT === $token[0] || (defined('T_ENUM') && T_ENUM === $token[0])) { + if ($currentName) { + break; + } + + $isInterface = false; + $token = $this->nextToken($tokens); + $currentName = $namespace . '\\' . $token[1]; + $unitLevel = count($stack); + $this->skipTo($tokens, '{', true); + $units[$currentName] = $initUnit($uses); + } + break; + } + $lastToken = $token; + } + + /* @phpstan-ignore-next-line */ + return $units; + } + + /** + * Get the next token that is not whitespace or comment. + * + * @return string|array + */ + protected function nextToken(array &$tokens) + { + $token = true; + while ($token) { + $token = next($tokens); + if (is_array($token)) { + if (in_array($token[0], [T_WHITESPACE, T_COMMENT])) { + continue; + } + } + + return $token; + } + + return $token; + } + + /** + * @return array + */ + protected function resolveFQN(array $names, string $namespace, array $uses): array + { + $resolve = function ($name) use ($namespace, $uses) { + if ('\\' == $name[0]) { + return substr($name, 1); + } + + if (array_key_exists($name, $uses)) { + return $uses[$name]; + } + + return $namespace . '\\' . $name; + }; + + return array_values(array_map($resolve, $names)); + } + + protected function skipTo(array &$tokens, string $char, bool $prev = false): void + { + while (false !== ($token = next($tokens))) { + if (is_string($token) && $token == $char) { + if ($prev) { + prev($tokens); + } + + break; + } + } + } + + /** + * Read next word. + * + * Skips leading whitespace. + */ + protected function nextWord(array &$tokens): string + { + $word = ''; + while (false !== ($token = next($tokens))) { + if (is_array($token)) { + if ($token[0] === T_WHITESPACE) { + if ($word) { + break; + } + continue; + } + $word .= $token[1]; + } + } + + return $word; + } + + /** + * Parse a use statement. + */ + protected function parseFQNStatement(array &$tokens, array &$token): array + { + $normalizeAlias = function ($alias): string { + $alias = ltrim($alias, '\\'); + $elements = explode('\\', $alias); + + return array_pop($elements); + }; + + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + $php8NSToken = defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : []; + $nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $php8NSToken); + while ($token !== false) { + $token = $this->nextToken($tokens); + $isNameToken = in_array($token[0], $nsToken); + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token[0] === T_IMPLEMENTS) { + $statements[$normalizeAlias($alias)] = $class; + break; + } elseif ($token === ',') { + $statements[$normalizeAlias($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[$normalizeAlias($alias)] = $class; + break; + } elseif ($token === '{') { + $statements[$normalizeAlias($alias)] = $class; + prev($tokens); + break; + } else { + break; + } + } + + return $statements; + } + + protected function parsePromotedProperties(array &$tokens): array + { + $properties = []; + + $this->skipTo($tokens, '('); + $round = 1; + $promoted = false; + while (false !== ($token = $this->nextToken($tokens))) { + if (is_string($token)) { + switch ($token) { + case '(': + ++$round; + break; + case ')': + --$round; + if (0 == $round) { + return $properties; + } + } + } + if (is_array($token)) { + switch ($token[0]) { + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + $promoted = true; + break; + case T_VARIABLE: + if ($promoted) { + $properties[] = ltrim($token[1], '$'); + $promoted = false; + } + break; + } + } + } + + /* @phpstan-ignore-next-line */ + return $properties; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Analysis.php b/Sources/API/vendor/zircote/swagger-php/src/Analysis.php new file mode 100644 index 0000000..83a62a4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Analysis.php @@ -0,0 +1,439 @@ +annotations = new \SplObjectStorage(); + $this->context = $context; + + $this->addAnnotations($annotations, $context); + } + + public function addAnnotation(object $annotation, Context $context): void + { + if ($this->annotations->contains($annotation)) { + return; + } + + if ($annotation instanceof OA\OpenApi) { + $this->openapi = $this->openapi ?: $annotation; + } else { + if ($context->is('annotations') === false) { + $context->annotations = []; + } + + if (in_array($annotation, $context->annotations, true) === false) { + $context->annotations[] = $annotation; + } + } + $this->annotations->attach($annotation, $context); + $blacklist = property_exists($annotation, '_blacklist') ? $annotation::$_blacklist : []; + foreach ($annotation as $property => $value) { + if (in_array($property, $blacklist)) { + if ($property === '_unmerged') { + foreach ($value as $item) { + $this->addAnnotation($item, $context); + } + } + } elseif (is_array($value)) { + foreach ($value as $item) { + if ($item instanceof OA\AbstractAnnotation) { + $this->addAnnotation($item, $context); + } + } + } elseif ($value instanceof OA\AbstractAnnotation) { + $this->addAnnotation($value, $context); + } + } + } + + public function addAnnotations(array $annotations, Context $context): void + { + foreach ($annotations as $annotation) { + $this->addAnnotation($annotation, $context); + } + } + + public function addClassDefinition(array $definition): void + { + $class = $definition['context']->fullyQualifiedName($definition['class']); + $this->classes[$class] = $definition; + } + + public function addInterfaceDefinition(array $definition): void + { + $interface = $definition['context']->fullyQualifiedName($definition['interface']); + $this->interfaces[$interface] = $definition; + } + + public function addTraitDefinition(array $definition): void + { + $trait = $definition['context']->fullyQualifiedName($definition['trait']); + $this->traits[$trait] = $definition; + } + + public function addEnumDefinition(array $definition): void + { + $enum = $definition['context']->fullyQualifiedName($definition['enum']); + $this->enums[$enum] = $definition; + } + + public function addAnalysis(Analysis $analysis): void + { + foreach ($analysis->annotations as $annotation) { + $this->addAnnotation($annotation, $analysis->annotations[$annotation]); + } + $this->classes = array_merge($this->classes, $analysis->classes); + $this->interfaces = array_merge($this->interfaces, $analysis->interfaces); + $this->traits = array_merge($this->traits, $analysis->traits); + $this->enums = array_merge($this->enums, $analysis->enums); + if ($this->openapi === null && $analysis->openapi !== null) { + $this->openapi = $analysis->openapi; + } + } + + /** + * Get all subclasses of the given parent class. + * + * @param string $parent the parent class + * + * @return array map of class => definition pairs of sub-classes + */ + public function getSubClasses(string $parent): array + { + $definitions = []; + foreach ($this->classes as $class => $classDefinition) { + if ($classDefinition['extends'] === $parent) { + $definitions[$class] = $classDefinition; + $definitions = array_merge($definitions, $this->getSubClasses($class)); + } + } + + return $definitions; + } + + /** + * Get a list of all super classes for the given class. + * + * @param string $class the class name + * @param bool $direct flag to find only the actual class parents + * + * @return array map of class => definition pairs of parent classes + */ + public function getSuperClasses(string $class, bool $direct = false): array + { + $classDefinition = $this->classes[$class] ?? null; + if (!$classDefinition || empty($classDefinition['extends'])) { + // unknown class, or no inheritance + return []; + } + + $extends = $classDefinition['extends']; + $extendsDefinition = $this->classes[$extends] ?? null; + if (!$extendsDefinition) { + return []; + } + + $parentDetails = [$extends => $extendsDefinition]; + + if ($direct) { + return $parentDetails; + } + + return array_merge($parentDetails, $this->getSuperClasses($extends)); + } + + /** + * Get the list of interfaces used by the given class or by classes which it extends. + * + * @param string $class the class name + * @param bool $direct flag to find only the actual class interfaces + * + * @return array map of class => definition pairs of interfaces + */ + public function getInterfacesOfClass(string $class, bool $direct = false): array + { + $classes = $direct ? [] : array_keys($this->getSuperClasses($class)); + // add self + $classes[] = $class; + + $definitions = []; + foreach ($classes as $clazz) { + if (isset($this->classes[$clazz])) { + $definition = $this->classes[$clazz]; + if (isset($definition['implements'])) { + foreach ($definition['implements'] as $interface) { + if (array_key_exists($interface, $this->interfaces)) { + $definitions[$interface] = $this->interfaces[$interface]; + } + } + } + } + } + + if (!$direct) { + // expand recursively for interfaces extending other interfaces + $collect = function ($interfaces, $cb) use (&$definitions): void { + foreach ($interfaces as $interface) { + if (isset($this->interfaces[$interface]['extends'])) { + $cb($this->interfaces[$interface]['extends'], $cb); + foreach ($this->interfaces[$interface]['extends'] as $fqdn) { + $definitions[$fqdn] = $this->interfaces[$fqdn]; + } + } + } + }; + $collect(array_keys($definitions), $collect); + } + + return $definitions; + } + + /** + * Get the list of traits used by the given class/trait or by classes which it extends. + * + * @param string $source the source name + * @param bool $direct flag to find only the actual class traits + * + * @return array map of class => definition pairs of traits + */ + public function getTraitsOfClass(string $source, bool $direct = false): array + { + $sources = $direct ? [] : array_keys($this->getSuperClasses($source)); + // add self + $sources[] = $source; + + $definitions = []; + foreach ($sources as $sourze) { + if (isset($this->classes[$sourze]) || isset($this->traits[$sourze])) { + $definition = $this->classes[$sourze] ?? $this->traits[$sourze]; + if (isset($definition['traits'])) { + foreach ($definition['traits'] as $trait) { + if (array_key_exists($trait, $this->traits)) { + $definitions[$trait] = $this->traits[$trait]; + } + } + } + } + } + + if (!$direct) { + // expand recursively for traits using other traits + $collect = function ($traits, $cb) use (&$definitions): void { + foreach ($traits as $trait) { + if (isset($this->traits[$trait]['traits'])) { + $cb($this->traits[$trait]['traits'], $cb); + foreach ($this->traits[$trait]['traits'] as $fqdn) { + $definitions[$fqdn] = $this->traits[$fqdn]; + } + } + } + }; + $collect(array_keys($definitions), $collect); + } + + return $definitions; + } + + /** + * @param string|array $classes One ore more class names + * @param bool $strict in non-strict mode child classes are also detected + * + * @return OA\AbstractAnnotation[] + */ + public function getAnnotationsOfType($classes, bool $strict = false): array + { + $annotations = []; + if ($strict) { + foreach ((array) $classes as $class) { + foreach ($this->annotations as $annotation) { + if (get_class($annotation) === $class) { + $annotations[] = $annotation; + } + } + } + } else { + foreach ((array) $classes as $class) { + foreach ($this->annotations as $annotation) { + if ($annotation instanceof $class) { + $annotations[] = $annotation; + } + } + } + } + + return $annotations; + } + + /** + * @param string $fqdn the source class/interface/trait + */ + public function getSchemaForSource(string $fqdn): ?OA\Schema + { + $fqdn = '\\' . ltrim($fqdn, '\\'); + + foreach ([$this->classes, $this->interfaces, $this->traits, $this->enums] as $definitions) { + if (array_key_exists($fqdn, $definitions)) { + $definition = $definitions[$fqdn]; + if (is_iterable($definition['context']->annotations)) { + foreach (array_reverse($definition['context']->annotations) as $annotation) { + if (in_array(get_class($annotation), [OA\Schema::class, OAT\Schema::class]) && !$annotation->_context->is('generated')) { + return $annotation; + } + } + } + } + } + + return null; + } + + public function getContext(object $annotation): ?Context + { + if ($annotation instanceof OA\AbstractAnnotation) { + return $annotation->_context; + } + if ($this->annotations->contains($annotation) === false) { + throw new \Exception('Annotation not found'); + } + $context = $this->annotations[$annotation]; + if ($context instanceof Context) { + return $context; + } + + // Weird, did you use the addAnnotation/addAnnotations methods? + throw new \Exception('Annotation has no context'); + } + + /** + * Build an analysis with only the annotations that are merged into the OpenAPI annotation. + */ + public function merged(): Analysis + { + if ($this->openapi === null) { + throw new \Exception('No openapi target set. Run the MergeIntoOpenApi processor'); + } + $unmerged = $this->openapi->_unmerged; + $this->openapi->_unmerged = []; + $analysis = new Analysis([$this->openapi], $this->context); + $this->openapi->_unmerged = $unmerged; + + return $analysis; + } + + /** + * Analysis with only the annotations that not merged. + */ + public function unmerged(): Analysis + { + return $this->split()->unmerged; + } + + /** + * Split the annotation into two analysis. + * One with annotations that are merged and one with annotations that are not merged. + * + * @return object {merged: Analysis, unmerged: Analysis} + */ + public function split() + { + $result = new \stdClass(); + $result->merged = $this->merged(); + $result->unmerged = new Analysis([], $this->context); + foreach ($this->annotations as $annotation) { + if ($result->merged->annotations->contains($annotation) === false) { + $result->unmerged->annotations->attach($annotation, $this->annotations[$annotation]); + } + } + + return $result; + } + + /** + * Apply the processor(s). + * + * @param callable|callable[] $processors One or more processors + */ + public function process($processors = null): void + { + if (is_array($processors) === false && is_callable($processors)) { + $processors = [$processors]; + } + + foreach ($processors as $processor) { + $processor($this); + } + } + + public function validate(): bool + { + if ($this->openapi !== null) { + return $this->openapi->validate(); + } + + $this->context->logger->warning('No openapi target set. Run the MergeIntoOpenApi processor before validate()'); + + return false; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php new file mode 100644 index 0000000..de5407c --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php @@ -0,0 +1,716 @@ + + */ + public $x = Generator::UNDEFINED; + + /** + * Arbitrary attachables for this annotation. + * These will be ignored but can be used for custom processing. + * + * @var array + */ + public $attachables = Generator::UNDEFINED; + + /** + * @var Context|null + */ + public $_context = null; + + /** + * Annotations that couldn't be merged by mapping or postprocessing. + * + * @var array + */ + public $_unmerged = []; + + /** + * The properties which are required by [the spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md). + * + * @var array + */ + public static $_required = []; + + /** + * Specify the type of the property. + * + * Examples: + * 'name' => 'string' // a string + * 'required' => 'boolean', // true or false + * 'tags' => '[string]', // array containing strings + * 'in' => ["query", "header", "path", "formData", "body"] // must be one on these + * 'oneOf' => [Schema::class] // array of schema objects. + * + * @var array> + */ + public static $_types = []; + + /** + * Declarative mapping of Annotation types to properties. + * Examples: + * Info::clas => 'info', // Set @OA\Info annotation as the info property. + * Parameter::clas => ['parameters'], // Append @OA\Parameter annotations the parameters array. + * PathItem::clas => ['paths', 'path'], // Append @OA\PathItem annotations the paths array and use path as key. + * + * @var array,string|array> + */ + public static $_nested = []; + + /** + * Reverse mapping of $_nested with the allowed parent annotations. + * + * @var array> + */ + public static $_parents = []; + + /** + * List of properties are blacklisted from the JSON output. + * + * @var array + */ + public static $_blacklist = ['_context', '_unmerged', '_analysis', 'attachables']; + + public function __construct(array $properties) + { + if (isset($properties['_context'])) { + $this->_context = $properties['_context']; + unset($properties['_context']); + } elseif (Generator::$context) { + $this->_context = Generator::$context; + } else { + $this->_context = Context::detect(1); + } + + if ($this->_context->is('annotations') === false) { + $this->_context->annotations = []; + } + + $this->_context->annotations[] = $this; + $nestedContext = new Context(['nested' => $this], $this->_context); + foreach ($properties as $property => $value) { + if (property_exists($this, $property)) { + $this->{$property} = $value; + if (is_array($value)) { + foreach ($value as $key => $annotation) { + if ($annotation instanceof AbstractAnnotation) { + $this->{$property}[$key] = $this->nested($annotation, $nestedContext); + } + } + } + } elseif ($property !== 'value') { + $this->{$property} = $value; + } elseif (is_array($value)) { + $annotations = []; + foreach ($value as $annotation) { + if ($annotation instanceof AbstractAnnotation) { + $annotations[] = $annotation; + } else { + $this->_context->logger->warning('Unexpected field in ' . $this->identity() . ' in ' . $this->_context); + } + } + $this->merge($annotations); + } elseif (is_object($value)) { + $this->merge([$value]); + } else { + if ($value !== Generator::UNDEFINED) { + $this->_context->logger->warning('Unexpected parameter "' . $property . '" in ' . $this->identity()); + } + } + } + + if ($this instanceof OpenApi) { + if ($this->_context->root()->version) { + // override via `Generator::setVersion()` + $this->openapi = $this->_context->root()->version; + } else { + $this->_context->root()->version = $this->openapi; + } + } + } + + public function __get(string $property) + { + $properties = get_object_vars($this); + $this->_context->logger->warning('Property "' . $property . '" doesn\'t exist in a ' . $this->identity() . ', existing properties: "' . implode('", "', array_keys($properties)) . '" in ' . $this->_context); + } + + /** + * @param mixed $value + */ + public function __set(string $property, $value): void + { + $fields = get_object_vars($this); + foreach (static::$_blacklist as $_property) { + unset($fields[$_property]); + } + $this->_context->logger->warning('Unexpected field "' . $property . '" for ' . $this->identity() . ', expecting "' . implode('", "', array_keys($fields)) . '" in ' . $this->_context); + $this->{$property} = $value; + } + + /** + * Merge given annotations to their mapped properties configured in static::$_nested. + * + * Annotations that couldn't be merged are added to the _unmerged array. + * + * @param AbstractAnnotation[] $annotations + * @param bool $ignore Ignore unmerged annotations + * + * @return AbstractAnnotation[] The unmerged annotations + */ + public function merge(array $annotations, bool $ignore = false): array + { + $unmerged = []; + $nestedContext = new Context(['nested' => $this], $this->_context); + + foreach ($annotations as $annotation) { + $mapped = false; + if ($details = static::matchNested(get_class($annotation))) { + $property = $details->value; + if (is_array($property)) { + $property = $property[0]; + if (Generator::isDefault($this->{$property})) { + $this->{$property} = []; + } + $this->{$property}[] = $this->nested($annotation, $nestedContext); + $mapped = true; + } elseif (Generator::isDefault($this->{$property})) { + // ignore duplicate nested if only one expected + $this->{$property} = $this->nested($annotation, $nestedContext); + $mapped = true; + } + } + if (!$mapped) { + $unmerged[] = $annotation; + } + } + if (!$ignore) { + foreach ($unmerged as $annotation) { + $this->_unmerged[] = $this->nested($annotation, $nestedContext); + } + } + + return $unmerged; + } + + /** + * Merge the properties from the given object into this annotation. + * Prevents overwriting properties that are already configured. + * + * @param object $object + */ + public function mergeProperties($object): void + { + $defaultValues = get_class_vars(get_class($this)); + $currentValues = get_object_vars($this); + foreach ($object as $property => $value) { + if ($property === '_context') { + continue; + } + if ($currentValues[$property] === $defaultValues[$property]) { // Overwrite default values + $this->{$property} = $value; + continue; + } + if ($property === '_unmerged') { + $this->_unmerged = array_merge($this->_unmerged, $value); + continue; + } + if ($currentValues[$property] !== $value) { // New value is not the same? + if ($defaultValues[$property] === $value) { // but is the same as the default? + continue; // Keep current, no notice + } + $identity = method_exists($object, 'identity') ? $object->identity() : get_class($object); + $context1 = $this->_context; + $context2 = property_exists($object, '_context') ? $object->_context : 'unknown'; + if (is_object($this->{$property}) && $this->{$property} instanceof AbstractAnnotation) { + $context1 = $this->{$property}->_context; + } + $this->_context->logger->error('Multiple definitions for ' . $identity . '->' . $property . "\n Using: " . $context1 . "\n Skipping: " . $context2); + } + } + } + + /** + * Generate the documentation in YAML format. + */ + public function toYaml(?int $flags = null): string + { + if ($flags === null) { + $flags = Yaml::DUMP_OBJECT_AS_MAP ^ Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE; + } + + return Yaml::dump(json_decode($this->toJson(JSON_INVALID_UTF8_IGNORE)), 10, 2, $flags); + } + + /** + * Generate the documentation in JSON format. + */ + public function toJson(?int $flags = null): string + { + if ($flags === null) { + $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_IGNORE; + } + + return json_encode($this, $flags); + } + + public function __debugInfo() + { + $properties = []; + foreach (get_object_vars($this) as $property => $value) { + if (!Generator::isDefault($value)) { + $properties[$property] = $value; + } + } + + return $properties; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = new \stdClass(); + + // Strip undefined values. + foreach (get_object_vars($this) as $property => $value) { + if (!Generator::isDefault($value)) { + $data->{$property} = $value; + } + } + + // Strip properties that are for internal (swagger-php) use. + foreach (static::$_blacklist as $property) { + unset($data->{$property}); + } + + // Correct empty array to empty objects. + foreach (static::$_types as $property => $type) { + if ($type === 'object' && is_array($data->{$property}) && empty($data->{$property})) { + $data->{$property} = new \stdClass(); + } + } + + // Inject vendor properties. + unset($data->x); + if (is_array($this->x)) { + foreach ($this->x as $property => $value) { + $prefixed = 'x-' . $property; + $data->{$prefixed} = $value; + } + } + + // Map nested keys + foreach (static::$_nested as $nested) { + if (is_string($nested) || count($nested) === 1) { + continue; + } + $property = $nested[0]; + if (Generator::isDefault($this->{$property})) { + continue; + } + $keyField = $nested[1]; + $object = new \stdClass(); + foreach ($this->{$property} as $key => $item) { + if (is_numeric($key) === false && is_array($item)) { + $object->{$key} = $item; + } else { + $key = $item->{$keyField}; + if (!Generator::isDefault($key) && empty($object->{$key})) { + if ($item instanceof \JsonSerializable) { + $object->{$key} = $item->jsonSerialize(); + } else { + $object->{$key} = $item; + } + unset($object->{$key}->{$keyField}); + } + } + } + $data->{$property} = $object; + } + + // $ref + if (isset($data->ref)) { + // Only specific https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#reference-object + $ref = ['$ref' => $data->ref]; + if ($this->_context->version == OpenApi::VERSION_3_1_0) { + $defaultValues = get_class_vars(get_class($this)); + foreach (['summary', 'description'] as $prop) { + if (property_exists($this, $prop)) { + if ($this->{$prop} !== $defaultValues[$prop]) { + $ref[$prop] = $data->{$prop}; + } + } + } + } + $data = (object) $ref; + } + + if ($this->_context->version == OpenApi::VERSION_3_1_0) { + if (isset($data->nullable)) { + if (true === $data->nullable) { + $data->type = (array) $data->type; + $data->type[] = 'null'; + } + unset($data->nullable); + } + } + + return $data; + } + + /** + * Validate annotation tree, and log notices & warnings. + * + * @param array $stack the path of annotations above this annotation in the tree + * @param array $skip (prevent stack overflow, when traversing an infinite dependency graph) + * @param string $ref Current ref path? + * @param object $context a free-form context contains + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = true; + + // Report orphaned annotations + foreach ($this->_unmerged as $annotation) { + if (!is_object($annotation)) { + $this->_context->logger->warning('Unexpected type: "' . gettype($annotation) . '" in ' . $this->identity() . '->_unmerged, expecting a Annotation object'); + break; + } + + /** @var class-string $class */ + $class = get_class($annotation); + if ($details = static::matchNested($class)) { + $property = $details->value; + if (is_array($property)) { + $this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . ' multiple found, skipped: ' . $annotation->_context); + } else { + $this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . " multiple found in:\n Using: " . $this->{$property}->_context . "\n Skipped: " . $annotation->_context); + } + } elseif ($annotation instanceof AbstractAnnotation) { + $message = 'Unexpected ' . $annotation->identity(); + if ($class::$_parents) { + $message .= ', expected to be inside ' . implode(', ', Util::shorten($class::$_parents)); + } + $this->_context->logger->warning($message . ' in ' . $annotation->_context); + } + $valid = false; + } + + // Report conflicting key + foreach (static::$_nested as $annotationClass => $nested) { + if (is_string($nested) || count($nested) === 1) { + continue; + } + $property = $nested[0]; + if (Generator::isDefault($this->{$property})) { + continue; + } + $keys = []; + $keyField = $nested[1]; + foreach ($this->{$property} as $key => $item) { + if (is_array($item) && is_numeric($key) === false) { + $this->_context->logger->warning($this->identity() . '->' . $property . ' is an object literal, use nested ' . Util::shorten($annotationClass) . '() annotation(s) in ' . $this->_context); + $keys[$key] = $item; + } elseif (Generator::isDefault($item->{$keyField})) { + $this->_context->logger->error($item->identity() . ' is missing key-field: "' . $keyField . '" in ' . $item->_context); + } elseif (isset($keys[$item->{$keyField}])) { + $this->_context->logger->error('Multiple ' . $item->_identity([]) . ' with the same ' . $keyField . '="' . $item->{$keyField} . "\":\n " . $item->_context . "\n " . $keys[$item->{$keyField}]->_context); + } else { + $keys[$item->{$keyField}] = $item; + } + } + } + + if (property_exists($this, 'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) { + if (substr($this->ref, 0, 2) === '#/' && count($stack) > 0 && $stack[0] instanceof OpenApi) { + // Internal reference + try { + $stack[0]->ref($this->ref); + } catch (\Exception $e) { + $this->_context->logger->warning($e->getMessage() . ' for ' . $this->identity() . ' in ' . $this->_context, ['exception' => $e]); + } + } + } else { + // Report missing required fields (when not a $ref) + foreach (static::$_required as $property) { + if (Generator::isDefault($this->{$property})) { + $message = 'Missing required field "' . $property . '" for ' . $this->identity() . ' in ' . $this->_context; + foreach (static::$_nested as $class => $nested) { + $nestedProperty = is_array($nested) ? $nested[0] : $nested; + if ($property === $nestedProperty) { + if ($this instanceof OpenApi) { + $message = 'Required ' . Util::shorten($class) . '() not found'; + } elseif (is_array($nested)) { + $message = $this->identity() . ' requires at least one ' . Util::shorten($class) . '() in ' . $this->_context; + } else { + $message = $this->identity() . ' requires a ' . Util::shorten($class) . '() in ' . $this->_context; + } + break; + } + } + $this->_context->logger->warning($message); + } + } + } + + // Report invalid types + foreach (static::$_types as $property => $type) { + $value = $this->{$property}; + if (Generator::isDefault($value) || $value === null) { + continue; + } + if (is_string($type)) { + if ($this->validateType($type, $value) === false) { + $valid = false; + $this->_context->logger->warning($this->identity() . '->' . $property . ' is a "' . gettype($value) . '", expecting a "' . $type . '" in ' . $this->_context); + } + } elseif (is_array($type)) { // enum? + if (in_array($value, $type) === false) { + $this->_context->logger->warning($this->identity() . '->' . $property . ' "' . $value . '" is invalid, expecting "' . implode('", "', $type) . '" in ' . $this->_context); + } + } else { + throw new \Exception('Invalid ' . get_class($this) . '::$_types[' . $property . ']'); + } + } + $stack[] = $this; + + return self::_validate($this, $stack, $skip, $ref, $context) ? $valid : false; + } + + /** + * Recursively validate all annotation properties. + * + * @param array|object $fields + */ + private static function _validate($fields, array $stack, array $skip, string $baseRef, ?object $context): bool + { + $valid = true; + $blacklist = []; + if (is_object($fields)) { + if (in_array($fields, $skip, true)) { + return true; + } + $skip[] = $fields; + $blacklist = property_exists($fields, '_blacklist') ? $fields::$_blacklist : []; + } + + foreach ($fields as $field => $value) { + if ($value === null || is_scalar($value) || in_array($field, $blacklist)) { + continue; + } + $ref = $baseRef !== '' ? $baseRef . '/' . urlencode((string) $field) : urlencode((string) $field); + if (is_object($value)) { + if (method_exists($value, 'validate')) { + if (!$value->validate($stack, $skip, $ref, $context)) { + $valid = false; + } + } elseif (!self::_validate($value, $stack, $skip, $ref, $context)) { + $valid = false; + } + } elseif (is_array($value) && !self::_validate($value, $stack, $skip, $ref, $context)) { + $valid = false; + } + } + + return $valid; + } + + /** + * Return a identity for easy debugging. + * Example: "@OA\Get(path="/pets")". + */ + public function identity(): string + { + $class = get_class($this); + $properties = []; + /** @var class-string $parent */ + foreach (static::$_parents as $parent) { + foreach ($parent::$_nested as $annotationClass => $entry) { + if ($annotationClass === $class && is_array($entry) && !Generator::isDefault($this->{$entry[1]})) { + $properties[] = $entry[1]; + break 2; + } + } + } + + return $this->_identity($properties); + } + + /** + * Find matching nested details. + * + * @param string $class the class to match + * + * @return null|object key/value object or `null` + */ + public static function matchNested(string $class) + { + if (array_key_exists($class, static::$_nested)) { + return (object) ['key' => $class, 'value' => static::$_nested[$class]]; + } + + $parent = $class; + // only consider the immediate OpenApi parent + while (0 !== strpos($parent, 'OpenApi\\Annotations\\') && $parent = get_parent_class($parent)) { + if ($kvp = static::matchNested($parent)) { + return $kvp; + } + } + + return null; + } + + /** + * Helper for generating the identity(). + */ + protected function _identity(array $properties): string + { + $fields = []; + foreach ($properties as $property) { + $value = $this->{$property}; + if ($value !== null && !Generator::isDefault($value)) { + $fields[] = $property . '=' . (is_string($value) ? '"' . $value . '"' : $value); + } + } + + return Util::shorten(get_class($this)) . '(' . implode(',', $fields) . ')'; + } + + /** + * Validates the matching of the property value to a annotation type. + * + * @param string $type The annotations property type + * @param mixed $value The property value + */ + private function validateType(string $type, $value): bool + { + if (substr($type, 0, 1) === '[' && substr($type, -1) === ']') { // Array of a specified type? + if ($this->validateType('array', $value) === false) { + return false; + } + $itemType = substr($type, 1, -1); + foreach ($value as $i => $item) { + if ($this->validateType($itemType, $item) === false) { + return false; + } + } + + return true; + } + + if (is_subclass_of($type, AbstractAnnotation::class)) { + $type = 'object'; + } + + return $this->validateDefaultTypes($type, $value); + } + + /** + * Validates default Open Api types. + * + * @param string $type The property type + * @param mixed $value The value to validate + */ + private function validateDefaultTypes(string $type, $value): bool + { + switch ($type) { + case 'string': + return is_string($value); + case 'boolean': + return is_bool($value); + case 'integer': + return is_int($value); + case 'number': + return is_numeric($value); + case 'object': + return is_object($value); + case 'array': + return $this->validateArrayType($value); + case 'scheme': + return in_array($value, ['http', 'https', 'ws', 'wss'], true); + default: + throw new \Exception('Invalid type "' . $type . '"'); + } + } + + /** + * Validate array type. + * + * @param mixed $value + */ + private function validateArrayType($value): bool + { + if (is_array($value) === false) { + return false; + } + $count = 0; + foreach ($value as $i => $item) { + // not a array, but a hash/map + if ($count !== $i) { + return false; + } + $count++; + } + + return true; + } + + /** + * Wrap the context with a reference to the annotation it is nested in. + * + * @param AbstractAnnotation $annotation + * + * @return AbstractAnnotation + */ + protected function nested(AbstractAnnotation $annotation, Context $nestedContext) + { + if (property_exists($annotation, '_context') && $annotation->_context === $this->_context) { + $annotation->_context = $nestedContext; + } + + return $annotation; + } + + protected function combine(...$args): array + { + $combined = []; + foreach ($args as $arg) { + if (is_array($arg)) { + $combined = array_merge($combined, $arg); + } else { + $combined[] = $arg; + } + } + + return array_filter($combined, function ($value) { + return !Generator::isDefault($value) && $value !== null; + }); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php new file mode 100644 index 0000000..0cdf211 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/AdditionalProperties.php @@ -0,0 +1,38 @@ + 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php new file mode 100644 index 0000000..7a7ab99 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Attachable.php @@ -0,0 +1,73 @@ +|null List of valid parent annotation classes. If `null`, the default nesting rules apply. + */ + public function allowedParents(): ?array + { + return null; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php new file mode 100644 index 0000000..e09a3d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Components.php @@ -0,0 +1,146 @@ + + */ + public $schemas = Generator::UNDEFINED; + + /** + * Reusable Responses. + * + * @var Response[] + */ + public $responses = Generator::UNDEFINED; + + /** + * Reusable Parameters. + * + * @var Parameter[] + */ + public $parameters = Generator::UNDEFINED; + + /** + * Reusable Examples. + * + * @var Examples[] + */ + public $examples = Generator::UNDEFINED; + + /** + * Reusable Request Bodies. + * + * @var RequestBody[] + */ + public $requestBodies = Generator::UNDEFINED; + + /** + * Reusable Headers. + * + * @var Header[] + */ + public $headers = Generator::UNDEFINED; + + /** + * Reusable Security Schemes. + * + * @var SecurityScheme[] + */ + public $securitySchemes = Generator::UNDEFINED; + + /** + * Reusable Links. + * + * @var Link[] + */ + public $links = Generator::UNDEFINED; + + /** + * Reusable Callbacks. + * + * @var array + */ + public $callbacks = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Response::class => ['responses', 'response'], + Parameter::class => ['parameters', 'parameter'], + PathParameter::class => ['parameters', 'parameter'], + RequestBody::class => ['requestBodies', 'request'], + Examples::class => ['examples', 'example'], + Header::class => ['headers', 'header'], + SecurityScheme::class => ['securitySchemes', 'securityScheme'], + Link::class => ['links', 'link'], + Schema::class => ['schemas', 'schema'], + Attachable::class => ['attachables'], + ]; + + /** + * Generate a `#/components/...` reference for the given annotation. + * + * A `string` component value always assumes type `Schema`. + * + * @param AbstractAnnotation|string $component + */ + public static function ref($component, bool $encode = true): string + { + if ($component instanceof AbstractAnnotation) { + foreach (Components::$_nested as $type => $nested) { + // exclude attachables + if (2 == count($nested)) { + if ($component instanceof $type) { + $type = $nested[0]; + $name = $component->{$nested[1]}; + break; + } + } + } + } else { + $type = 'schemas'; + $name = $component; + } + + return self::COMPONENTS_PREFIX . $type . '/' . ($encode ? Util::refEncode((string) $name) : $name); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php new file mode 100644 index 0000000..1075627 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Contact.php @@ -0,0 +1,63 @@ + 'string', + 'url' => 'string', + 'email' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Info::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php new file mode 100644 index 0000000..8c3a54f --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Delete.php @@ -0,0 +1,25 @@ + 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Schema::class, + Property::class, + AdditionalProperties::class, + Items::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php new file mode 100644 index 0000000..c38a24b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Examples.php @@ -0,0 +1,96 @@ + 'string', + 'description' => 'string', + 'externalValue' => 'string', + ]; + + public static $_required = ['summary']; + + public static $_parents = [ + Components::class, + Parameter::class, + PathParameter::class, + MediaType::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php new file mode 100644 index 0000000..81bff45 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ExternalDocumentation.php @@ -0,0 +1,76 @@ + 'string', + 'url' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_required = ['url']; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + Tag::class, + Schema::class, + AdditionalProperties::class, + Property::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Delete::class, + Patch::class, + Head::class, + Options::class, + Trace::class, + Items::class, + JsonContent::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php new file mode 100644 index 0000000..5409206 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Flow.php @@ -0,0 +1,106 @@ + ['implicit', 'password', 'authorizationCode', 'clientCredentials'], + 'refreshUrl' => 'string', + 'authorizationUrl' => 'string', + 'tokenUrl' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + SecurityScheme::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + if (is_array($this->scopes) && empty($this->scopes)) { + $this->scopes = new \stdClass(); + } + + return parent::jsonSerialize(); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php new file mode 100644 index 0000000..c0ff442 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Get.php @@ -0,0 +1,25 @@ + 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Response::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php new file mode 100644 index 0000000..db535b3 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Info.php @@ -0,0 +1,98 @@ + 'string', + 'version' => 'string', + 'description' => 'string', + 'termsOfService' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Contact::class => 'contact', + License::class => 'license', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php new file mode 100644 index 0000000..c46ec94 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Items.php @@ -0,0 +1,62 @@ + 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Property::class, + AdditionalProperties::class, + Schema::class, + JsonContent::class, + XmlContent::class, + Items::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + $parent = end($stack); + if ($parent instanceof Schema && $parent->type !== 'array') { + $this->_context->logger->warning('@OA\\Items() parent type must be "array" in ' . $this->_context); + $valid = false; + } + + // @todo Additional validation when used inside a Header or Parameter context. + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php new file mode 100644 index 0000000..348f8d6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/JsonContent.php @@ -0,0 +1,42 @@ +`'application/json'` will be generated. + * + * @Annotation + */ +class JsonContent extends Schema +{ + /** + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = []; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + AdditionalProperties::class => 'additionalProperties', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php new file mode 100644 index 0000000..ac04bc6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/License.php @@ -0,0 +1,102 @@ + 'string', + 'identifier' => 'string', + 'url' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_required = ['name']; + + /** + * @inheritdoc + */ + public static $_parents = [ + Info::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) { + unset($data->identifier); + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + $valid = parent::validate($stack, $skip, $ref, $context); + + if ($this->_context->isVersion(OpenApi::VERSION_3_1_0)) { + if (!Generator::isDefault($this->url) && $this->identifier !== Generator::UNDEFINED) { + $this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive'); + $valid = false; + } + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php new file mode 100644 index 0000000..b224a55 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Link.php @@ -0,0 +1,114 @@ +links array. + * + * @var string + */ + public $link = Generator::UNDEFINED; + + /** + * A relative or absolute reference to an OA operation. + * + * This field is mutually exclusive of the operationId field, and must point to an Operation object. + * + * Relative values may be used to locate an existing Operation object in the OpenAPI definition. + * + * @var string + */ + public $operationRef = Generator::UNDEFINED; + + /** + * The name of an existing, resolvable OA operation, as defined with a unique operationId. + * + * This field is mutually exclusive of the operationRef field. + * + * @var string + */ + public $operationId = Generator::UNDEFINED; + + /** + * A map representing parameters to pass to an operation as specified with operationId or identified via + * operationRef. + * + * The key is the parameter name to be used, whereas the value can be a constant or an expression to + * be evaluated and passed to the linked operation. + * The parameter name can be qualified using the parameter location [{in}.]{name} for operations + * that use the same parameter name in different locations (e.g. path.id). + * + * @var array + */ + public $parameters = Generator::UNDEFINED; + + /** + * A literal value or {expression} to use as a request body when calling the target operation. + * + * @var mixed + */ + public $requestBody = Generator::UNDEFINED; + + /** + * A description of the link. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * A server object to be used by the target operation. + * + * @var Server + */ + public $server = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_nested = [ + Server::class => 'server', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Response::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php new file mode 100644 index 0000000..624fe69 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/MediaType.php @@ -0,0 +1,88 @@ +content array. + * + * @var string + */ + public $mediaType = Generator::UNDEFINED; + + /** + * The schema defining the type used for the request body. + * + * @var Schema + */ + public $schema = Generator::UNDEFINED; + + /** + * Example of the media type. + * + * The example object should be in the correct format as specified by the media type. + * The example object is mutually exclusive of the examples object. + * + * Furthermore, if referencing a schema which contains an example, + * the example value shall override the example provided by the schema. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Examples of the media type. + * + * Each example object should match the media type and specified schema if present. + * The examples object is mutually exclusive of the example object. + * + * Furthermore, if referencing a schema which contains an example, + * the examples value shall override the example provided by the schema. + * + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * A map between a property name and its encoding information. + * + * The key, being the property name, must exist in the schema as a property. + * + * The encoding object shall only apply to requestBody objects when the media type is multipart or + * application/x-www-form-urlencoded. + * + * @var array + */ + public $encoding = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Response::class, + RequestBody::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php new file mode 100644 index 0000000..7477527 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/OpenApi.php @@ -0,0 +1,233 @@ +@Server objects, which provide connectivity information to a target server. + * + * If not provided, or is an empty array, the default value would be a Server Object with an url value of /. + * + * @var Server[] + */ + public $servers = Generator::UNDEFINED; + + /** + * The available paths and operations for the API. + * + * @var PathItem[] + */ + public $paths = Generator::UNDEFINED; + + /** + * An element to hold various components for the specification. + * + * @var Components + */ + public $components = Generator::UNDEFINED; + + /** + * A declaration of which security mechanisms can be used across the API. + * + * The list of values includes alternative security requirement objects that can be used. + * Only one of the security requirement objects need to be satisfied to authorize a request. + * Individual operations can override this definition. + * To make security optional, an empty security requirement `({})` can be included in the array. + * + * @var array + */ + public $security = Generator::UNDEFINED; + + /** + * A list of tags used by the specification with additional metadata. + * + * The order of the tags can be used to reflect on their order by the parsing tools. + * Not all tags that are used by the Operation Object must be declared. + * The tags that are not declared may be organized randomly or based on the tools' logic. + * Each tag name in the list must be unique. + * + * @var Tag[] + */ + public $tags = Generator::UNDEFINED; + + /** + * Additional external documentation. + * + * @var ExternalDocumentation + */ + public $externalDocs = Generator::UNDEFINED; + + /** + * @var Analysis + */ + public $_analysis = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['openapi', 'info', 'paths']; + + /** + * @inheritdoc + */ + public static $_nested = [ + Info::class => 'info', + Server::class => ['servers'], + PathItem::class => ['paths', 'path'], + Components::class => 'components', + Tag::class => ['tags'], + ExternalDocumentation::class => 'externalDocs', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_types = []; + + /** + * @inheritdoc + */ + public function validate(array $stack = null, array $skip = null, string $ref = '', $context = null): bool + { + if ($stack !== null || $skip !== null || $ref !== '') { + $this->_context->logger->warning('Nested validation for ' . $this->identity() . ' not allowed'); + + return false; + } + + if (!in_array($this->openapi, self::SUPPORTED_VERSIONS)) { + $this->_context->logger->warning('Unsupported OpenAPI version "' . $this->openapi . '". Allowed versions are: ' . implode(', ', self::SUPPORTED_VERSIONS)); + + return false; + } + + return parent::validate([], [], '#', new \stdClass()); + } + + /** + * Save the OpenAPI documentation to a file. + */ + public function saveAs(string $filename, string $format = 'auto'): void + { + if ($format === 'auto') { + $format = strtolower(substr($filename, -5)) === '.json' ? 'json' : 'yaml'; + } + + if (strtolower($format) === 'json') { + $content = $this->toJson(); + } else { + $content = $this->toYaml(); + } + + if (file_put_contents($filename, $content) === false) { + throw new \Exception('Failed to saveAs("' . $filename . '", "' . $format . '")'); + } + } + + /** + * Look up an annotation with a $ref url. + * + * @param string $ref The $ref value, for example: "#/components/schemas/Product" + */ + public function ref(string $ref) + { + if (substr($ref, 0, 2) !== '#/') { + // @todo Add support for external (http) refs? + throw new \Exception('Unsupported $ref "' . $ref . '", it should start with "#/"'); + } + + return $this->resolveRef($ref, '#/', $this, []); + } + + /** + * Recursive helper for ref(). + * + * @param array|AbstractAnnotation $container + */ + private static function resolveRef(string $ref, string $resolved, $container, array $mapping) + { + if ($ref === $resolved) { + return $container; + } + $path = substr($ref, strlen($resolved)); + $slash = strpos($path, '/'); + + $subpath = $slash === false ? $path : substr($path, 0, $slash); + $property = Util::refDecode($subpath); + $unresolved = $slash === false ? $resolved . $subpath : $resolved . $subpath . '/'; + + if (is_object($container)) { + if (property_exists($container, $property) === false) { + throw new \Exception('$ref "' . $ref . '" not found'); + } + if ($slash === false) { + return $container->{$property}; + } + $mapping = []; + if ($container instanceof AbstractAnnotation) { + foreach ($container::$_nested as $nestedClass => $nested) { + if (is_string($nested) === false && count($nested) === 2 && $nested[0] === $property) { + $mapping[$nestedClass] = $nested[1]; + } + } + } + + return self::resolveRef($ref, $unresolved, $container->{$property}, $mapping); + } elseif (is_array($container)) { + if (array_key_exists($property, $container)) { + return self::resolveRef($ref, $unresolved, $container[$property], []); + } + foreach ($mapping as $nestedClass => $keyField) { + foreach ($container as $key => $item) { + if (is_numeric($key) && is_object($item) && $item instanceof $nestedClass && (string) $item->{$keyField} === $property) { + return self::resolveRef($ref, $unresolved, $item, []); + } + } + } + } + + throw new \Exception('$ref "' . $unresolved . '" not found'); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php new file mode 100644 index 0000000..e4d1e9a --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Operation.php @@ -0,0 +1,248 @@ + 'string', + 'method' => 'string', + 'tags' => '[string]', + 'summary' => 'string', + 'description' => 'string', + 'deprecated' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Parameter::class => ['parameters'], + PathParameter::class => ['parameters'], + Response::class => ['responses', 'response'], + ExternalDocumentation::class => 'externalDocs', + Server::class => ['servers'], + RequestBody::class => 'requestBody', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + unset($data->method); + unset($data->path); + + // ensure security elements are object + if (isset($data->security) && is_array($data->security)) { + foreach ($data->security as $key => $scheme) { + $data->security[$key] = (object) $scheme; + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + if (!Generator::isDefault($this->responses)) { + foreach ($this->responses as $response) { + if (!Generator::isDefault($response->response) && $response->response !== 'default' && preg_match('/^([12345]{1}[0-9]{2})|([12345]{1}XX)$/', (string) $response->response) === 0) { + $this->_context->logger->warning('Invalid value "' . $response->response . '" for ' . $response->_identity([]) . '->response, expecting "default", a HTTP Status Code or HTTP Status Code range definition in ' . $response->_context); + $valid = false; + } + } + } + + if (is_object($context) && !Generator::isDefault($this->operationId)) { + if (!property_exists($context, 'operationIds')) { + $context->operationIds = []; + } + + if (in_array($this->operationId, $context->operationIds)) { + $this->_context->logger->warning('operationId must be unique. Duplicate value found: "' . $this->operationId . '"'); + $valid = false; + } + + $context->operationIds[] = $this->operationId; + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php new file mode 100644 index 0000000..312505e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Options.php @@ -0,0 +1,25 @@ +Components::parameters or PathItem::parameters array. + * + * @var string + */ + public $parameter = Generator::UNDEFINED; + + /** + * The (case-sensitive) name of the parameter. + * + * If in is "path", the name field must correspond to the associated path segment from the path field in the Paths Object. + * + * If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition shall be ignored. + * For all other cases, the name corresponds to the parameter name used by the in property. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * The location of the parameter. + * + * Possible values are "query", "header", "path" or "cookie". + * + * @var string + */ + public $in = Generator::UNDEFINED; + + /** + * A brief description of the parameter. + * + * This could contain examples of use. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * Determines whether this parameter is mandatory. + * + * If the parameter location is "path", this property is required and its value must be true. + * Otherwise, the property may be included and its default value is false. + * + * @var bool + */ + public $required = Generator::UNDEFINED; + + /** + * Specifies that a parameter is deprecated and should be transitioned out of usage. + * + * @var bool + */ + public $deprecated = Generator::UNDEFINED; + + /** + * Sets the ability to pass empty-valued parameters. + * + * This is valid only for query parameters and allows sending a parameter with an empty value. + * + * Default value is false. + * + * If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue shall be ignored. + * + * @var bool + */ + public $allowEmptyValue = Generator::UNDEFINED; + + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value. + * + * Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form. + * + * @var string + */ + public $style = Generator::UNDEFINED; + + /** + * When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map. + * + * For other types of parameters this property has no effect. + * + * When style is form, the default value is true. + * For all other styles, the default value is false. + * + * @var bool + */ + public $explode = Generator::UNDEFINED; + + /** + * Determines whether the parameter value should allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding. + * + * This property only applies to parameters with an in value of query. + * + * The default value is false. + * + * @var bool + */ + public $allowReserved = Generator::UNDEFINED; + + /** + * The schema defining the type used for the parameter. + * + * @var Schema + */ + public $schema = Generator::UNDEFINED; + + /** + * Example of the media type. + * + * The example should match the specified schema and encoding properties if present. + * The example object is mutually exclusive of the examples object. + * Furthermore, if referencing a schema which contains an example, the example value shall override the example provided by the schema. + * To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Examples of the media type. + * + * Each example should contain a value in the correct format as specified in the parameter encoding. + * The examples object is mutually exclusive of the example object. + * Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema. + * + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * A map containing the representations for the parameter. + * + * The key is the media type and the value describes it. + * The map must only contain one entry. + * + * @var array|JsonContent|XmlContent|Attachable + */ + public $content = Generator::UNDEFINED; + + /** + * Path-style parameters defined by RFC6570. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.7) + */ + public $matrix = Generator::UNDEFINED; + + /** + * Label style parameters defined by RFC6570. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.5) + */ + public $label = Generator::UNDEFINED; + + /** + * Form style parameters defined by RFC6570. + * + * This option replaces collectionFormat with a csv (when explode is false) or multi (when explode is true) value from OpenAPI 2.0. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.8) + */ + public $form = Generator::UNDEFINED; + + /** + * Simple style parameters defined by RFC6570. + * + * This option replaces collectionFormat with a csv value from OpenAPI 2.0. + * + * @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.2) + * + * @var array + */ + public $simple = Generator::UNDEFINED; + + /** + * Space separated array values. + * + * This option replaces collectionFormat equal to ssv from OpenAPI 2.0. + * + * @var array + */ + public $spaceDelimited = Generator::UNDEFINED; + + /** + * Pipe separated array values. + * + * This option replaces collectionFormat equal to pipes from OpenAPI 2.0. + * + * @var array + */ + public $pipeDelimited = Generator::UNDEFINED; + + /** + * Provides a simple way of rendering nested objects using form parameters. + */ + public $deepObject = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['name', 'in']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'name' => 'string', + 'in' => ['query', 'header', 'path', 'cookie'], + 'description' => 'string', + 'style' => ['matrix', 'label', 'form', 'simple', 'spaceDelimited', 'pipeDelimited', 'deepObject'], + 'required' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Schema::class => 'schema', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + PathItem::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Delete::class, + Patch::class, + Head::class, + Options::class, + Trace::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if (in_array($this, $skip, true)) { + return true; + } + + $valid = parent::validate($stack, $skip, $ref, $context); + + if (Generator::isDefault($this->ref)) { + if ($this->in === 'body') { + if (Generator::isDefault($this->schema)) { + $this->_context->logger->warning('Field "schema" is required when ' . $this->identity() . ' is in "' . $this->in . '" in ' . $this->_context); + $valid = false; + } + } + } + + return $valid; + } + + /** + * @inheritdoc + */ + public function identity(): string + { + return parent::_identity(['name', 'in']); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php new file mode 100644 index 0000000..7972f3b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Patch.php @@ -0,0 +1,25 @@ +paths array). + * + * @var string + */ + public $path = Generator::UNDEFINED; + + /** + * An optional, string summary, intended to apply to all operations in this path. + * + * @var string + */ + public $summary = Generator::UNDEFINED; + + /** + * A definition of a GET operation on this path. + * + * @var Get + */ + public $get = Generator::UNDEFINED; + + /** + * A definition of a PUT operation on this path. + * + * @var Put + */ + public $put = Generator::UNDEFINED; + + /** + * A definition of a POST operation on this path. + * + * @var Post + */ + public $post = Generator::UNDEFINED; + + /** + * A definition of a DELETE operation on this path. + * + * @var Delete + */ + public $delete = Generator::UNDEFINED; + + /** + * A definition of a OPTIONS operation on this path. + * + * @var Options + */ + public $options = Generator::UNDEFINED; + + /** + * A definition of a HEAD operation on this path. + * + * @var Head + */ + public $head = Generator::UNDEFINED; + + /** + * A definition of a PATCH operation on this path. + * + * @var Patch + */ + public $patch = Generator::UNDEFINED; + + /** + * A definition of a TRACE operation on this path. + * + * @var Trace + */ + public $trace = Generator::UNDEFINED; + + /** + * An alternative server array to service all operations in this path. + * + * @var Server[] + */ + public $servers = Generator::UNDEFINED; + + /** + * A list of parameters that are applicable for all the operations described under this path. + * + * These parameters can be overridden at the operation level, but cannot be removed there. + * The list must not include duplicated parameters. + * A unique parameter is defined by a combination of a name and location. + * The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters. + * + * @var Parameter[] + */ + public $parameters = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'path' => 'string', + 'summary' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Get::class => 'get', + Post::class => 'post', + Put::class => 'put', + Delete::class => 'delete', + Patch::class => 'patch', + Trace::class => 'trace', + Head::class => 'head', + Options::class => 'options', + Parameter::class => ['parameters'], + PathParameter::class => ['parameters'], + Server::class => ['servers'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php new file mode 100644 index 0000000..9e600b4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/PathParameter.php @@ -0,0 +1,25 @@ +properties array. + * + * @var string + */ + public $property = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + AdditionalProperties::class, + Schema::class, + JsonContent::class, + XmlContent::class, + Property::class, + Items::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php new file mode 100644 index 0000000..a0a0303 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Put.php @@ -0,0 +1,25 @@ +|JsonContent|XmlContent|Attachable + */ + public $content = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'description' => 'string', + 'required' => 'boolean', + 'request' => 'string', + ]; + + public static $_parents = [ + Components::class, + Delete::class, + Get::class, + Head::class, + Operation::class, + Options::class, + Patch::class, + Post::class, + Trace::class, + Put::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + MediaType::class => ['content', 'mediaType'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php new file mode 100644 index 0000000..73d91bc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Response.php @@ -0,0 +1,128 @@ +responses array. + * + * A HTTP status code or default. + * + * @var string|int + */ + public $response = Generator::UNDEFINED; + + /** + * A short description of the response. + * + * CommonMark syntax may be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * Maps a header name to its definition. + * + * RFC7230 states header names are case insensitive. + * + * If a response header is defined with the name "Content-Type", it shall be ignored. + * + * @see [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) + * + * @var Header[] + */ + public $headers = Generator::UNDEFINED; + + /** + * A map containing descriptions of potential response payloads. + * + * The key is a media type or media type range and the value describes it. + * + * For responses that match multiple keys, only the most specific key is applicable; + * e.g. text/plain overrides text/*. + * + * @var MediaType|JsonContent|XmlContent|Attachable|array + */ + public $content = Generator::UNDEFINED; + + /** + * A map of operations links that can be followed from the response. + * + * The key of the map is a short name for the link, following the naming constraints of the names for Component + * Objects. + * + * @var Link[] + */ + public $links = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + MediaType::class => ['content', 'mediaType'], + Header::class => ['headers', 'header'], + Link::class => ['links', 'link'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Operation::class, + Get::class, + Post::class, + Put::class, + Patch::class, + Delete::class, + Head::class, + Options::class, + Trace::class, + ]; + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + $valid = parent::validate($stack, $skip, $ref, $context); + + if (Generator::isDefault($this->description) && Generator::isDefault($this->ref)) { + $this->_context->logger->warning($this->identity() . ' One of description or ref is required'); + $valid = false; + } + + return $valid; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php new file mode 100644 index 0000000..fc3c908 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Schema.php @@ -0,0 +1,444 @@ +schemas array. + * + * @var string + */ + public $schema = Generator::UNDEFINED; + + /** + * Can be used to decorate a user interface with information about the data produced by this user interface. + * + * Preferably short; use description for more details. + * + * @var string + */ + public $title = Generator::UNDEFINED; + + /** + * A description will provide explanation about the purpose of the instance described by this schema. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the + * value of this property. + * + * @var int + */ + public $maxProperties = Generator::UNDEFINED; + + /** + * An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, + * the value of this property. + * + * @var int + */ + public $minProperties = Generator::UNDEFINED; + + /** + * An object instance is valid against this property if its property set contains all elements in this property's + * array value. + * + * @var string[] + */ + public $required = Generator::UNDEFINED; + + /** + * @var Property[] + */ + public $properties = Generator::UNDEFINED; + + /** + * The type of the schema/property. The value MUST be one of "string", "number", "integer", "boolean", "array" or + * "object". + * + * @var string + */ + public $type = Generator::UNDEFINED; + + /** + * The extending format for the previously mentioned type. See Data Type Formats for further details. + * + * @var string + */ + public $format = Generator::UNDEFINED; + + /** + * Required if type is "array". Describes the type of items in the array. + * + * @var Items + */ + public $items = Generator::UNDEFINED; + + /** + * Determines the format of the array if type array is used. + * Possible values are: + * - csv: comma separated values foo,bar. + * - ssv: space separated values foo bar. + * - tsv: tab separated values foo\tbar. + * - pipes: pipe separated values foo|bar. + * - multi: corresponds to multiple parameter instances instead of multiple values for a single instance foo=bar&foo=baz. + * This is valid only for parameters of type query or formData. + * Default value is csv. + * + * @var string + */ + public $collectionFormat = Generator::UNDEFINED; + + /** + * Sets a default value to the parameter. The type of the value depends on the defined type. + * + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor101) + * + * @var mixed + */ + public $default = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17) + * + * @var int|float + */ + public $maximum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17) + * + * @var bool + */ + public $exclusiveMaximum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21) + * + * @var int|float + */ + public $minimum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21) + * + * @var bool + */ + public $exclusiveMinimum = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor26) + * + * @var int + */ + public $maxLength = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor29) + * + * @var int + */ + public $minLength = Generator::UNDEFINED; + + /** + * A string instance is considered valid if the regular expression matches the instance successfully. + * + * @var string + */ + public $pattern = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor42) + * + * @var int + */ + public $maxItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor45) + * + * @var int + */ + public $minItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor49) + * + * @var bool + */ + public $uniqueItems = Generator::UNDEFINED; + + /** + * @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor76) + * + * @var string[]|int[]|float[]|\UnitEnum[]|class-string + */ + public $enum = Generator::UNDEFINED; + + /** + * A numeric instance is valid against "multipleOf" if the result of the division of the instance by this + * property's value is an integer. + * + * @var int|float + */ + public $multipleOf = Generator::UNDEFINED; + + /** + * Adds support for polymorphism. + * + * The discriminator is an object name that is used to differentiate between other schemas which may satisfy the + * payload description. See Composition and Inheritance for more details. + * + * @var Discriminator + */ + public $discriminator = Generator::UNDEFINED; + + /** + * Declares the property as "read only". + * + * Relevant only for Schema "properties" definitions. + * + * This means that it may be sent as part of a response but should not be sent as part of the request. + * If the property is marked as readOnly being true and is in the required list, the required will take effect on + * the response only. A property must not be marked as both readOnly and writeOnly being true. Default value is + * false. + * + * @var bool + */ + public $readOnly = Generator::UNDEFINED; + + /** + * Declares the property as "write only". + * + * Relevant only for Schema "properties" definitions. + * Therefore, it may be sent as part of a request but should not be sent as part of the response. + * If the property is marked as writeOnly being true and is in the required list, the required will take effect on + * the request only. A property must not be marked as both readOnly and writeOnly being true. Default value is + * false. + * + * @var bool + */ + public $writeOnly = Generator::UNDEFINED; + + /** + * This may be used only on properties schemas. + * + * It has no effect on root schemas. + * Adds additional metadata to describe the XML representation of this property. + * + * @var Xml + */ + public $xml = Generator::UNDEFINED; + + /** + * Additional external documentation for this schema. + * + * @var ExternalDocumentation + */ + public $externalDocs = Generator::UNDEFINED; + + /** + * A free-form property to include an example of an instance for this schema. + * + * To represent examples that cannot naturally be represented in JSON or YAML, a string value can be used to + * contain the example with escaping where necessary. + * + * @var mixed + */ + public $example = Generator::UNDEFINED; + + /** + * Allows sending a null value for the defined schema. + * Default value is false. + * + * @var bool + */ + public $nullable = Generator::UNDEFINED; + + /** + * Specifies that a schema is deprecated and should be transitioned out of usage. + * Default value is false. + * + * @var bool + */ + public $deprecated = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against all schemas + * defined by this property's value. + * + * @var array + */ + public $allOf = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against at least one + * schema defined by this property's value. + * + * @var array + */ + public $anyOf = Generator::UNDEFINED; + + /** + * An instance validates successfully against this property if it validates successfully against exactly one schema + * defined by this property's value. + * + * @var array + */ + public $oneOf = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.29. + */ + public $not = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#anchor64. + * + * @var bool|AdditionalProperties + */ + public $additionalProperties = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.10. + */ + public $additionalItems = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.14. + */ + public $contains = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.19. + */ + public $patternProperties = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.21. + */ + public $dependencies = Generator::UNDEFINED; + + /** + * http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.22. + */ + public $propertyNames = Generator::UNDEFINED; + + /** + * http://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.3. + * + * @var mixed + */ + public $const = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'title' => 'string', + 'description' => 'string', + 'required' => '[string]', + 'format' => 'string', + 'collectionFormat' => ['csv', 'ssv', 'tsv', 'pipes', 'multi'], + 'maximum' => 'number', + 'exclusiveMaximum' => 'boolean', + 'minimum' => 'number', + 'exclusiveMinimum' => 'boolean', + 'maxLength' => 'integer', + 'minLength' => 'integer', + 'pattern' => 'string', + 'maxItems' => 'integer', + 'minItems' => 'integer', + 'uniqueItems' => 'boolean', + 'multipleOf' => 'integer', + 'allOf' => '[' . Schema::class . ']', + 'oneOf' => '[' . Schema::class . ']', + 'anyOf' => '[' . Schema::class . ']', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + Parameter::class, + PathParameter::class, + MediaType::class, + Header::class, + ]; + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + + if (isset($data->const)) { + if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) { + $data->enum = [$data->const]; + unset($data->const); + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool + { + if ($this->type === 'array' && Generator::isDefault($this->items)) { + $this->_context->logger->warning('@OA\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context); + + return false; + } + + return parent::validate($stack, $skip, $ref, $context); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php new file mode 100644 index 0000000..6564e1f --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/SecurityScheme.php @@ -0,0 +1,136 @@ +security array. + * + * @var string + */ + public $securityScheme = Generator::UNDEFINED; + + /** + * The type of the security scheme. + * + * @var string + */ + public $type = Generator::UNDEFINED; + + /** + * A short description for security scheme. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * The name of the header or query parameter to be used. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * Required The location of the API key. + * + * @var string + */ + public $in = Generator::UNDEFINED; + + /** + * The flow used by the OAuth2 security scheme. + * + * @var Flow[] + */ + public $flows = Generator::UNDEFINED; + + /** + * A hint to the client to identify how the bearer token is formatted. + * + * Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. + * + * @var string + */ + public $bearerFormat = Generator::UNDEFINED; + + /** + * The name of the HTTP Authorization scheme. + * + * @see [RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1) + * + * @var string + */ + public $scheme = Generator::UNDEFINED; + + /** + * OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of a URL. + * + * @var string + */ + public $openIdConnectUrl = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_required = ['securityScheme', 'type']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'type' => ['http', 'apiKey', 'oauth2', 'openIdConnect'], + 'description' => 'string', + 'name' => 'string', + 'bearerFormat' => 'string', + 'in' => ['query', 'header', 'cookie'], + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Flow::class => ['flows', 'flow'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + Components::class, + ]; + + /** + * @inheritdoc + */ + public function merge(array $annotations, bool $ignore = false): array + { + $unmerged = parent::merge($annotations, $ignore); + + if ($this->type === 'oauth2') { + $this->name = Generator::UNDEFINED; + } + + return $unmerged; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php new file mode 100644 index 0000000..df0cb0d --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Server.php @@ -0,0 +1,87 @@ + ['variables', 'serverVariable'], + Attachable::class => ['attachables'], + ]; + + /** + * @inheritdoc + */ + public static $_required = ['url']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'url' => 'string', + 'description' => 'string', + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php new file mode 100644 index 0000000..0dade38 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php @@ -0,0 +1,87 @@ +variables array. + * + * @var string + */ + public $serverVariable = Generator::UNDEFINED; + + /** + * An enumeration of values to be used if the substitution options are from a limited set. + * + * @var string[]|int[]|float[]|\UnitEnum[]|class-string + */ + public $enum = Generator::UNDEFINED; + + /** + * The default value to use for substitution, and to send, if an alternate value is not supplied. + * + * Unlike the Schema Object's default, this value must be provided by the consumer. + * + * @var string + */ + public $default = Generator::UNDEFINED; + + /** + * A map between a variable name and its value. + * + * The value is used for substitution in the server's URL template. + * + * @var array + */ + public $variables = Generator::UNDEFINED; + + /** + * An optional description for the server variable. + * + * CommonMark syntax MAY be used for rich text representation. + * + * @var string + */ + public $description = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = [ + Server::class, + ]; + + /** + * @inheritdoc + */ + public static $_required = ['default']; + + /** + * @inheritdoc + */ + public static $_types = [ + 'default' => 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php new file mode 100644 index 0000000..670ac87 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Tag.php @@ -0,0 +1,66 @@ + 'string', + 'description' => 'string', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + OpenApi::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + ExternalDocumentation::class => 'externalDocs', + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php new file mode 100644 index 0000000..aac2820 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/Trace.php @@ -0,0 +1,25 @@ +true. + * + * If wrapped is false, it will be ignored. + * + * @var string + */ + public $name = Generator::UNDEFINED; + + /** + * The URL of the namespace definition. Value SHOULD be in the form of a URL. + * + * @var string + */ + public $namespace = Generator::UNDEFINED; + + /** + * The prefix to be used for the name. + * + * @var string + */ + public $prefix = Generator::UNDEFINED; + + /** + * Declares whether the property definition translates to an attribute instead of an element. + * + * Default value is false. + * + * @var bool + */ + public $attribute = Generator::UNDEFINED; + + /** + * MAY be used only for an array definition. + * + * Signifies whether the array is wrapped (for example <books><book/><book/></books>) + * or unwrapped (<book/><book/>). + * + * Default value is false. The definition takes effect only when defined alongside type being array (outside the items). + * + * @var bool + */ + public $wrapped = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_types = [ + 'name' => 'string', + 'namespace' => 'string', + 'prefix' => 'string', + 'attribute' => 'boolean', + 'wrapped' => 'boolean', + ]; + + /** + * @inheritdoc + */ + public static $_parents = [ + AdditionalProperties::class, + Schema::class, + Property::class, + Schema::class, + Items::class, + XmlContent::class, + ]; + + /** + * @inheritdoc + */ + public static $_nested = [ + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php new file mode 100644 index 0000000..59f7651 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Annotations/XmlContent.php @@ -0,0 +1,43 @@ +`'application/xml'` will be generated. + * + * @Annotation + */ +class XmlContent extends Schema +{ + /** + * @var array + */ + public $examples = Generator::UNDEFINED; + + /** + * @inheritdoc + */ + public static $_parents = []; + + /** + * @inheritdoc + */ + public static $_nested = [ + Discriminator::class => 'discriminator', + Items::class => 'items', + Property::class => ['properties', 'property'], + ExternalDocumentation::class => 'externalDocs', + Xml::class => 'xml', + AdditionalProperties::class => 'additionalProperties', + Examples::class => ['examples', 'example'], + Attachable::class => ['attachables'], + ]; +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php new file mode 100644 index 0000000..5a56dce --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php @@ -0,0 +1,101 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php new file mode 100644 index 0000000..0fc6fa7 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Attachable.php @@ -0,0 +1,16 @@ +|null $schemas + * @param Response[]|null $responses + * @param Parameter[]|null $parameters + * @param RequestBody[]|null $requestBodies + * @param Examples[]|null $examples + * @param Header[]|null $headers + * @param SecurityScheme[]|null $securitySchemes + * @param Link[]|null $links + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $schemas = null, + ?array $responses = null, + ?array $parameters = null, + ?array $requestBodies = null, + ?array $examples = null, + ?array $headers = null, + ?array $securitySchemes = null, + ?array $links = null, + ?array $callbacks = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'callbacks' => $callbacks ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($schemas, $responses, $parameters, $examples, $requestBodies, $headers, $securitySchemes, $links, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php new file mode 100644 index 0000000..332979e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Contact.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $url = null, + ?string $email = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'email' => $email ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php new file mode 100644 index 0000000..d2f1c73 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Delete.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $propertyName = null, + ?array $mapping = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'propertyName' => $propertyName ?? Generator::UNDEFINED, + 'mapping' => $mapping ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php new file mode 100644 index 0000000..9e1a9a0 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Examples.php @@ -0,0 +1,42 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $example = null, + ?string $summary = null, + ?string $description = null, + int|string|array|null $value = null, + ?string $externalValue = null, + string|object|null $ref = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'example' => $example ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'value' => $value ?? Generator::UNDEFINED, + 'externalValue' => $externalValue ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + ]); + if ($attachables) { + $this->merge($attachables); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php new file mode 100644 index 0000000..f9bfa94 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ExternalDocumentation.php @@ -0,0 +1,32 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $description = null, + ?string $url = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'description' => $description ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php new file mode 100644 index 0000000..80e6ebc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Flow.php @@ -0,0 +1,38 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $authorizationUrl = null, + ?string $tokenUrl = null, + ?string $refreshUrl = null, + ?string $flow = null, + ?array $scopes = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'authorizationUrl' => $authorizationUrl ?? Generator::UNDEFINED, + 'tokenUrl' => $tokenUrl ?? Generator::UNDEFINED, + 'refreshUrl' => $refreshUrl ?? Generator::UNDEFINED, + 'flow' => $flow ?? Generator::UNDEFINED, + 'scopes' => $scopes ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php new file mode 100644 index 0000000..81e5ad8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Get.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $header = null, + ?string $description = null, + ?bool $required = null, + ?Schema $schema = null, + ?bool $deprecated = null, + ?bool $allowEmptyValue = null, + // annotation4 + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'header' => $header ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables, $schema), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php new file mode 100644 index 0000000..39348f5 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Info.php @@ -0,0 +1,38 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $version = null, + ?string $description = null, + ?string $title = null, + ?string $termsOfService = null, + ?Contact $contact = null, + ?License $license = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'version' => $version ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'termsOfService' => $termsOfService ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($contact, $license, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php new file mode 100644 index 0000000..1c0dc28 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Items.php @@ -0,0 +1,105 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php new file mode 100644 index 0000000..d9606ef --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/JsonContent.php @@ -0,0 +1,108 @@ + $examples + * @param string[] $required + * @param Property[] $properties + * @param int|float $maximum + * @param int|float $minimum + * @param string[]|int[]|float[]|\UnitEnum[]|class-string $enum + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $examples = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'examples' => $examples ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php new file mode 100644 index 0000000..b929237 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/License.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $identifier = null, + ?string $url = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'identifier' => $identifier ?? Generator::UNDEFINED, + 'url' => $url ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php new file mode 100644 index 0000000..4f90659 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Link.php @@ -0,0 +1,44 @@ + $parameters + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $link = null, + ?string $operationRef = null, + string|object|null $ref = null, + ?string $operationId = null, + ?array $parameters = null, + mixed $requestBody = null, + ?string $description = null, + ?Server $server = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'link' => $link ?? Generator::UNDEFINED, + 'operationRef' => $operationRef ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'operationId' => $operationId ?? Generator::UNDEFINED, + 'parameters' => $parameters ?? Generator::UNDEFINED, + 'requestBody' => $requestBody ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($server, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php new file mode 100644 index 0000000..4f307d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/MediaType.php @@ -0,0 +1,38 @@ + $examples + * @param array $encoding + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $mediaType = null, + ?Schema $schema = null, + mixed $example = Generator::UNDEFINED, + ?array $examples = null, + ?array $encoding = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'mediaType' => $mediaType ?? Generator::UNDEFINED, + 'example' => $example, + 'encoding' => $encoding ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($schema, $examples, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php new file mode 100644 index 0000000..ce625e2 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OpenApi.php @@ -0,0 +1,41 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string $openapi = self::DEFAULT_VERSION, + ?Info $info = null, + ?array $servers = null, + ?array $security = null, + ?array $tags = null, + ?ExternalDocumentation $externalDocs = null, + ?array $paths = null, + ?Components $components = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'openapi' => $openapi, + 'security' => $security ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php new file mode 100644 index 0000000..40d26fe --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/OperationTrait.php @@ -0,0 +1,54 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $path = null, + ?string $operationId = null, + ?string $description = null, + ?string $summary = null, + ?array $security = null, + ?array $servers = null, + ?RequestBody $requestBody = null, + ?array $tags = null, + ?array $parameters = null, + ?array $responses = null, + ?array $callbacks = null, + ?ExternalDocumentation $externalDocs = null, + ?bool $deprecated = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'path' => $path ?? Generator::UNDEFINED, + 'operationId' => $operationId ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'security' => $security ?? Generator::UNDEFINED, + 'servers' => $servers ?? Generator::UNDEFINED, + 'tags' => $tags ?? Generator::UNDEFINED, + 'callbacks' => $callbacks ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($requestBody, $responses, $parameters, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php new file mode 100644 index 0000000..175b962 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Options.php @@ -0,0 +1,13 @@ + $examples + * @param array|JsonContent|XmlContent|Attachable|null $content + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $parameter = null, + ?string $name = null, + ?string $description = null, + ?string $in = null, + ?bool $required = null, + ?bool $deprecated = null, + ?bool $allowEmptyValue = null, + string|object|null $ref = null, + ?Schema $schema = null, + mixed $example = Generator::UNDEFINED, + ?array $examples = null, + array|JsonContent|XmlContent|Attachable|null $content = null, + ?string $style = null, + ?bool $explode = null, + ?bool $allowReserved = null, + ?array $spaceDelimited = null, + ?array $pipeDelimited = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'parameter' => $parameter ?? Generator::UNDEFINED, + 'name' => $name ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'in' => Generator::isDefault($this->in) ? $in : $this->in, + 'required' => $required ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, + 'ref' => $ref ?? Generator::UNDEFINED, + 'example' => $example, + 'style' => $style ?? Generator::UNDEFINED, + 'explode' => $explode ?? Generator::UNDEFINED, + 'allowReserved' => $allowReserved ?? Generator::UNDEFINED, + 'spaceDelimited' => $spaceDelimited ?? Generator::UNDEFINED, + 'pipeDelimited' => $pipeDelimited ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($schema, $examples, $content, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php new file mode 100644 index 0000000..1a0a3c8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Patch.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $path = null, + ?string $summary = null, + ?array $servers = null, + ?array $parameters = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'path' => $path ?? Generator::UNDEFINED, + 'summary' => $summary ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($servers, $parameters, $attachables), + ]); + } +} + +// Missing parameters: get, put, post, delete, options, head, patch, trace, parameters diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php new file mode 100644 index 0000000..70ccfd6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/PathParameter.php @@ -0,0 +1,13 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $property = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'property' => $property ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php new file mode 100644 index 0000000..2133102 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Put.php @@ -0,0 +1,13 @@ +|JsonContent|XmlContent|Attachable|null $content + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $request = null, + ?string $description = null, + ?bool $required = null, + array|JsonContent|XmlContent|Attachable|null $content = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'request' => $request ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($content, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php new file mode 100644 index 0000000..996a697 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Response.php @@ -0,0 +1,41 @@ + $content + * @param Link[] $links + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + int|string $response = null, + ?string $description = null, + ?array $headers = null, + MediaType|JsonContent|XmlContent|Attachable|array|null $content = null, + ?array $links = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'response' => $response ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($headers, $content, $links, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php new file mode 100644 index 0000000..6426f00 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Schema.php @@ -0,0 +1,106 @@ + $allOf + * @param array $anyOf + * @param array $oneOf + * @param mixed $const + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + $const = Generator::UNDEFINED, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + 'const' => $const, + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php new file mode 100644 index 0000000..f1d8bec --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/SecurityScheme.php @@ -0,0 +1,48 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + string|object|null $ref = null, + ?string $securityScheme = null, + ?string $type = null, + ?string $description = null, + ?string $name = null, + ?string $in = null, + ?string $bearerFormat = null, + ?string $scheme = null, + ?string $openIdConnectUrl = null, + ?array $flows = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'ref' => $ref ?? Generator::UNDEFINED, + 'securityScheme' => $securityScheme ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'name' => $name ?? Generator::UNDEFINED, + 'in' => $in ?? Generator::UNDEFINED, + 'bearerFormat' => $bearerFormat ?? Generator::UNDEFINED, + 'scheme' => $scheme ?? Generator::UNDEFINED, + 'openIdConnectUrl' => $openIdConnectUrl ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($flows, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php new file mode 100644 index 0000000..337d595 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Server.php @@ -0,0 +1,34 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $url = null, + ?string $description = null, + ?array $variables = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'url' => $url ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($variables, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php new file mode 100644 index 0000000..3b096d4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php @@ -0,0 +1,39 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $serverVariable = null, + ?string $description = null, + ?string $default = null, + array|string|null $enum = null, + ?array $variables = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'serverVariable' => $serverVariable ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'default' => $default ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'variables' => $variables ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php new file mode 100644 index 0000000..8ef0ef1 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Tag.php @@ -0,0 +1,33 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $description = null, + ?ExternalDocumentation $externalDocs = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php new file mode 100644 index 0000000..aff7fe7 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/Trace.php @@ -0,0 +1,13 @@ +|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?string $name = null, + ?string $namespace = null, + ?string $prefix = null, + ?bool $attribute = null, + ?bool $wrapped = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'name' => $name ?? Generator::UNDEFINED, + 'namespace' => $namespace ?? Generator::UNDEFINED, + 'prefix' => $prefix ?? Generator::UNDEFINED, + 'attribute' => $attribute ?? Generator::UNDEFINED, + 'wrapped' => $wrapped ?? Generator::UNDEFINED, + 'x' => $x ?? Generator::UNDEFINED, + 'value' => $this->combine($attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php new file mode 100644 index 0000000..47a3083 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Attributes/XmlContent.php @@ -0,0 +1,108 @@ + $examples + * @param string[] $required + * @param int|float $maximum + * @param int|float $minimum + * @param Property[] $properties + * @param string[]|int[]|float[]|\UnitEnum[]|class-string $enum + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param array|null $x + * @param Attachable[]|null $attachables + */ + public function __construct( + ?array $examples = null, + // schema + string|object|null $ref = null, + ?string $schema = null, + ?string $title = null, + ?string $description = null, + ?array $required = null, + ?array $properties = null, + ?string $type = null, + ?string $format = null, + ?Items $items = null, + ?string $collectionFormat = null, + mixed $default = Generator::UNDEFINED, + $maximum = null, + ?bool $exclusiveMaximum = null, + $minimum = null, + ?bool $exclusiveMinimum = null, + ?int $maxLength = null, + ?int $minLength = null, + ?int $maxItems = null, + ?int $minItems = null, + ?bool $uniqueItems = null, + ?string $pattern = null, + array|string|null $enum = null, + ?Discriminator $discriminator = null, + ?bool $readOnly = null, + ?bool $writeOnly = null, + ?Xml $xml = null, + ?ExternalDocumentation $externalDocs = null, + mixed $example = Generator::UNDEFINED, + ?bool $nullable = null, + ?bool $deprecated = null, + ?array $allOf = null, + ?array $anyOf = null, + ?array $oneOf = null, + AdditionalProperties|bool|null $additionalProperties = null, + // annotation + ?array $x = null, + ?array $attachables = null + ) { + parent::__construct([ + 'examples' => $examples ?? Generator::UNDEFINED, + // schema + 'ref' => $ref ?? Generator::UNDEFINED, + 'schema' => $schema ?? Generator::UNDEFINED, + 'title' => $title ?? Generator::UNDEFINED, + 'description' => $description ?? Generator::UNDEFINED, + 'required' => $required ?? Generator::UNDEFINED, + 'properties' => $properties ?? Generator::UNDEFINED, + 'type' => $type ?? Generator::UNDEFINED, + 'format' => $format ?? Generator::UNDEFINED, + 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, + 'default' => $default, + 'maximum' => $maximum ?? Generator::UNDEFINED, + 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, + 'minimum' => $minimum ?? Generator::UNDEFINED, + 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, + 'maxLength' => $maxLength ?? Generator::UNDEFINED, + 'minLength' => $minLength ?? Generator::UNDEFINED, + 'maxItems' => $maxItems ?? Generator::UNDEFINED, + 'minItems' => $minItems ?? Generator::UNDEFINED, + 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, + 'pattern' => $pattern ?? Generator::UNDEFINED, + 'enum' => $enum ?? Generator::UNDEFINED, + 'readOnly' => $readOnly ?? Generator::UNDEFINED, + 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, + 'xml' => $xml ?? Generator::UNDEFINED, + 'example' => $example, + 'nullable' => $nullable ?? Generator::UNDEFINED, + 'deprecated' => $deprecated ?? Generator::UNDEFINED, + 'allOf' => $allOf ?? Generator::UNDEFINED, + 'anyOf' => $anyOf ?? Generator::UNDEFINED, + 'oneOf' => $oneOf ?? Generator::UNDEFINED, + 'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED, + // annotation + 'x' => $x ?? Generator::UNDEFINED, + 'attachables' => $attachables ?? Generator::UNDEFINED, + 'value' => $this->combine($items, $discriminator, $externalDocs, $attachables), + ]); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Context.php b/Sources/API/vendor/zircote/swagger-php/src/Context.php new file mode 100644 index 0000000..58bdb49 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Context.php @@ -0,0 +1,274 @@ +parent); + } + + public function __construct(array $properties = [], ?Context $parent = null) + { + foreach ($properties as $property => $value) { + $this->{$property} = $value; + } + $this->parent = $parent; + + $this->logger = $this->logger ?: new DefaultLogger(); + } + + /** + * Check if a property is set directly on this context and not its parent context. + * + * Example: $c->is('method') or $c->is('class') + */ + public function is(string $property): bool + { + return property_exists($this, $property); + } + + /** + * Check if a property is NOT set directly on this context and its parent context. + * + * Example: $c->not('method') or $c->not('class') + */ + public function not(string $property): bool + { + return $this->is($property) === false; + } + + /** + * Return the context containing the specified property. + */ + public function with(string $property): ?Context + { + if ($this->is($property)) { + return $this; + } + if ($this->parent !== null) { + return $this->parent->with($property); + } + + return null; + } + + /** + * Get the root context. + */ + public function root(): Context + { + if ($this->parent !== null) { + return $this->parent->root(); + } + + return $this; + } + + /** + * Check if one of the given version numbers matches the current OpenAPI version. + * + * @param string|array $versions One or more version numbers + */ + public function isVersion($versions): bool + { + if (!$this->version) { + throw new \RuntimeException('Version is only available reliably for validation and serialization'); + } + + $versions = (array) $versions; + $currentVersion = $this->version ?: OA\OpenApi::DEFAULT_VERSION; + + return in_array($currentVersion, $versions); + } + + /** + * Export location for debugging. + * + * @return string Example: "file1.php on line 12" + */ + public function getDebugLocation(): string + { + $location = ''; + if ($this->class && ($this->method || $this->property)) { + $location .= $this->fullyQualifiedName($this->class); + if ($this->method) { + $location .= ($this->static ? '::' : '->') . $this->method . '()'; + } elseif ($this->property) { + $location .= ($this->static ? '::$' : '->') . $this->property; + } + } + if ($this->filename) { + if ($location !== '') { + $location .= ' in '; + } + $location .= $this->filename; + } + if ($this->line) { + if ($location !== '') { + $location .= ' on'; + } + $location .= ' line ' . $this->line; + if ($this->character) { + $location .= ':' . $this->character; + } + } + + return $location; + } + + /** + * Traverse the context tree to get the property value. + */ + public function __get(string $property) + { + if ($this->parent !== null) { + return $this->parent->{$property}; + } + + return null; + } + + public function __toString() + { + return $this->getDebugLocation(); + } + + public function __debugInfo() + { + return ['-' => $this->getDebugLocation()]; + } + + /** + * Create a Context based on the debug_backtrace. + * + * @deprecated + */ + public static function detect(int $index = 0): Context + { + // trigger_deprecation('zircote/swagger-php', '4.0', 'Context detecting is deprecated'); + + $context = new Context(); + $backtrace = debug_backtrace(); + $position = $backtrace[$index]; + if (isset($position['file'])) { + $context->filename = $position['file']; + } + if (isset($position['line'])) { + $context->line = $position['line']; + } + $caller = $backtrace[$index + 1] ?? null; + if (isset($caller['function'])) { + $context->method = $caller['function']; + if (isset($caller['type']) && $caller['type'] === '::') { + $context->static = true; + } + } + if (isset($caller['class'])) { + $fqn = explode('\\', $caller['class']); + $context->class = array_pop($fqn); + if (count($fqn)) { + $context->namespace = implode('\\', $fqn); + } + } + + // @todo extract namespaces and use statements + return $context; + } + + /** + * Resolve the fully qualified name. + */ + public function fullyQualifiedName(?string $source): string + { + if ($source === null) { + return ''; + } + + if ($this->namespace) { + $namespace = str_replace('\\\\', '\\', '\\' . $this->namespace . '\\'); + } else { + // global namespace + $namespace = '\\'; + } + + $thisSource = $this->class ?? $this->interface ?? $this->trait; + if ($thisSource && strcasecmp($source, $thisSource) === 0) { + return $namespace . $thisSource; + } + $pos = strpos($source, '\\'); + if ($pos !== false) { + if ($pos === 0) { + // Fully qualified name (\Foo\Bar) + return $source; + } + // Qualified name (Foo\Bar) + if ($this->uses) { + foreach ($this->uses as $alias => $aliasedNamespace) { + $alias .= '\\'; + if (strcasecmp(substr($source, 0, strlen($alias)), $alias) === 0) { + // Aliased namespace (use \Long\Namespace as Foo) + return '\\' . $aliasedNamespace . substr($source, strlen($alias) - 1); + } + } + } + } elseif ($this->uses) { + // Unqualified name (Foo) + foreach ($this->uses as $alias => $aliasedNamespace) { + if (strcasecmp($alias, $source) === 0) { + return '\\' . $aliasedNamespace; + } + } + } + + return $namespace . $source; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Generator.php b/Sources/API/vendor/zircote/swagger-php/src/Generator.php new file mode 100644 index 0000000..d37116e --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Generator.php @@ -0,0 +1,475 @@ + */ + public const DEFAULT_ALIASES = ['oa' => 'OpenApi\\Annotations']; + /** @var array */ + public const DEFAULT_NAMESPACES = ['OpenApi\\Annotations\\']; + + /** @var array Map of namespace aliases to be supported by doctrine. */ + protected $aliases; + + /** @var array|null List of annotation namespaces to be autoloaded by doctrine. */ + protected $namespaces; + + /** @var AnalyserInterface|null The configured analyzer. */ + protected $analyser; + + /** @var array */ + protected $config = []; + + /** @var callable[]|null List of configured processors. */ + protected $processors = null; + + /** @var LoggerInterface|null PSR logger. */ + protected $logger = null; + + /** + * OpenApi version override. + * + * If set, it will override the version set in the `OpenApi` annotation. + * + * Due to the order of processing any conditional code using this (via `Context::$version`) + * must come only after the analysis is finished. + * + * @var string|null + */ + protected $version = null; + + private $configStack; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->setAliases(self::DEFAULT_ALIASES); + $this->setNamespaces(self::DEFAULT_NAMESPACES); + + // kinda config stack to stay BC... + $this->configStack = new class() { + protected $generator; + + public function push(Generator $generator): void + { + $this->generator = $generator; + if (class_exists(AnnotationRegistry::class, true)) { + // keeping track of &this->generator allows to 'disable' the loader after we are done; + // no unload, unfortunately :/ + $gref = &$this->generator; + AnnotationRegistry::registerLoader( + function (string $class) use (&$gref): bool { + if ($gref) { + foreach ($gref->getNamespaces() as $namespace) { + if (strtolower(substr($class, 0, strlen($namespace))) === strtolower($namespace)) { + $loaded = class_exists($class); + if (!$loaded && $namespace === 'OpenApi\\Annotations\\') { + if (in_array(strtolower(substr($class, 20)), ['definition', 'path'])) { + // Detected an 2.x annotation? + throw new \Exception('The annotation @SWG\\' . substr($class, 20) . '() is deprecated. Found in ' . Generator::$context . "\nFor more information read the migration guide: https://github.com/zircote/swagger-php/blob/master/docs/Migrating-to-v3.md"); + } + } + + return $loaded; + } + } + } + + return false; + } + ); + } + } + + public function pop(): void + { + $this->generator = null; + } + }; + } + + /** + * @param mixed $value + */ + public static function isDefault($value): bool + { + return $value === Generator::UNDEFINED; + } + + /** + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + public function addAlias(string $alias, string $namespace): Generator + { + $this->aliases[$alias] = $namespace; + + return $this; + } + + public function setAliases(array $aliases): Generator + { + $this->aliases = $aliases; + + return $this; + } + + /** + * @return array|null + */ + public function getNamespaces(): ?array + { + return $this->namespaces; + } + + public function addNamespace(string $namespace): Generator + { + $namespaces = (array) $this->getNamespaces(); + $namespaces[] = $namespace; + + return $this->setNamespaces(array_unique($namespaces)); + } + + public function setNamespaces(?array $namespaces): Generator + { + $this->namespaces = $namespaces; + + return $this; + } + + public function getAnalyser(): AnalyserInterface + { + $this->analyser = $this->analyser ?: new ReflectionAnalyser([new DocBlockAnnotationFactory(), new AttributeAnnotationFactory()]); + $this->analyser->setGenerator($this); + + return $this->analyser; + } + + public function setAnalyser(?AnalyserInterface $analyser): Generator + { + $this->analyser = $analyser; + + return $this; + } + + public function getDefaultConfig(): array + { + return [ + 'operationId' => [ + 'hash' => true, + ], + ]; + } + + public function getConfig(): array + { + return $this->config + $this->getDefaultConfig(); + } + + protected function normaliseConfig(array $config): array + { + $normalised = []; + foreach ($config as $key => $value) { + if (is_numeric($key)) { + $token = explode('=', $value); + if (2 == count($token)) { + // 'operationId.hash=false' + [$key, $value] = $token; + } + } + + if (in_array($value, ['true', 'false'])) { + $value = 'true' == $value; + } + + $token = explode('.', $key); + if (2 == count($token)) { + // 'operationId.hash' => false + $normalised[$token[0]][$token[1]] = $value; + } else { + $normalised[$key] = $value; + } + } + + return $normalised; + } + + /** + * Set generator and/or processor config. + * + * @param array $config + */ + public function setConfig(array $config): Generator + { + $this->config = $this->normaliseConfig($config) + $this->config; + + return $this; + } + + /** + * @return callable[] + */ + public function getProcessors(): array + { + if (null === $this->processors) { + $this->processors = [ + new Processors\DocBlockDescriptions(), + new Processors\MergeIntoOpenApi(), + new Processors\MergeIntoComponents(), + new Processors\ExpandClasses(), + new Processors\ExpandInterfaces(), + new Processors\ExpandTraits(), + new Processors\ExpandEnums(), + new Processors\AugmentSchemas(), + new Processors\AugmentProperties(), + new Processors\BuildPaths(), + new Processors\AugmentParameters(), + new Processors\AugmentRefs(), + new Processors\MergeJsonContent(), + new Processors\MergeXmlContent(), + new Processors\OperationId(), + new Processors\CleanUnmerged(), + ]; + } + + $config = $this->getConfig(); + foreach ($this->processors as $processor) { + $rc = new \ReflectionClass($processor); + + // apply config + $processorKey = lcfirst($rc->getShortName()); + if (array_key_exists($processorKey, $config)) { + foreach ($config[$processorKey] as $name => $value) { + $setter = 'set' . ucfirst($name); + if (method_exists($processor, $setter)) { + $processor->{$setter}($value); + } + } + } + } + + return $this->processors; + } + + /** + * @param null|callable[] $processors + */ + public function setProcessors(?array $processors): Generator + { + $this->processors = $processors; + + return $this; + } + + public function addProcessor(callable $processor): Generator + { + $processors = $this->getProcessors(); + $processors[] = $processor; + $this->setProcessors($processors); + + return $this; + } + + public function removeProcessor(callable $processor, bool $silent = false): Generator + { + $processors = $this->getProcessors(); + if (false === ($key = array_search($processor, $processors, true))) { + if ($silent) { + return $this; + } + throw new \InvalidArgumentException('Processor not found'); + } + unset($processors[$key]); + $this->setProcessors($processors); + + return $this; + } + + /** + * Update/replace an existing processor with a new one. + * + * @param callable $processor The new processor + * @param null|callable $matcher Optional matcher callable to identify the processor to replace. + * If none given, matching is based on the processors class. + */ + public function updateProcessor(callable $processor, ?callable $matcher = null): Generator + { + $matcher = $matcher ?: function ($other) use ($processor): bool { + $otherClass = get_class($other); + + return $processor instanceof $otherClass; + }; + + $processors = array_map(function ($other) use ($processor, $matcher) { + return $matcher($other) ? $processor : $other; + }, $this->getProcessors()); + $this->setProcessors($processors); + + return $this; + } + + public function getLogger(): ?LoggerInterface + { + return $this->logger ?: new DefaultLogger(); + } + + public function getVersion(): ?string + { + return $this->version; + } + + public function setVersion(?string $version): Generator + { + $this->version = $version; + + return $this; + } + + public static function scan(iterable $sources, array $options = []): ?OA\OpenApi + { + // merge with defaults + $config = $options + [ + 'aliases' => self::DEFAULT_ALIASES, + 'namespaces' => self::DEFAULT_NAMESPACES, + 'analyser' => null, + 'analysis' => null, + 'processors' => null, + 'logger' => null, + 'validate' => true, + 'version' => null, + ]; + + return (new Generator($config['logger'])) + ->setVersion($config['version']) + ->setAliases($config['aliases']) + ->setNamespaces($config['namespaces']) + ->setAnalyser($config['analyser']) + ->setProcessors($config['processors']) + ->generate($sources, $config['analysis'], $config['validate']); + } + + /** + * Run code in the context of this generator. + * + * @param callable $callable Callable in the form of + * `function(Generator $generator, Analysis $analysis, Context $context): mixed` + * + * @return mixed the result of the `callable` + */ + public function withContext(callable $callable) + { + $rootContext = new Context([ + 'version' => $this->getVersion(), + 'logger' => $this->getLogger(), + ]); + $analysis = new Analysis([], $rootContext); + + $this->configStack->push($this); + try { + return $callable($this, $analysis, $rootContext); + } finally { + $this->configStack->pop(); + } + } + + /** + * Generate OpenAPI spec by scanning the given source files. + * + * @param iterable $sources PHP source files to scan. + * Supported sources: + * * string - file / directory name + * * \SplFileInfo + * * \Symfony\Component\Finder\Finder + * @param null|Analysis $analysis custom analysis instance + * @param bool $validate flag to enable/disable validation of the returned spec + */ + public function generate(iterable $sources, ?Analysis $analysis = null, bool $validate = true): ?OA\OpenApi + { + $rootContext = new Context([ + 'version' => $this->getVersion(), + 'logger' => $this->getLogger(), + ]); + $analysis = $analysis ?: new Analysis([], $rootContext); + + $this->configStack->push($this); + try { + $this->scanSources($sources, $analysis, $rootContext); + + // post-processing + $analysis->process($this->getProcessors()); + + if ($analysis->openapi) { + $analysis->openapi->openapi = $this->version ?: $analysis->openapi->openapi; + $rootContext->version = $analysis->openapi->openapi; + } + + // validation + if ($validate) { + $analysis->validate(); + } + } finally { + $this->configStack->pop(); + } + + return $analysis->openapi; + } + + protected function scanSources(iterable $sources, Analysis $analysis, Context $rootContext): void + { + $analyser = $this->getAnalyser(); + + foreach ($sources as $source) { + if (is_iterable($source)) { + $this->scanSources($source, $analysis, $rootContext); + } else { + $resolvedSource = $source instanceof \SplFileInfo ? $source->getPathname() : realpath($source); + if (!$resolvedSource) { + $rootContext->logger->warning(sprintf('Skipping invalid source: %s', $source)); + continue; + } + if (is_dir($resolvedSource)) { + $this->scanSources(Util::finder($resolvedSource), $analysis, $rootContext); + } else { + $analysis->addAnalysis($analyser->fromFile($resolvedSource, $rootContext)); + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php b/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php new file mode 100644 index 0000000..064c251 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Loggers/ConsoleLogger.php @@ -0,0 +1,97 @@ +debug = $debug; + } + + public function loggedMessageAboveNotice(): bool + { + return $this->loggedMessageAboveNotice; + } + + /** + * @param string $level + * @param string|\Exception $message + * @param array $context additional details; supports custom `prefix` and `exception` + */ + public function log($level, $message, array $context = []): void + { + $prefix = ''; + $color = ''; + // level adjustments + switch ($level) { + case LogLevel::DEBUG: + if (!$this->debug) { + return; + } + // no break + case LogLevel::WARNING: + $prefix = $context['prefix'] ?? 'Warning: '; + $color = static::COLOR_WARNING; + break; + case LogLevel::ERROR: + $prefix = $context['prefix'] ?? 'Error: '; + $color = static::COLOR_ERROR; + break; + } + $stop = !empty($color) ? static::COLOR_STOP : ''; + + if (!in_array($level, self::LOG_LEVELS_UP_TO_NOTICE, true)) { + $this->loggedMessageAboveNotice = true; + } + + /** @var ?\Exception $exception */ + $exception = $context['exception'] ?? null; + if ($message instanceof \Exception) { + $exception = $message; + $message = $exception->getMessage(); + } + + $logLine = sprintf('%s%s%s%s', $color, $prefix, $message, $stop); + error_log($logLine); + + if ($this->debug) { + if ($exception) { + error_log($exception->getTraceAsString()); + } elseif (!empty($logLine)) { + $stack = explode(PHP_EOL, (new \Exception())->getTraceAsString()); + // self + array_shift($stack); + // AbstractLogger + array_shift($stack); + foreach ($stack as $line) { + error_log($line); + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php b/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php new file mode 100644 index 0000000..d4118f0 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php @@ -0,0 +1,33 @@ +getMessage(); + } + + if (in_array($level, [LogLevel::NOTICE, LogLevel::INFO])) { + $error_level = E_USER_NOTICE; + } else { + $error_level = E_USER_WARNING; + } + + trigger_error($message, $error_level); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php new file mode 100644 index 0000000..4b616b3 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentParameters.php @@ -0,0 +1,96 @@ +augmentOperationParameters = $augmentOperationParameters; + } + + public function isAugmentOperationParameters(): bool + { + return $this->augmentOperationParameters; + } + + /** + * If set to true try to find operation parameter descriptions in the operation docblock. + * + * @param bool $augmentOperationParameters + */ + public function setAugmentOperationParameters(bool $augmentOperationParameters): void + { + $this->augmentOperationParameters = $augmentOperationParameters; + } + + public function __invoke(Analysis $analysis) + { + $this->augmentSharedParameters($analysis); + if ($this->augmentOperationParameters) { + $this->augmentOperationParameters($analysis); + } + } + + /** + * Use the parameter->name as key field (parameter->parameter) when used as reusable component + * (openapi->components->parameters). + */ + protected function augmentSharedParameters(Analysis $analysis): void + { + if (!Generator::isDefault($analysis->openapi->components) && !Generator::isDefault($analysis->openapi->components->parameters)) { + $keys = []; + $parametersWithoutKey = []; + foreach ($analysis->openapi->components->parameters as $parameter) { + if (!Generator::isDefault($parameter->parameter)) { + $keys[$parameter->parameter] = $parameter; + } else { + $parametersWithoutKey[] = $parameter; + } + } + foreach ($parametersWithoutKey as $parameter) { + if (!Generator::isDefault($parameter->name) && empty($keys[$parameter->name])) { + $parameter->parameter = $parameter->name; + $keys[$parameter->parameter] = $parameter; + } + } + } + } + + protected function augmentOperationParameters(Analysis $analysis): void + { + /** @var OA\Operation[] $operations */ + $operations = $analysis->getAnnotationsOfType(OA\Operation::class); + + foreach ($operations as $operation) { + if (!Generator::isDefault($operation->parameters)) { + $tags = []; + $this->extractContent($operation->_context->comment, $tags); + if (array_key_exists('param', $tags)) { + foreach ($tags['param'] as $name => $details) { + foreach ($operation->parameters as $parameter) { + if ($parameter->name == $name) { + if (Generator::isDefault($parameter->description) && $details['description']) { + $parameter->description = $details['description']; + } + } + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php new file mode 100644 index 0000000..7b55104 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentProperties.php @@ -0,0 +1,191 @@ +openapi->components) && !Generator::isDefault($analysis->openapi->components->schemas)) { + foreach ($analysis->openapi->components->schemas as $schema) { + if (!Generator::isDefault($schema->schema)) { + $refKey = $this->toRefKey($schema->_context, $schema->_context->class); + $refs[$refKey] = OA\Components::ref($schema); + } + } + } + + /** @var OA\Property[] $properties */ + $properties = $analysis->getAnnotationsOfType(OA\Property::class); + + foreach ($properties as $property) { + $context = $property->_context; + + if (Generator::isDefault($property->property)) { + $property->property = $context->property; + } + + if (!Generator::isDefault($property->ref)) { + continue; + } + + $comment = str_replace("\r\n", "\n", (string) $context->comment); + preg_match('/@var\s+(?[^\s]+)([ \t])?(?.+)?$/im', $comment, $varMatches); + + if (Generator::isDefault($property->type)) { + $this->augmentType($analysis, $property, $context, $refs, $varMatches); + } else { + $this->mapNativeType($property, $property->type); + } + + if (Generator::isDefault($property->description) && isset($varMatches['description'])) { + $property->description = trim($varMatches['description']); + } + if (Generator::isDefault($property->description) && $this->isRoot($property)) { + $property->description = $this->extractContent($context->comment); + } + + if (Generator::isDefault($property->example) && preg_match('/@example\s+([ \t])?(?.+)?$/im', $comment, $varMatches)) { + $property->example = $varMatches['example']; + } + } + } + + protected function toRefKey(Context $context, ?string $name): string + { + $fqn = strtolower($context->fullyQualifiedName($name)); + + return ltrim($fqn, '\\'); + } + + protected function augmentType(Analysis $analysis, OA\Property $property, Context $context, array $refs, array $varMatches): void + { + // docblock typehints + if (isset($varMatches['type'])) { + $allTypes = strtolower(trim($varMatches['type'])); + + if ($this->isNullable($allTypes) && Generator::isDefault($property->nullable)) { + $property->nullable = true; + } + + $allTypes = $this->stripNull($allTypes); + preg_match('/^([^\[]+)(.*$)/', $allTypes, $typeMatches); + $type = $typeMatches[1]; + + // finalise property type/ref + if (!$this->mapNativeType($property, $type)) { + $refKey = $this->toRefKey($context, $type); + if (Generator::isDefault($property->ref) && array_key_exists($refKey, $refs)) { + $property->ref = $refs[$refKey]; + } + } + + // ok, so we possibly have a type or ref + if (!Generator::isDefault($property->ref) && $typeMatches[2] === '' && $property->nullable) { + $refKey = $this->toRefKey($context, $type); + $property->oneOf = [ + $schema = new OA\Schema([ + 'ref' => $refs[$refKey], + '_context' => new Context(['generated' => true], $property->_context), + ]), + ]; + $analysis->addAnnotation($schema, $schema->_context); + $property->nullable = true; + } elseif ($typeMatches[2] === '[]') { + if (Generator::isDefault($property->items)) { + $property->items = $items = new OA\Items( + [ + 'type' => $property->type, + '_context' => new Context(['generated' => true], $context), + ] + ); + $analysis->addAnnotation($items, $items->_context); + if (!Generator::isDefault($property->ref)) { + $property->items->ref = $property->ref; + $property->ref = Generator::UNDEFINED; + } + $property->type = 'array'; + } + } + } + + // native typehints + if ($context->type && !Generator::isDefault($context->type)) { + if ($context->nullable === true) { + $property->nullable = true; + } + $type = strtolower($context->type); + if (!$this->mapNativeType($property, $type)) { + $refKey = $this->toRefKey($context, $type); + if (Generator::isDefault($property->ref) && array_key_exists($refKey, $refs)) { + $this->applyRef($analysis, $property, $refs[$refKey]); + } else { + if ($typeSchema = $analysis->getSchemaForSource($context->type)) { + if (Generator::isDefault($property->format)) { + $property->ref = OA\Components::ref($typeSchema); + $property->type = Generator::UNDEFINED; + } + } + } + } + } + + if (!Generator::isDefault($property->const) && Generator::isDefault($property->type)) { + if (!$this->mapNativeType($property, gettype($property->const))) { + $property->type = Generator::UNDEFINED; + } + } + } + + protected function isNullable(string $typeDescription): bool + { + return in_array('null', explode('|', strtolower($typeDescription))); + } + + protected function stripNull(string $typeDescription): string + { + if (strpos($typeDescription, '|') === false) { + return $typeDescription; + } + $types = []; + foreach (explode('|', $typeDescription) as $type) { + if (strtolower($type) === 'null') { + continue; + } + $types[] = $type; + } + + return implode('|', $types); + } + + protected function applyRef(Analysis $analysis, OA\Property $property, string $ref): void + { + if ($property->nullable === true) { + $property->oneOf = [ + $schema = new OA\Schema([ + 'ref' => $ref, + '_context' => new Context(['generated' => true], $property->_context), + ]), + ]; + $analysis->addAnnotation($schema, $schema->_context); + } else { + $property->ref = $ref; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php new file mode 100644 index 0000000..d2bd92b --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentRefs.php @@ -0,0 +1,49 @@ +getAnnotationsOfType(OA\Schema::class); + + // ref rewriting + $updatedRefs = []; + foreach ($schemas as $schema) { + if ($schema->allOf!== Generator::UNDEFINED) { + // do we have to keep track of properties refs that need updating? + foreach ($schema->allOf as $ii => $allOfSchema) { + if ($allOfSchema->properties!== Generator::UNDEFINED) { + $updatedRefs[OA\Components::ref($schema->schema . '/properties', false)] = OA\Components::ref($schema->schema . '/allOf/' . $ii . '/properties', false); + break; + } + } + } + } + + if ($updatedRefs) { + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, 'ref') && $annotation->ref !== Generator::UNDEFINED && $annotation->ref !== null) { + foreach ($updatedRefs as $origRef => $updatedRef) { + if (0 === strpos($annotation->ref, $origRef)) { + $annotation->ref = str_replace($origRef, $updatedRef, $annotation->ref); + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php new file mode 100644 index 0000000..d126bc2 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/AugmentSchemas.php @@ -0,0 +1,112 @@ +getAnnotationsOfType(OA\Schema::class); + + // Use the class names for @OA\Schema() + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->schema)) { + if ($schema->_context->is('class')) { + $schema->schema = $schema->_context->class; + } elseif ($schema->_context->is('interface')) { + $schema->schema = $schema->_context->interface; + } elseif ($schema->_context->is('trait')) { + $schema->schema = $schema->_context->trait; + } elseif ($schema->_context->is('enum')) { + $schema->schema = $schema->_context->enum; + } + } + } + + // Merge unmerged @OA\Property annotations into the @OA\Schema of the class + $unmergedProperties = $analysis->unmerged()->getAnnotationsOfType(OA\Property::class); + foreach ($unmergedProperties as $property) { + if ($property->_context->nested) { + continue; + } + + $schemaContext = $property->_context->with('class') + ?: $property->_context->with('interface') + ?: $property->_context->with('trait') + ?: $property->_context->with('enum'); + if ($schemaContext->annotations) { + foreach ($schemaContext->annotations as $annotation) { + if ($annotation instanceof OA\Schema) { + if ($annotation->_context->nested) { + // we shouldn't merge property into nested schemas + continue; + } + + $annotation->merge([$property], true); + break; + } + } + } + } + + // set schema type based on various properties + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->type)) { + if (is_array($schema->properties) && count($schema->properties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->additionalProperties) && count($schema->additionalProperties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->patternProperties) && count($schema->patternProperties) > 0) { + $schema->type = 'object'; + } elseif (is_array($schema->propertyNames) && count($schema->propertyNames) > 0) { + $schema->type = 'object'; + } + } else { + if ($typeSchema = $analysis->getSchemaForSource($schema->type)) { + if (Generator::isDefault($schema->format)) { + $schema->ref = OA\Components::ref($typeSchema); + $schema->type = Generator::UNDEFINED; + } + } + } + } + + // move schema properties into allOf if both exist + foreach ($schemas as $schema) { + if (!Generator::isDefault($schema->properties) && !Generator::isDefault($schema->allOf)) { + $allOfPropertiesSchema = null; + foreach ($schema->allOf as $allOfSchema) { + if (!Generator::isDefault($allOfSchema->properties)) { + $allOfPropertiesSchema = $allOfSchema; + break; + } + } + if (!$allOfPropertiesSchema) { + $allOfPropertiesSchema = new OA\Schema([ + 'properties' => [], + '_context' => new Context(['generated' => true], $schema->_context), + ]); + $analysis->addAnnotation($allOfPropertiesSchema, $allOfPropertiesSchema->_context); + $schema->allOf[] = $allOfPropertiesSchema; + } + $allOfPropertiesSchema->properties = array_merge($allOfPropertiesSchema->properties, $schema->properties); + $schema->properties = Generator::UNDEFINED; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php new file mode 100644 index 0000000..84ed100 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/BuildPaths.php @@ -0,0 +1,60 @@ +paths using the detected `@OA\PathItem` and `@OA\Operation` (`@OA\Get`, `@OA\Post`, etc). + */ +class BuildPaths +{ + public function __invoke(Analysis $analysis) + { + $paths = []; + // Merge @OA\PathItems with the same path. + if (!Generator::isDefault($analysis->openapi->paths)) { + foreach ($analysis->openapi->paths as $annotation) { + if (empty($annotation->path)) { + $annotation->_context->logger->warning($annotation->identity() . ' is missing required property "path" in ' . $annotation->_context); + } elseif (isset($paths[$annotation->path])) { + $paths[$annotation->path]->mergeProperties($annotation); + $analysis->annotations->detach($annotation); + } else { + $paths[$annotation->path] = $annotation; + } + } + } + + /** @var OA\Operation[] $operations */ + $operations = $analysis->unmerged()->getAnnotationsOfType(OA\Operation::class); + + // Merge @OA\Operations into existing @OA\PathItems or create a new one. + foreach ($operations as $operation) { + if ($operation->path) { + if (empty($paths[$operation->path])) { + $paths[$operation->path] = $pathItem = new OA\PathItem( + [ + 'path' => $operation->path, + '_context' => new Context(['generated' => true], $operation->_context), + ] + ); + $analysis->addAnnotation($pathItem, $pathItem->_context); + } + if ($paths[$operation->path]->merge([$operation])) { + $operation->_context->logger->warning('Unable to merge ' . $operation->identity() . ' in ' . $operation->_context); + } + } + } + if ($paths) { + $analysis->openapi->paths = array_values($paths); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php new file mode 100644 index 0000000..65ac526 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php @@ -0,0 +1,35 @@ +split(); + $merged = $split->merged->annotations; + $unmerged = $split->unmerged->annotations; + + /** @var OA\AbstractAnnotation $annotation */ + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, '_unmerged')) { + foreach ($annotation->_unmerged as $i => $item) { + if ($merged->contains($item)) { + unset($annotation->_unmerged[$i]); // Property was merged + } + } + } + } + $analysis->openapi->_unmerged = []; + foreach ($unmerged as $annotation) { + $analysis->openapi->_unmerged[] = $annotation; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php new file mode 100644 index 0000000..25ac1ad --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php @@ -0,0 +1,96 @@ +openapi->components)) { + return; + } + + $analysis->annotations = $this->collect($analysis->annotations); + + // allow multiple runs to catch nested dependencies + for ($ii = 0; $ii < 10; ++$ii) { + if (!$this->cleanup($analysis)) { + break; + } + } + } + + protected function cleanup(Analysis $analysis): bool + { + $usedRefs = []; + foreach ($analysis->annotations as $annotation) { + if (property_exists($annotation, 'ref') && !Generator::isDefault($annotation->ref) && $annotation->ref !== null) { + $usedRefs[$annotation->ref] = $annotation->ref; + } + + foreach (['allOf', 'anyOf', 'oneOff'] as $sub) { + if (property_exists($annotation, $sub) && !Generator::isDefault($annotation->{$sub})) { + foreach ($annotation->{$sub} as $subElem) { + if (is_object($subElem) && property_exists($subElem, 'ref') && !Generator::isDefault($subElem->ref) && $subElem->ref !== null) { + $usedRefs[$subElem->ref] = $subElem->ref; + } + } + } + } + + if ($annotation instanceof OA\OpenApi || $annotation instanceof OA\Operation) { + if (!Generator::isDefault($annotation->security)) { + foreach ($annotation->security as $security) { + foreach (array_keys($security) as $securityName) { + $ref = OA\Components::COMPONENTS_PREFIX . 'securitySchemes/' . $securityName; + $usedRefs[$ref] = $ref; + } + } + } + } + } + + $unusedRefs = []; + foreach (OA\Components::$_nested as $nested) { + if (2 == count($nested)) { + // $nested[1] is the name of the property that holds the component name + [$componentType, $nameProperty] = $nested; + if (!Generator::isDefault($analysis->openapi->components->{$componentType})) { + foreach ($analysis->openapi->components->{$componentType} as $component) { + $ref = OA\Components::ref($component); + if (!in_array($ref, $usedRefs)) { + $unusedRefs[$ref] = [$ref, $nameProperty]; + } + } + } + } + } + + // remove unused + foreach ($unusedRefs as $refDetails) { + [$ref, $nameProperty] = $refDetails; + [$hash, $components, $componentType, $name] = explode('/', $ref); + foreach ($analysis->openapi->components->{$componentType} as $ii => $component) { + if ($component->{$nameProperty} == $name) { + $annotation = $analysis->openapi->components->{$componentType}[$ii]; + foreach ($this->collect([$annotation]) as $unused) { + $analysis->annotations->detach($unused); + } + unset($analysis->openapi->components->{$componentType}[$ii]); + } + } + } + + return 0 != count($unusedRefs); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php new file mode 100644 index 0000000..de5f271 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/CollectorTrait.php @@ -0,0 +1,67 @@ +addAll($this->traverse($annotation)); + } + } + + return $storage; + } + + public function traverse(OA\AbstractAnnotation $annotation): \SplObjectStorage + { + $storage = new \SplObjectStorage(); + + if ($storage->contains($annotation)) { + return $storage; + } + + $storage->attach($annotation); + + foreach (array_merge($annotation::$_nested, ['allOf', 'anyOf', 'oneOff', 'callbacks']) as $properties) { + foreach ((array) $properties as $property) { + if (isset($annotation->{$property})) { + $storage->addAll($this->traverseNested($annotation->{$property})); + } + } + } + + return $storage; + } + + /** + * @param string|array|OA\AbstractAnnotation $nested + */ + protected function traverseNested($nested): \SplObjectStorage + { + $storage = new \SplObjectStorage(); + + if (is_array($nested)) { + foreach ($nested as $value) { + $storage->addAll($this->traverseNested($value)); + } + } elseif ($nested instanceof OA\AbstractAnnotation) { + $storage->addAll($this->traverse($nested)); + } + + return $storage; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php new file mode 100644 index 0000000..52683e9 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php @@ -0,0 +1,171 @@ +_context) { + return true; + } + + if (1 == count($annotation->_context->annotations)) { + return true; + } + + /** @var array $matchPriorityMap */ + $matchPriorityMap = [ + OA\OpenApi::class, + + OA\Operation::class => false, + OA\Property::class => false, + OA\Parameter::class => false, + OA\Response::class => false, + + OA\Schema::class => true, + OAT\Schema::class => true, + ]; + // try to find best root match + foreach ($matchPriorityMap as $className => $strict) { + foreach ($annotation->_context->annotations as $contextAnnotation) { + if ($strict) { + if ($className === get_class($contextAnnotation)) { + return $annotation === $contextAnnotation; + } + } else { + if ($contextAnnotation instanceof $className) { + return $annotation === $contextAnnotation; + } + } + } + } + + return false; + } + + protected function handleTag(string $line, ?array &$tags = null): void + { + if (null === $tags) { + return; + } + + // split of tag name + $token = preg_split("@[\s+ ]@u", $line, 2); + if (2 == count($token)) { + $tag = substr($token[0], 1); + $tail = $token[1]; + if (!array_key_exists($tag, $tags)) { + $tags[$tag] = []; + } + + if (false !== ($dpos = strpos($tail, '$'))) { + $type = trim(substr($tail, 0, $dpos)); + $token = preg_split("@[\s+ ]@u", substr($tail, $dpos), 2); + $name = trim(substr($token[0], 1)); + $description = 2 == count($token) ? trim($token[1]) : null; + + $tags[$tag][$name] = [ + 'type' => $type, + 'description' => $description, + ]; + } + } + } + + /** + * The text contents of the phpdoc comment (excl. tags). + */ + public function extractContent(?string $docblock, ?array &$tags = null): string + { + if (Generator::isDefault($docblock)) { + return Generator::UNDEFINED; + } + + $comment = preg_split('/(\n|\r\n)/', (string) $docblock); + $comment[0] = preg_replace('/[ \t]*\\/\*\*/', '', $comment[0]); // strip '/**' + $i = count($comment) - 1; + $comment[$i] = preg_replace('/\*\/[ \t]*$/', '', $comment[$i]); // strip '*/' + $lines = []; + $append = false; + $skip = false; + foreach ($comment as $line) { + $line = ltrim($line, "\t *"); + if (substr($line, 0, 1) === '@') { + $this->handleTag($line, $tags); + $skip = true; + } + if ($skip) { + continue; + } + if ($append) { + $i = count($lines) - 1; + $lines[$i] = substr($lines[$i], 0, -1) . $line; + } else { + $lines[] = $line; + } + $append = (substr($line, -1) === '\\'); + } + $description = trim(implode("\n", $lines)); + if ($description === '') { + return Generator::UNDEFINED; + } + + return $description; + } + + /** + * A short piece of text, usually one line, providing the basic function of the associated element. + */ + public function extractSummary(?string $docblock): string + { + if (!$content = $this->extractContent($docblock)) { + return Generator::UNDEFINED; + } + $lines = preg_split('/(\n|\r\n)/', $content); + $summary = ''; + foreach ($lines as $line) { + $summary .= $line . "\n"; + if ($line === '' || substr($line, -1) === '.') { + return trim($summary); + } + } + $summary = trim($summary); + if ($summary === '') { + return Generator::UNDEFINED; + } + + return $summary; + } + + /** + * An optional longer piece of text providing more details on the associated element’s function. + * + * This is very useful when working with a complex element. + */ + public function extractDescription(?string $docblock): string + { + $summary = $this->extractSummary($docblock); + if (!$summary) { + return Generator::UNDEFINED; + } + + $description = ''; + if (false !== ($substr = substr($this->extractContent($docblock), strlen($summary)))) { + $description = trim($substr); + } + + return $description ?: Generator::UNDEFINED; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php new file mode 100644 index 0000000..86748d8 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php @@ -0,0 +1,65 @@ + update all $ref that might reference a property merged. + */ +trait MergePropertiesTrait +{ + protected function inheritFrom(Analysis $analysis, OA\Schema $schema, OA\Schema $from, string $refPath, Context $context): void + { + if (Generator::isDefault($schema->allOf)) { + $schema->allOf = []; + } + // merging other properties into allOf is done in the AugmentSchemas processor + $schema->allOf[] = $refSchema = new OA\Schema([ + 'ref' => OA\Components::ref($refPath), + '_context' => new Context(['generated' => true], $context), + ]); + $analysis->addAnnotation($refSchema, $refSchema->_context); + } + + protected function mergeProperties(OA\Schema $schema, array $from, array &$existing): void + { + foreach ($from['properties'] as $context) { + if (is_iterable($context->annotations)) { + foreach ($context->annotations as $annotation) { + if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { + $existing[] = $annotation->_context->property; + $schema->merge([$annotation], true); + } + } + } + } + } + + protected function mergeMethods(OA\Schema $schema, array $from, array &$existing): void + { + foreach ($from['methods'] as $context) { + if (is_iterable($context->annotations)) { + foreach ($context->annotations as $annotation) { + if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { + $existing[] = $annotation->_context->property; + $schema->merge([$annotation], true); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php new file mode 100644 index 0000000..feb6907 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/Concerns/TypesTrait.php @@ -0,0 +1,54 @@ + 'array', + 'byte' => ['string', 'byte'], + 'boolean' => 'boolean', + 'bool' => 'boolean', + 'int' => 'integer', + 'integer' => 'integer', + 'long' => ['integer', 'long'], + 'float' => ['number', 'float'], + 'double' => ['number', 'double'], + 'string' => 'string', + 'date' => ['string', 'date'], + 'datetime' => ['string', 'date-time'], + '\\datetime' => ['string', 'date-time'], + 'datetimeimmutable' => ['string', 'date-time'], + '\\datetimeimmutable' => ['string', 'date-time'], + 'datetimeinterface' => ['string', 'date-time'], + '\\datetimeinterface' => ['string', 'date-time'], + 'number' => 'number', + 'object' => 'object', + ]; + + public function mapNativeType(OA\Schema $schema, string $type): bool + { + if (!array_key_exists($type, self::$NATIVE_TYPE_MAP)) { + return false; + } + + $type = self::$NATIVE_TYPE_MAP[$type]; + if (is_array($type)) { + if (Generator::isDefault($schema->format)) { + $schema->format = $type[1]; + } + $type = $type[0]; + } + + $schema->type = $type; + + return true; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php new file mode 100644 index 0000000..974bc24 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php @@ -0,0 +1,94 @@ +annotations as $annotation) { + if (property_exists($annotation, '_context') === false) { + // only annotations with context + continue; + } + + if (!$this->isRoot($annotation)) { + // only top-level annotations + continue; + } + + $hasSummary = property_exists($annotation, 'summary'); + $hasDescription = property_exists($annotation, 'description'); + if (!$hasSummary && !$hasDescription) { + continue; + } + + if ($hasSummary && $hasDescription) { + $this->summaryAndDescription($annotation); + } elseif ($hasDescription) { + $this->description($annotation); + } + } + } + + /** + * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation + */ + protected function description(OA\AbstractAnnotation $annotation): void + { + if (!Generator::isDefault($annotation->description)) { + if ($annotation->description === null) { + $annotation->description = Generator::UNDEFINED; + } + + return; + } + + $annotation->description = $this->extractContent($annotation->_context->comment); + } + + /** + * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation + */ + protected function summaryAndDescription(OA\AbstractAnnotation $annotation): void + { + $ignoreSummary = !Generator::isDefault($annotation->summary); + $ignoreDescription = !Generator::isDefault($annotation->description); + if ($annotation->summary === null) { + $ignoreSummary = true; + $annotation->summary = Generator::UNDEFINED; + } + if ($annotation->description === null) { + $annotation->description = Generator::UNDEFINED; + $ignoreDescription = true; + } + if ($ignoreSummary && $ignoreDescription) { + return; + } + if ($ignoreSummary) { + $annotation->description = $this->extractContent($annotation->_context->comment); + } elseif ($ignoreDescription) { + $annotation->summary = $this->extractContent($annotation->_context->comment); + } else { + $annotation->summary = $this->extractSummary($annotation->_context->comment); + $annotation->description = $this->extractDescription($annotation->_context->comment); + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php new file mode 100644 index 0000000..b7bea86 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandClasses.php @@ -0,0 +1,50 @@ + inherit from the ancestor if it has a schema (allOf) and stop. + * - else + * => merge ancestor properties into the schema. + */ +class ExpandClasses +{ + use Concerns\MergePropertiesTrait; + + public function __invoke(Analysis $analysis) + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('class')) { + $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); + $existing = []; + foreach ($ancestors as $ancestor) { + $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); + if ($ancestorSchema) { + $refPath = !Generator::isDefault($ancestorSchema->schema) ? $ancestorSchema->schema : $ancestor['class']; + $this->inheritFrom($analysis, $schema, $ancestorSchema, $refPath, $ancestor['context']); + + // one ancestor is enough + break; + } else { + $this->mergeMethods($schema, $ancestor, $existing); + $this->mergeProperties($schema, $ancestor, $existing); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php new file mode 100644 index 0000000..d31b712 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php @@ -0,0 +1,106 @@ +expandContextEnum($analysis); + $this->expandSchemaEnum($analysis); + } + + private function expandContextEnum(Analysis $analysis): void + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('enum')) { + $source = $schema->_context->enum; + $re = new \ReflectionEnum($schema->_context->fullyQualifiedName($source)); + $schema->schema = !Generator::isDefault($schema->schema) ? $schema->schema : $re->getShortName(); + $type = 'string'; + $schemaType = 'string'; + if ($re->isBacked() && ($backingType = $re->getBackingType()) && method_exists($backingType, 'getName')) { + if (Generator::isDefault($schema->type)) { + $type = $backingType->getName(); + } else { + $type = $schema->type; + $schemaType = $schema->type; + } + } + $schema->enum = array_map(function ($case) use ($re, $schemaType, $type) { + if ($re->isBacked() && $type === $schemaType) { + return $case->getBackingValue(); + } + + return $case->name; + }, $re->getCases()); + $this->mapNativeType($schema, $type); + } + } + } + + private function expandSchemaEnum(Analysis $analysis): void + { + /** @var OA\Schema[] $schemas */ + $schemas = $analysis->getAnnotationsOfType([ + OA\Schema::class, + OAT\Schema::class, + OA\ServerVariable::class, + OAT\ServerVariable::class, + ]); + + foreach ($schemas as $schema) { + if (Generator::isDefault($schema->enum)) { + continue; + } + + if (is_string($schema->enum)) { + // might be enum class + if (is_a($schema->enum, \UnitEnum::class, true)) { + $source = $schema->enum::cases(); + } else { + throw new \InvalidArgumentException("Unexpected enum value, requires specifying the Enum class string: $schema->enum"); + } + } elseif (is_array($schema->enum)) { + // might be array of enum, string, int, etc... + $source = $schema->enum; + } else { + throw new \InvalidArgumentException('Unexpected enum value, requires Enum class string or array'); + } + + $enums = []; + foreach ($source as $enum) { + if (is_a($enum, \UnitEnum::class)) { + $enums[] = $enum->value ?? $enum->name; + } else { + $enums[] = $enum; + } + } + + $schema->enum = $enums; + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php new file mode 100644 index 0000000..a4f5269 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandInterfaces.php @@ -0,0 +1,56 @@ +getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + foreach ($schemas as $schema) { + if ($schema->_context->is('class')) { + $className = $schema->_context->fullyQualifiedName($schema->_context->class); + $interfaces = $analysis->getInterfacesOfClass($className, true); + + if (class_exists($className) && ($parent = get_parent_class($className)) && ($inherited = array_keys(class_implements($parent)))) { + // strip interfaces we inherit from ancestor + foreach (array_keys($interfaces) as $interface) { + if (in_array(ltrim($interface, '\\'), $inherited)) { + unset($interfaces[$interface]); + } + } + } + + $existing = []; + foreach ($interfaces as $interface) { + $interfaceName = $interface['context']->fullyQualifiedName($interface['interface']); + $interfaceSchema = $analysis->getSchemaForSource($interfaceName); + if ($interfaceSchema) { + $refPath = !Generator::isDefault($interfaceSchema->schema) ? $interfaceSchema->schema : $interface['interface']; + $this->inheritFrom($analysis, $schema, $interfaceSchema, $refPath, $interface['context']); + } else { + $this->mergeMethods($schema, $interface, $existing); + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php new file mode 100644 index 0000000..a4313a6 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/ExpandTraits.php @@ -0,0 +1,81 @@ +getAnnotationsOfType([OA\Schema::class, OAT\Schema::class], true); + + // do regular trait inheritance / merge + foreach ($schemas as $schema) { + if ($schema->_context->is('trait')) { + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->trait), true); + $existing = []; + foreach ($traits as $trait) { + $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); + if ($traitSchema) { + $refPath = !Generator::isDefault($traitSchema->schema) ? $traitSchema->schema : $trait['trait']; + $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); + } else { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + } + } + + foreach ($schemas as $schema) { + if ($schema->_context->is('class') && !$schema->_context->is('generated')) { + // look at class traits + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->class), true); + $existing = []; + foreach ($traits as $trait) { + $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); + if ($traitSchema) { + $refPath = !Generator::isDefault($traitSchema->schema) ? $traitSchema->schema : $trait['trait']; + $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); + } else { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + + // also merge ancestor traits of non schema parents + $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); + $existing = []; + foreach ($ancestors as $ancestor) { + $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); + if ($ancestorSchema) { + // stop here as we inherit everything above + break; + } else { + $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($ancestor['class']), true); + foreach ($traits as $trait) { + $this->mergeMethods($schema, $trait, $existing); + $this->mergeProperties($schema, $trait, $existing); + } + } + } + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php new file mode 100644 index 0000000..cefac23 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoComponents.php @@ -0,0 +1,34 @@ +openapi->components; + if (Generator::isDefault($components)) { + $components = new OA\Components(['_context' => new Context(['generated' => true], $analysis->context)]); + } + + foreach ($analysis->annotations as $annotation) { + if (OA\Components::matchNested(get_class($annotation)) && $annotation->_context->is('nested') === false) { + // A top level annotation. + $components->merge([$annotation], true); + $analysis->openapi->components = $components; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php new file mode 100644 index 0000000..dcf2d39 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php @@ -0,0 +1,55 @@ +openapi) { + $context = new Context([], $analysis->context); + $analysis->addAnnotation(new OA\OpenApi(['_context' => $context]), $context); + } + $openapi = $analysis->openapi; + $openapi->_analysis = $analysis; + + // Merge annotations into the target openapi + $merge = []; + /** @var OA\AbstractAnnotation $annotation */ + foreach ($analysis->annotations as $annotation) { + if ($annotation === $openapi) { + continue; + } + if ($annotation instanceof OA\OpenApi) { + $paths = $annotation->paths; + unset($annotation->paths); + $openapi->mergeProperties($annotation); + if (!Generator::isDefault($paths)) { + foreach ($paths as $path) { + if (Generator::isDefault($openapi->paths)) { + $openapi->paths = []; + } + $openapi->paths[] = $path; + } + } + } elseif (OA\OpenApi::matchNested(get_class($annotation)) && property_exists($annotation, '_context') && $annotation->_context->is('nested') === false) { + // A top level annotation. + $merge[] = $annotation; + } + } + $openapi->merge($merge, true); + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php new file mode 100644 index 0000000..ab8d804 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php @@ -0,0 +1,56 @@ +getAnnotationsOfType(OA\JsonContent::class); + + foreach ($annotations as $jsonContent) { + $parent = $jsonContent->_context->nested; + if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { + if ($parent) { + $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); + } else { + $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' must be nested'); + } + continue; + } + if (Generator::isDefault($parent->content)) { + $parent->content = []; + } + $parent->content['application/json'] = $mediaType = new OA\MediaType([ + 'schema' => $jsonContent, + 'example' => $jsonContent->example, + 'examples' => $jsonContent->examples, + '_context' => new Context(['generated' => true], $jsonContent->_context), + ]); + $analysis->addAnnotation($mediaType, $mediaType->_context); + if (!$parent instanceof OA\Parameter) { + $parent->content['application/json']->mediaType = 'application/json'; + } + $jsonContent->example = Generator::UNDEFINED; + $jsonContent->examples = Generator::UNDEFINED; + + $index = array_search($jsonContent, $parent->_unmerged, true); + if ($index !== false) { + array_splice($parent->_unmerged, $index, 1); + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php new file mode 100644 index 0000000..45590dc --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php @@ -0,0 +1,56 @@ +getAnnotationsOfType(OA\XmlContent::class); + + foreach ($annotations as $xmlContent) { + $parent = $xmlContent->_context->nested; + if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { + if ($parent) { + $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); + } else { + $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' must be nested'); + } + continue; + } + if (Generator::isDefault($parent->content)) { + $parent->content = []; + } + $parent->content['application/xml'] = $mediaType = new OA\MediaType([ + 'schema' => $xmlContent, + 'example' => $xmlContent->example, + 'examples' => $xmlContent->examples, + '_context' => new Context(['generated' => true], $xmlContent->_context), + ]); + $analysis->addAnnotation($mediaType, $mediaType->_context); + if (!$parent instanceof OA\Parameter) { + $parent->content['application/xml']->mediaType = 'application/xml'; + } + $xmlContent->example = Generator::UNDEFINED; + $xmlContent->examples = Generator::UNDEFINED; + + $index = array_search($xmlContent, $parent->_unmerged, true); + if ($index !== false) { + array_splice($parent->_unmerged, $index, 1); + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php b/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php new file mode 100644 index 0000000..100a568 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Processors/OperationId.php @@ -0,0 +1,74 @@ +hash = $hash; + } + + public function isHash(): bool + { + return $this->hash; + } + + /** + * If set to true generate ids (md5) instead of clear text operation ids. + * + * @param bool $hash + */ + public function setHash(bool $hash): OperationId + { + $this->hash = $hash; + + return $this; + } + + public function __invoke(Analysis $analysis) + { + $allOperations = $analysis->getAnnotationsOfType(OA\Operation::class); + + /** @var OA\Operation $operation */ + foreach ($allOperations as $operation) { + if (null === $operation->operationId) { + $operation->operationId = Generator::UNDEFINED; + } + + if (!Generator::isDefault($operation->operationId)) { + continue; + } + + $context = $operation->_context; + if ($context && $context->method) { + $source = $context->class ?? $context->interface ?? $context->trait; + $operationId = null; + if ($source) { + if ($context->namespace) { + $operationId = $context->namespace . '\\' . $source . '::' . $context->method; + } else { + $operationId = $source . '::' . $context->method; + } + } else { + $operationId = $context->method; + } + $operationId = strtoupper($operation->method) . '::' . $operation->path . '::' . $operationId; + $operation->operationId = $this->hash ? md5($operationId) : $operationId; + } + } + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Serializer.php b/Sources/API/vendor/zircote/swagger-php/src/Serializer.php new file mode 100644 index 0000000..137a576 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Serializer.php @@ -0,0 +1,209 @@ +isValidAnnotationClass($className)) { + throw new \Exception($className . ' is not defined in OpenApi PHP Annotations'); + } + + return $this->doDeserialize(json_decode($jsonString), $className, new Context(['generated' => true])); + } + + /** + * Deserialize a file. + */ + public function deserializeFile(string $filename, string $format = 'json', string $className = OA\OpenApi::class): OA\AbstractAnnotation + { + if (!$this->isValidAnnotationClass($className)) { + throw new \Exception($className . ' is not a valid OpenApi PHP Annotations'); + } + + $contents = file_get_contents($filename); + + $ext = pathinfo($filename, PATHINFO_EXTENSION); + if ('yaml' == $format || in_array($ext, ['yml', 'yaml'])) { + $contents = json_encode(Yaml::parse($contents)); + } + + return $this->doDeserialize(json_decode($contents), $className, new Context(['generated' => true])); + } + + /** + * Do deserialization. + */ + protected function doDeserialize(\stdClass $c, string $class, Context $context): OA\AbstractAnnotation + { + $annotation = new $class(['_context' => $context]); + foreach ((array) $c as $property => $value) { + if ($property === '$ref') { + $property = 'ref'; + } + + if (substr($property, 0, 2) === 'x-') { + if (Generator::isDefault($annotation->x)) { + $annotation->x = []; + } + $custom = substr($property, 2); + $annotation->x[$custom] = $value; + } else { + $annotation->{$property} = $this->doDeserializeProperty($annotation, $property, $value, $context); + } + } + + if ($annotation instanceof OA\OpenApi) { + $context->root()->version = $annotation->openapi; + } + + return $annotation; + } + + /** + * Deserialize the annotation's property. + * + * @param mixed $value + * + * @return mixed + */ + protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, string $property, $value, Context $context) + { + // property is primitive type + if (array_key_exists($property, $annotation::$_types)) { + return $this->doDeserializeBaseProperty($annotation::$_types[$property], $value, $context); + } + + // property is embedded annotation + // note: this does not support custom nested annotation classes + foreach ($annotation::$_nested as $nestedClass => $declaration) { + // property is an annotation + if (is_string($declaration) && $declaration === $property) { + if (is_object($value)) { + return $this->doDeserialize($value, $nestedClass, $context); + } else { + return $value; + } + } + + // property is an annotation array + if (is_array($declaration) && count($declaration) === 1 && $declaration[0] === $property) { + $annotationArr = []; + foreach ($value as $v) { + $annotationArr[] = $this->doDeserialize($v, $nestedClass, $context); + } + + return $annotationArr; + } + + // property is an annotation hash map + if (is_array($declaration) && count($declaration) === 2 && $declaration[0] === $property) { + $key = $declaration[1]; + $annotationHash = []; + foreach ($value as $k => $v) { + $annotation = $this->doDeserialize($v, $nestedClass, $context); + $annotation->{$key} = $k; + $annotationHash[$k] = $annotation; + } + + return $annotationHash; + } + } + + return $value; + } + + /** + * Deserialize base annotation property. + * + * @param array|string $type The property type + * @param mixed $value The value to deserialization + * + * @return array|OA\AbstractAnnotation + */ + protected function doDeserializeBaseProperty($type, $value, Context $context) + { + $isAnnotationClass = is_string($type) && is_subclass_of(trim($type, '[]'), OA\AbstractAnnotation::class); + + if ($isAnnotationClass) { + $isArray = strpos($type, '[') === 0 && substr($type, -1) === ']'; + + if ($isArray) { + $annotationArr = []; + $class = trim($type, '[]'); + + foreach ($value as $v) { + $annotationArr[] = $this->doDeserialize($v, $class, $context); + } + + return $annotationArr; + } + + return $this->doDeserialize($value, $type, $context); + } + + return $value; + } +} diff --git a/Sources/API/vendor/zircote/swagger-php/src/Util.php b/Sources/API/vendor/zircote/swagger-php/src/Util.php new file mode 100644 index 0000000..be427b4 --- /dev/null +++ b/Sources/API/vendor/zircote/swagger-php/src/Util.php @@ -0,0 +1,154 @@ +files()->followLinks(); + } else { + $finder = new Finder(); + $finder->sortByName(); + } + if ($pattern === null) { + $pattern = '*.php'; + } + + $finder->files()->followLinks()->name($pattern); + if (is_string($directory)) { + if (is_file($directory)) { // Scan a single file? + $finder->append([$directory]); + } else { // Scan a directory + $finder->in($directory); + } + } elseif (is_array($directory)) { + foreach ($directory as $path) { + if (is_file($path)) { // Scan a file? + $finder->append([$path]); + } else { + $finder->in($path); + } + } + } else { + throw new \InvalidArgumentException('Unexpected $directory value:' . gettype($directory)); + } + if ($exclude !== null) { + if (is_string($exclude)) { + $finder->notPath(Util::getRelativePath($exclude, $directory)); + } elseif (is_array($exclude)) { + foreach ($exclude as $path) { + $finder->notPath(Util::getRelativePath($path, $directory)); + } + } else { + throw new \InvalidArgumentException('Unexpected $exclude value:' . gettype($exclude)); + } + } + + return $finder; + } + + /** + * Escapes the special characters "/" and "~". + * + * https://swagger.io/docs/specification/using-ref/ + * https://tools.ietf.org/html/rfc6901#page-3 + */ + public static function refEncode(string $raw): string + { + return str_replace('/', '~1', str_replace('~', '~0', $raw)); + } + + /** + * Converted the escaped characters "~1" and "~" back to "/" and "~". + * + * https://swagger.io/docs/specification/using-ref/ + * https://tools.ietf.org/html/rfc6901#page-3 + */ + public static function refDecode(string $encoded): string + { + return str_replace('~1', '/', str_replace('~0', '~', $encoded)); + } + + /** + * Shorten class name(s). + * + * @param array|object|string $classes Class(es) to shorten + * + * @return string|string[] One or more shortened class names + */ + public static function shorten($classes) + { + $short = []; + foreach ((array) $classes as $class) { + $short[] = '@' . str_replace([ + 'OpenApi\\Annotations\\', + 'OpenApi\\Attributes\\', + ], 'OA\\', $class); + } + + return is_array($classes) ? $short : array_pop($short); + } +}