From 1ee24079a6e45705ffbbab22e6078d57ccfb3585 Mon Sep 17 00:00:00 2001 From: Gokujo <19776684+Gokujo@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:33:16 +0000 Subject: [PATCH] chore(deps): composer update --- upload/api/composer.lock | 94 +-- upload/api/vendor/composer/installed.json | 168 ++-- upload/api/vendor/composer/installed.php | 76 +- .../src/Serializers/Native.php | 36 +- .../src/Support/SelfReference.php | 4 +- .../api/vendor/monolog/monolog/CHANGELOG.md | 7 + upload/api/vendor/monolog/monolog/README.md | 2 +- .../Monolog/Formatter/RedactingFormatter.php | 11 +- .../Monolog/Handler/RotatingFileHandler.php | 2 +- .../src/Monolog/Handler/SocketHandler.php | 1 + .../monolog/monolog/src/Monolog/Logger.php | 14 +- .../src/Parser/TokenIterator.php | 15 +- .../src/TypeResolver/StringTypeResolver.php | 24 +- .../console/Formatter/OutputFormatter.php | 21 +- upload/api/vendor/symfony/finder/Glob.php | 5 +- .../Iterator/VcsIgnoredFilterIterator.php | 2 +- .../polyfill-intl-grapheme/Grapheme.php | 156 +++- .../polyfill-intl-grapheme/bootstrap.php | 2 +- .../polyfill-intl-grapheme/bootstrap80.php | 2 +- .../polyfill-intl-normalizer/Normalizer.php | 48 +- .../symfony/polyfill-mbstring/Mbstring.php | 756 +++++++++++++++--- upload/api/vendor/symfony/type-info/Type.php | 2 +- .../symfony/type-info/Type/ArrayShapeType.php | 2 +- .../type-info/Type/IntersectionType.php | 11 + .../symfony/type-info/Type/UnionType.php | 11 + .../type-info/TypeContext/TypeContext.php | 2 +- .../TypeContext/TypeContextFactory.php | 19 +- .../symfony/type-info/TypeFactoryTrait.php | 5 + .../PhpDocAwareReflectionTypeResolver.php | 2 +- .../TypeResolver/StringTypeResolver.php | 58 +- upload/api/vendor/symfony/yaml/Inline.php | 33 +- upload/api/vendor/symfony/yaml/Parser.php | 233 ++++-- .../zircote/swagger-php/CONTRIBUTING.md | 37 + .../api/vendor/zircote/swagger-php/README.md | 2 +- .../api/vendor/zircote/swagger-php/ROADMAP.md | 88 +- .../vendor/zircote/swagger-php/bin/openapi | 2 +- .../vendor/zircote/swagger-php/composer.json | 2 +- .../zircote/swagger-php/package-lock.json | 8 +- .../vendor/zircote/swagger-php/package.json | 2 +- .../Analysers/DocBlockAnnotationFactory.php | 24 + .../src/Analysers/DocBlockParser.php | 17 +- .../src/Analysers/ReflectionAnalyser.php | 60 +- .../src/Analysers/TokenScanner.php | 2 +- .../zircote/swagger-php/src/Analysis.php | 110 ++- .../src/Annotations/AbstractAnnotation.php | 4 +- .../src/Annotations/Components.php | 8 +- .../src/Annotations/JsonSchemaTrait.php | 5 +- .../src/Annotations/ServerVariable.php | 5 +- .../Assembler/AbstractAttributeTranslator.php | 10 +- .../OptionalPropertyAttributeTranslator.php | 10 +- .../src/Attributes/AdditionalProperties.php | 25 +- .../swagger-php/src/Attributes/Items.php | 25 +- .../src/Attributes/JsonContent.php | 25 +- .../swagger-php/src/Attributes/Property.php | 25 +- .../swagger-php/src/Attributes/Schema.php | 25 +- .../src/Attributes/ServerVariable.php | 9 +- .../swagger-php/src/Attributes/XmlContent.php | 27 +- .../swagger-php/src/Augmenter/Enums.php | 66 +- .../src/Augmenter/Inheritance/Operations.php | 23 +- .../swagger-php/src/Augmenter/MediaTypes.php | 9 +- .../swagger-php/src/Augmenter/PathItems.php | 251 ++---- .../swagger-php/src/Augmenter/Refs.php | 9 +- .../swagger-php/src/Augmenter/Shortcuts.php | 59 +- .../swagger-php/src/Augmenter/Types.php | 6 +- .../zircote/swagger-php/src/Builder.php | 30 +- .../src/Compiler/OpenApi31Compiler.php | 67 +- .../src/Console/GenerateCommand.php | 64 +- .../swagger-php/src/Console/GenerateInput.php | 77 +- .../zircote/swagger-php/src/Context.php | 40 +- .../AttributeTranslatorInterface.php | 10 +- .../zircote/swagger-php/src/Generator.php | 80 +- .../zircote/swagger-php/src/HybridBridge.php | 46 +- .../src/Loggers/CollectingLogger.php | 3 + .../swagger-php/src/Loggers/DefaultLogger.php | 5 +- .../zircote/swagger-php/src/Pipeline.php | 4 +- .../src/Processors/AugmentMediaType.php | 4 +- .../src/Processors/AugmentTags.php | 9 + .../src/Processors/CleanUnmerged.php | 8 +- .../src/Processors/CleanUnusedComponents.php | 9 +- .../src/Processors/Concerns/DocblockTrait.php | 3 + .../Concerns/MergePropertiesTrait.php | 12 + .../src/Processors/DocBlockDescriptions.php | 9 +- .../src/Processors/ExpandEnums.php | 24 +- .../src/Processors/MergeIntoOpenApi.php | 5 +- .../src/Processors/MergeJsonContent.php | 4 +- .../src/Processors/MergeXmlContent.php | 4 +- .../swagger-php/src/Processors/PathFilter.php | 16 + .../zircote/swagger-php/src/Resolver.php | 8 + .../zircote/swagger-php/src/Serializer.php | 26 +- .../zircote/swagger-php/src/SourceFinder.php | 2 +- .../src/Spec/AbstractAttribute.php | 12 +- .../zircote/swagger-php/src/Spec/Schema.php | 116 +-- .../swagger-php/src/Spec/ServerVariable.php | 13 +- .../zircote/swagger-php/src/Specification.php | 6 + .../src/Specification/PathItemHierarchy.php | 128 +++ .../swagger-php/src/Specification/Walker.php | 6 +- .../src/Type/AbstractTypeResolver.php | 10 +- .../src/Type/LegacyTypeResolver.php | 7 +- .../swagger-php/src/Type/SchemaType.php | 3 + .../src/Type/TypeInfoTypeResolver.php | 33 +- .../swagger-php/src/Type/TypeMapper.php | 4 +- .../swagger-php/src/Type/TypeResolver.php | 16 +- .../swagger-php/src/TypeResolverInterface.php | 5 +- .../src/Utils/AttributeFactory.php | 22 +- .../swagger-php/src/Utils/ClassReflector.php | 40 + .../zircote/swagger-php/src/Utils/Config.php | 2 + .../swagger-php/src/Utils/DocBlockParser.php | 6 +- .../swagger-php/src/Utils/Pipeline.php | 23 +- .../src/Utils/ServerVariableEnum.php | 60 ++ .../swagger-php/src/Utils/SourceFinder.php | 7 + .../swagger-php/src/Utils/SourceScanner.php | 5 +- .../swagger-php/src/Utils/TokenScanner.php | 56 +- .../swagger-php/src/Utils/TypeMapper.php | 2 +- .../swagger-php/src/Utils/TypedList.php | 11 +- 114 files changed, 2885 insertions(+), 1081 deletions(-) create mode 100644 upload/api/vendor/zircote/swagger-php/src/Specification/PathItemHierarchy.php create mode 100644 upload/api/vendor/zircote/swagger-php/src/Utils/ClassReflector.php create mode 100644 upload/api/vendor/zircote/swagger-php/src/Utils/ServerVariableEnum.php diff --git a/upload/api/composer.lock b/upload/api/composer.lock index 9bdd8941..a7fc5ed0 100644 --- a/upload/api/composer.lock +++ b/upload/api/composer.lock @@ -131,16 +131,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.16", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" + "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", - "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/2d5869a838bbcf37e0d8b0568fc41914e81374b5", + "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5", "shasum": "" }, "require": { @@ -188,7 +188,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-08-18T20:28:54+00:00" + "time": "2026-09-22T14:32:34+00:00" }, { "name": "lcobucci/jwt", @@ -603,16 +603,16 @@ }, { "name": "monolog/monolog", - "version": "3.12.0", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "72c534fc0ab181ef52d92a68382318631e301608" + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/72c534fc0ab181ef52d92a68382318631e301608", - "reference": "72c534fc0ab181ef52d92a68382318631e301608", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7059e4c691d05ea1222b5124bed0f961be46e15", + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15", "shasum": "" }, "require": { @@ -692,7 +692,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.12.0" + "source": "https://github.com/Seldaek/monolog/tree/3.12.1" }, "funding": [ { @@ -704,7 +704,7 @@ "type": "tidelift" } ], - "time": "2026-09-09T08:34:20+00:00" + "time": "2026-09-29T19:21:12+00:00" }, { "name": "nikic/fast-route", @@ -2083,16 +2083,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { @@ -2141,7 +2141,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { @@ -2161,20 +2161,20 @@ "type": "tidelift" } ], - "time": "2026-07-28T08:25:59+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.42.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { @@ -2226,7 +2226,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -2246,20 +2246,20 @@ "type": "tidelift" } ], - "time": "2026-08-07T06:33:24+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { @@ -2311,7 +2311,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -2331,7 +2331,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:59:30+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { "name": "symfony/service-contracts", @@ -3614,16 +3614,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.35", + "version": "12.5.37", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "345ac22e42cb14cdc7aa8428655d8bb8c82a4aab" + "reference": "684340c1590e5f3f3b859f5e80619725d88b63ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/345ac22e42cb14cdc7aa8428655d8bb8c82a4aab", - "reference": "345ac22e42cb14cdc7aa8428655d8bb8c82a4aab", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/684340c1590e5f3f3b859f5e80619725d88b63ba", + "reference": "684340c1590e5f3f3b859f5e80619725d88b63ba", "shasum": "" }, "require": { @@ -3692,7 +3692,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.35" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.37" }, "funding": [ { @@ -3700,7 +3700,7 @@ "type": "other" } ], - "time": "2026-09-09T04:48:50+00:00" + "time": "2026-09-29T12:08:54+00:00" }, { "name": "psr/http-client", @@ -4729,16 +4729,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.37.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { @@ -4789,7 +4789,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -4809,20 +4809,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.38.1", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", "shasum": "" }, "require": { @@ -4869,7 +4869,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" }, "funding": [ { @@ -4889,7 +4889,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:45:58+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "theseer/tokenizer", diff --git a/upload/api/vendor/composer/installed.json b/upload/api/vendor/composer/installed.json index fc82d6f3..5c870950 100644 --- a/upload/api/vendor/composer/installed.json +++ b/upload/api/vendor/composer/installed.json @@ -131,17 +131,17 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.16", - "version_normalized": "2.0.16.0", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" + "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", - "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/2d5869a838bbcf37e0d8b0568fc41914e81374b5", + "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5", "shasum": "" }, "require": { @@ -154,7 +154,7 @@ "phpstan/phpstan": "^2.0", "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, - "time": "2026-08-18T20:28:54+00:00", + "time": "2026-09-22T14:32:34+00:00", "type": "library", "extra": { "branch-alias": { @@ -621,17 +621,17 @@ }, { "name": "monolog/monolog", - "version": "3.12.0", - "version_normalized": "3.12.0.0", + "version": "3.12.1", + "version_normalized": "3.12.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "72c534fc0ab181ef52d92a68382318631e301608" + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/72c534fc0ab181ef52d92a68382318631e301608", - "reference": "72c534fc0ab181ef52d92a68382318631e301608", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7059e4c691d05ea1222b5124bed0f961be46e15", + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15", "shasum": "" }, "require": { @@ -680,7 +680,7 @@ "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, - "time": "2026-09-09T08:34:20+00:00", + "time": "2026-09-29T19:21:12+00:00", "type": "library", "extra": { "branch-alias": { @@ -713,7 +713,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.12.0" + "source": "https://github.com/Seldaek/monolog/tree/3.12.1" }, "funding": [ { @@ -1029,17 +1029,17 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.5", - "version_normalized": "2.3.5.0", + "version": "2.3.6", + "version_normalized": "2.3.6.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" + "reference": "1427af4647235b4a73b13940b1f1258d3584f36e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", - "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1427af4647235b4a73b13940b1f1258d3584f36e", + "reference": "1427af4647235b4a73b13940b1f1258d3584f36e", "shasum": "" }, "require": { @@ -1056,7 +1056,7 @@ "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, - "time": "2026-08-31T16:05:28+00:00", + "time": "2026-09-27T20:07:45+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1073,7 +1073,7 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.5" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.6" }, "install-path": "../phpstan/phpdoc-parser" }, @@ -1525,17 +1525,17 @@ }, { "name": "radebatz/type-info-extras", - "version": "1.0.8", - "version_normalized": "1.0.8.0", + "version": "1.0.9", + "version_normalized": "1.0.9.0", "source": { "type": "git", "url": "https://github.com/DerManoMann/type-info-extras.git", - "reference": "519056728bcd4b4bd883c3ee5d78774d0384e757" + "reference": "2d9f01d60d642f890d9f170c0d8fde1f75ab17fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/519056728bcd4b4bd883c3ee5d78774d0384e757", - "reference": "519056728bcd4b4bd883c3ee5d78774d0384e757", + "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/2d9f01d60d642f890d9f170c0d8fde1f75ab17fb", + "reference": "2d9f01d60d642f890d9f170c0d8fde1f75ab17fb", "shasum": "" }, "require": { @@ -1548,7 +1548,7 @@ "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^11.0" }, - "time": "2026-09-08T08:46:24+00:00", + "time": "2026-09-29T22:04:31+00:00", "type": "library", "extra": { "branch-alias": { @@ -1584,7 +1584,7 @@ ], "support": { "issues": "https://github.com/DerManoMann/type-info-extras/issues", - "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.8" + "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.9" }, "install-path": "../radebatz/type-info-extras" }, @@ -1838,17 +1838,17 @@ }, { "name": "symfony/console", - "version": "v7.4.19", - "version_normalized": "7.4.19.0", + "version": "v7.4.20", + "version_normalized": "7.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3a1973458b153f566a8d7499bc06ece323d60063" + "reference": "d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3a1973458b153f566a8d7499bc06ece323d60063", - "reference": "3a1973458b153f566a8d7499bc06ece323d60063", + "url": "https://api.github.com/repos/symfony/console/zipball/d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d", + "reference": "d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d", "shasum": "" }, "require": { @@ -1881,7 +1881,7 @@ "symfony/stopwatch": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0" }, - "time": "2026-09-13T10:38:41+00:00", + "time": "2026-09-18T15:55:00+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1915,7 +1915,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.19" + "source": "https://github.com/symfony/console/tree/v7.4.20" }, "funding": [ { @@ -2013,17 +2013,17 @@ }, { "name": "symfony/finder", - "version": "v7.4.19", - "version_normalized": "7.4.19.0", + "version": "v7.4.20", + "version_normalized": "7.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1b900bc6ae5ba60c5eee69c11ba44566190576fe" + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1b900bc6ae5ba60c5eee69c11ba44566190576fe", - "reference": "1b900bc6ae5ba60c5eee69c11ba44566190576fe", + "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", "shasum": "" }, "require": { @@ -2032,7 +2032,7 @@ "require-dev": { "symfony/filesystem": "^6.4|^7.0|^8.0" }, - "time": "2026-09-10T19:14:10+00:00", + "time": "2026-09-26T06:31:10+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2060,7 +2060,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.19" + "source": "https://github.com/symfony/finder/tree/v7.4.20" }, "funding": [ { @@ -2170,17 +2170,17 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", - "version_normalized": "1.41.0.0", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { @@ -2189,7 +2189,7 @@ "suggest": { "ext-intl": "For best performance" }, - "time": "2026-07-28T08:25:59+00:00", + "time": "2026-09-25T15:46:47+00:00", "type": "library", "extra": { "thanks": { @@ -2231,7 +2231,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { @@ -2255,17 +2255,17 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.42.0", - "version_normalized": "1.42.0.0", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { @@ -2274,7 +2274,7 @@ "suggest": { "ext-intl": "For best performance" }, - "time": "2026-08-07T06:33:24+00:00", + "time": "2026-09-23T22:02:09+00:00", "type": "library", "extra": { "thanks": { @@ -2319,7 +2319,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -2343,17 +2343,17 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", - "version_normalized": "1.38.2.0", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { @@ -2366,7 +2366,7 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2026-05-27T06:59:30+00:00", + "time": "2026-09-24T15:19:04+00:00", "type": "library", "extra": { "thanks": { @@ -2407,7 +2407,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -2615,17 +2615,17 @@ }, { "name": "symfony/type-info", - "version": "v7.4.17", - "version_normalized": "7.4.17.0", + "version": "v7.4.20", + "version_normalized": "7.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "62d0ad6995630a4f7ea8fd270aadb3e2545c8e31" + "reference": "bbaaf3366400f5c85f66800b955c77b46e8abad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/62d0ad6995630a4f7ea8fd270aadb3e2545c8e31", - "reference": "62d0ad6995630a4f7ea8fd270aadb3e2545c8e31", + "url": "https://api.github.com/repos/symfony/type-info/zipball/bbaaf3366400f5c85f66800b955c77b46e8abad2", + "reference": "bbaaf3366400f5c85f66800b955c77b46e8abad2", "shasum": "" }, "require": { @@ -2639,7 +2639,7 @@ "require-dev": { "phpstan/phpdoc-parser": "^1.30|^2.0" }, - "time": "2026-08-21T15:52:48+00:00", + "time": "2026-09-28T12:19:37+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2677,7 +2677,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.17" + "source": "https://github.com/symfony/type-info/tree/v7.4.20" }, "funding": [ { @@ -2701,17 +2701,17 @@ }, { "name": "symfony/yaml", - "version": "v7.4.18", - "version_normalized": "7.4.18.0", + "version": "v7.4.20", + "version_normalized": "7.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1" + "reference": "0222bf4dfaa1e3ceb8fff579635d655f2950d5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4cef939e55b8a21c5780418de0d98ab00d0736e1", - "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0222bf4dfaa1e3ceb8fff579635d655f2950d5a7", + "reference": "0222bf4dfaa1e3ceb8fff579635d655f2950d5a7", "shasum": "" }, "require": { @@ -2725,7 +2725,7 @@ "require-dev": { "symfony/console": "^6.4|^7.0|^8.0" }, - "time": "2026-08-30T00:47:26+00:00", + "time": "2026-09-26T06:31:10+00:00", "bin": [ "Resources/bin/yaml-lint" ], @@ -2756,7 +2756,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.18" + "source": "https://github.com/symfony/yaml/tree/v7.4.20" }, "funding": [ { @@ -2780,17 +2780,17 @@ }, { "name": "zircote/swagger-php", - "version": "6.9.0", - "version_normalized": "6.9.0.0", + "version": "6.11.0", + "version_normalized": "6.11.0.0", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "839d6b65c06737e4243635d6f81cbbe13022aa5a" + "reference": "f998f7e712658fba61f86fccbdcf8e301a982175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/839d6b65c06737e4243635d6f81cbbe13022aa5a", - "reference": "839d6b65c06737e4243635d6f81cbbe13022aa5a", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/f998f7e712658fba61f86fccbdcf8e301a982175", + "reference": "f998f7e712658fba61f86fccbdcf8e301a982175", "shasum": "" }, "require": { @@ -2799,7 +2799,7 @@ "phpstan/phpdoc-parser": "^2.0", "psr/log": "^1.1 || ^2.0 || ^3.0", "radebatz/type-info-extras": "^1.0.2", - "symfony/console": "^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", "symfony/deprecation-contracts": "^2 || ^3", "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" @@ -2814,7 +2814,7 @@ "phpunit/phpunit": "^11.5 || >=12.5.22", "rector/rector": "2.6.5" }, - "time": "2026-09-13T22:58:11+00:00", + "time": "2026-09-26T07:11:30+00:00", "bin": [ "bin/openapi" ], @@ -2860,7 +2860,7 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/6.9.0" + "source": "https://github.com/zircote/swagger-php/tree/6.11.0" }, "funding": [ { diff --git a/upload/api/vendor/composer/installed.php b/upload/api/vendor/composer/installed.php index 0476877e..14392176 100644 --- a/upload/api/vendor/composer/installed.php +++ b/upload/api/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'devcraftclub/dle-api', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '609968a85ca5329dc81cca33c741b120108ccd25', + 'reference' => '120f945758f45cdffb74a37fceb394ed1759eb3e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'devcraftclub/dle-api' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '609968a85ca5329dc81cca33c741b120108ccd25', + 'reference' => '120f945758f45cdffb74a37fceb394ed1759eb3e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -38,9 +38,9 @@ 'dev_requirement' => false, ), 'laravel/serializable-closure' => array( - 'pretty_version' => 'v2.0.16', - 'version' => '2.0.16.0', - 'reference' => '7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed', + 'pretty_version' => 'v2.1.0', + 'version' => '2.1.0.0', + 'reference' => '2d5869a838bbcf37e0d8b0568fc41914e81374b5', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/serializable-closure', 'aliases' => array(), @@ -104,9 +104,9 @@ ), ), 'monolog/monolog' => array( - 'pretty_version' => '3.12.0', - 'version' => '3.12.0.0', - 'reference' => '72c534fc0ab181ef52d92a68382318631e301608', + 'pretty_version' => '3.12.1', + 'version' => '3.12.1.0', + 'reference' => 'd7059e4c691d05ea1222b5124bed0f961be46e15', 'type' => 'library', 'install_path' => __DIR__ . '/../monolog/monolog', 'aliases' => array(), @@ -158,9 +158,9 @@ 'dev_requirement' => false, ), 'phpstan/phpdoc-parser' => array( - 'pretty_version' => '2.3.5', - 'version' => '2.3.5.0', - 'reference' => '148cefffaf0233e4c08cc13db8a195a56dd6dfe9', + 'pretty_version' => '2.3.6', + 'version' => '2.3.6.0', + 'reference' => '1427af4647235b4a73b13940b1f1258d3584f36e', 'type' => 'library', 'install_path' => __DIR__ . '/../phpstan/phpdoc-parser', 'aliases' => array(), @@ -270,9 +270,9 @@ ), ), 'radebatz/type-info-extras' => array( - 'pretty_version' => '1.0.8', - 'version' => '1.0.8.0', - 'reference' => '519056728bcd4b4bd883c3ee5d78774d0384e757', + 'pretty_version' => '1.0.9', + 'version' => '1.0.9.0', + 'reference' => '2d9f01d60d642f890d9f170c0d8fde1f75ab17fb', 'type' => 'library', 'install_path' => __DIR__ . '/../radebatz/type-info-extras', 'aliases' => array(), @@ -306,9 +306,9 @@ 'dev_requirement' => false, ), 'symfony/console' => array( - 'pretty_version' => 'v7.4.19', - 'version' => '7.4.19.0', - 'reference' => '3a1973458b153f566a8d7499bc06ece323d60063', + 'pretty_version' => 'v7.4.20', + 'version' => '7.4.20.0', + 'reference' => 'd7bca2fe05241dc7732d0c8b6d551b879f5d0a7d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), @@ -324,9 +324,9 @@ 'dev_requirement' => false, ), 'symfony/finder' => array( - 'pretty_version' => 'v7.4.19', - 'version' => '7.4.19.0', - 'reference' => '1b900bc6ae5ba60c5eee69c11ba44566190576fe', + 'pretty_version' => 'v7.4.20', + 'version' => '7.4.20.0', + 'reference' => '2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), @@ -342,27 +342,27 @@ 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( - 'pretty_version' => 'v1.41.0', - 'version' => '1.41.0.0', - 'reference' => 'bb899c1db0aa8127dc3afe8cda4a67eb24915f8d', + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '5cac8f4db97208823aa353f59a01faad2da52c75', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( - 'pretty_version' => 'v1.42.0', - 'version' => '1.42.0.0', - 'reference' => 'aa20edea75bd9c48cfecc8360922e5a6e5c44502', + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => 'ebd57ccc7edb93d632e184efbe7693d2517bab3b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.38.2', - 'version' => '1.38.2.0', - 'reference' => 'd3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6', + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '4d5d331bf731e0aedbc6b2f5e61135b7b7996a72', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), @@ -387,27 +387,27 @@ 'dev_requirement' => false, ), 'symfony/type-info' => array( - 'pretty_version' => 'v7.4.17', - 'version' => '7.4.17.0', - 'reference' => '62d0ad6995630a4f7ea8fd270aadb3e2545c8e31', + 'pretty_version' => 'v7.4.20', + 'version' => '7.4.20.0', + 'reference' => 'bbaaf3366400f5c85f66800b955c77b46e8abad2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/type-info', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/yaml' => array( - 'pretty_version' => 'v7.4.18', - 'version' => '7.4.18.0', - 'reference' => '4cef939e55b8a21c5780418de0d98ab00d0736e1', + 'pretty_version' => 'v7.4.20', + 'version' => '7.4.20.0', + 'reference' => '0222bf4dfaa1e3ceb8fff579635d655f2950d5a7', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), 'dev_requirement' => false, ), 'zircote/swagger-php' => array( - 'pretty_version' => '6.9.0', - 'version' => '6.9.0.0', - 'reference' => '839d6b65c06737e4243635d6f81cbbe13022aa5a', + 'pretty_version' => '6.11.0', + 'version' => '6.11.0.0', + 'reference' => 'f998f7e712658fba61f86fccbdcf8e301a982175', 'type' => 'library', 'install_path' => __DIR__ . '/../zircote/swagger-php', 'aliases' => array(), diff --git a/upload/api/vendor/laravel/serializable-closure/src/Serializers/Native.php b/upload/api/vendor/laravel/serializable-closure/src/Serializers/Native.php index 8c608c7e..ac95d117 100644 --- a/upload/api/vendor/laravel/serializable-closure/src/Serializers/Native.php +++ b/upload/api/vendor/laravel/serializable-closure/src/Serializers/Native.php @@ -55,7 +55,7 @@ class Native implements Serializable /** * The closure's reference. * - * @var string + * @var string|int */ protected $reference; @@ -133,7 +133,9 @@ public function __serialize() } } - $this->reference = spl_object_hash($this->closure); + $this->reference = PHP_VERSION_ID >= 80600 + ? spl_object_id($this->closure) + : spl_object_hash($this->closure); $this->scope[$this->closure] = $this; @@ -265,7 +267,7 @@ public static function wrapClosures(&$data, $storage) $instance = $data; $reflection = new ReflectionObject($instance); - if (! $reflection->isUserDefined()) { + if (! $reflection->isUserDefined() || static::hasInternalAncestry($reflection)) { $storage[$instance] = $data; return; @@ -497,7 +499,7 @@ protected function mapByReference(&$data) $reflection = new ReflectionObject($data); - if (! $reflection->isUserDefined()) { + if (! $reflection->isUserDefined() || static::hasInternalAncestry($reflection)) { $this->scope[$instance] = $data; return; @@ -566,6 +568,32 @@ protected static function setPropertyValue(ReflectionProperty $property, object : $property->setValue($object, $value); } + /** + * Determine if the class inherits from a PHP internal class. + * + * Instances of such classes carry internal state that + * newInstanceWithoutConstructor() cannot rebuild (e.g. DateTime + * subclasses like Carbon), so they must be kept as-is instead of + * being reconstructed property by property. + * + * @param \ReflectionObject $reflection + * @return bool + */ + protected static function hasInternalAncestry(ReflectionObject $reflection): bool + { + $class = $reflection->getParentClass(); + + while ($class !== false) { + if ($class->isInternal()) { + return true; + } + + $class = $class->getParentClass(); + } + + return false; + } + /** * Determine is virtual property. * diff --git a/upload/api/vendor/laravel/serializable-closure/src/Support/SelfReference.php b/upload/api/vendor/laravel/serializable-closure/src/Support/SelfReference.php index 58319504..e97f06fb 100644 --- a/upload/api/vendor/laravel/serializable-closure/src/Support/SelfReference.php +++ b/upload/api/vendor/laravel/serializable-closure/src/Support/SelfReference.php @@ -7,14 +7,14 @@ class SelfReference /** * The unique hash representing the object. * - * @var string + * @var string|int */ public $hash; /** * Creates a new self reference instance. * - * @param string $hash + * @param string|int $hash * @return void */ public function __construct($hash) diff --git a/upload/api/vendor/monolog/monolog/CHANGELOG.md b/upload/api/vendor/monolog/monolog/CHANGELOG.md index a94f2ff2..15a35665 100644 --- a/upload/api/vendor/monolog/monolog/CHANGELOG.md +++ b/upload/api/vendor/monolog/monolog/CHANGELOG.md @@ -1,3 +1,10 @@ +### 3.12.1 (2026-09-29) + + * Fixed `Logger` silently dropping every record for the rest of the process once the infinite loop guard aborted a nested logging cycle, which could hit long-running workers (#2072) + * Fixed `RedactingFormatter` letting secrets containing `&`, `<` or `>` through when wrapping an `HtmlFormatter` (#2069) + * Fixed `SocketHandler` throwing the writing timeout immediately when the first write made no progress, instead of waiting for the configured timeout (#2071) + * Fixed `RotatingFileHandler` computing the first rotation in the default timezone instead of the `$timezone` passed to the constructor (#2070) + ### 3.12.0 (2026-09-09) * Added support for reading the timestamp of new records from a PSR-20 clock, via a new `$clock` constructor param and `Logger::setClock()` (#2065) diff --git a/upload/api/vendor/monolog/monolog/README.md b/upload/api/vendor/monolog/monolog/README.md index 0564f96f..52cf8c7c 100644 --- a/upload/api/vendor/monolog/monolog/README.md +++ b/upload/api/vendor/monolog/monolog/README.md @@ -1,4 +1,4 @@ -

Monolog

+

Monolog

# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) diff --git a/upload/api/vendor/monolog/monolog/src/Monolog/Formatter/RedactingFormatter.php b/upload/api/vendor/monolog/monolog/src/Monolog/Formatter/RedactingFormatter.php index d97a66a1..65a311ae 100644 --- a/upload/api/vendor/monolog/monolog/src/Monolog/Formatter/RedactingFormatter.php +++ b/upload/api/vendor/monolog/monolog/src/Monolog/Formatter/RedactingFormatter.php @@ -173,13 +173,14 @@ private function collectSecrets(iterable $records): array return []; } - // a secret containing quotes/backslashes/newlines shows up escaped in json output, - // so both forms have to be looked for + // a secret shows up escaped in json output (quotes/backslashes/newlines) and in html output + // (& < >), so every spelling the wrapped formatter can produce has to be looked for $escapedSecrets = []; foreach ($secrets as $secret) { - $escaped = substr(Utils::jsonEncode($secret, null, true), 1, -1); - if ($escaped !== $secret && $escaped !== '') { - $escapedSecrets[] = $escaped; + foreach ([substr(Utils::jsonEncode($secret, null, true), 1, -1), htmlspecialchars($secret, ENT_NOQUOTES, 'UTF-8')] as $escaped) { + if ($escaped !== $secret && $escaped !== '') { + $escapedSecrets[] = $escaped; + } } } diff --git a/upload/api/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/upload/api/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php index bb0d2bbc..ccdf2194 100644 --- a/upload/api/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php +++ b/upload/api/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -50,9 +50,9 @@ public function __construct(string $filename, int $maxFiles = 0, int|string|Leve { $this->filename = Utils::canonicalizePath($filename); $this->maxFiles = $maxFiles; + $this->timezone = $timezone; $this->setFilenameFormat($filenameFormat, $dateFormat); $this->nextRotation = $this->getNextRotation(); - $this->timezone = $timezone; parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); } diff --git a/upload/api/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/upload/api/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php index 36d46bf9..4560d763 100644 --- a/upload/api/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php +++ b/upload/api/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -385,6 +385,7 @@ private function writeToSocket(string $data): void $length = \strlen($data); $sent = 0; $this->lastSentBytes = $sent; + $this->lastWritingAt = microtime(true); while ($this->isConnected() && $sent < $length) { if (0 === $sent) { $chunk = $this->fwrite($data); diff --git a/upload/api/vendor/monolog/monolog/src/Monolog/Logger.php b/upload/api/vendor/monolog/monolog/src/Monolog/Logger.php index 75656235..177e1455 100644 --- a/upload/api/vendor/monolog/monolog/src/Monolog/Logger.php +++ b/upload/api/vendor/monolog/monolog/src/Monolog/Logger.php @@ -353,15 +353,15 @@ public function addRecord(int|Level $level, string $message, array $context = [] $logDepth = 0; } - if ($logDepth === 3) { - $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + try { + if ($logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); - return false; - } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above - return false; - } + return false; + } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above + return false; + } - try { $recordInitialized = \count($this->processors) === 0; $record = new LogRecord( diff --git a/upload/api/vendor/phpstan/phpdoc-parser/src/Parser/TokenIterator.php b/upload/api/vendor/phpstan/phpdoc-parser/src/Parser/TokenIterator.php index f42cc2fb..043639f1 100644 --- a/upload/api/vendor/phpstan/phpdoc-parser/src/Parser/TokenIterator.php +++ b/upload/api/vendor/phpstan/phpdoc-parser/src/Parser/TokenIterator.php @@ -31,6 +31,9 @@ class TokenIterator private ?string $newline = null; + /** @var list|null */ + private ?array $offsets = null; + /** * @param list $tokens */ @@ -81,12 +84,16 @@ public function currentTokenType(): int public function currentTokenOffset(): int { - $offset = 0; - for ($i = 0; $i < $this->index; $i++) { - $offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]); + if ($this->offsets === null) { + $offset = 0; + $this->offsets = []; + foreach ($this->tokens as $token) { + $this->offsets[] = $offset; + $offset += strlen($token[Lexer::VALUE_OFFSET]); + } } - return $offset; + return $this->offsets[$this->index]; } public function currentTokenLine(): int diff --git a/upload/api/vendor/radebatz/type-info-extras/src/TypeResolver/StringTypeResolver.php b/upload/api/vendor/radebatz/type-info-extras/src/TypeResolver/StringTypeResolver.php index 37d4973f..efcd3be0 100644 --- a/upload/api/vendor/radebatz/type-info-extras/src/TypeResolver/StringTypeResolver.php +++ b/upload/api/vendor/radebatz/type-info-extras/src/TypeResolver/StringTypeResolver.php @@ -111,10 +111,18 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Bas if ($node instanceof ArrayShapeNode) { $shape = []; foreach ($node->items as $item) { - $shape[(string) $item->keyName] = [ + $shapeItem = [ 'type' => $this->getTypeFromNode($item->valueType, $typeContext), 'optional' => $item->optional, ]; + + if (null === $item->keyName) { + $shape[] = $shapeItem; + + continue; + } + + $shape[$item->keyName instanceof ConstExprStringNode ? $item->keyName->value : (string) $item->keyName] = $shapeItem; } return Type::arrayShape( @@ -128,7 +136,7 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Bas if ($node instanceof ObjectShapeNode) { $shape = []; foreach ($node->items as $item) { - $shape[(string) $item->keyName] = [ + $shape[$item->keyName instanceof ConstExprStringNode ? $item->keyName->value : (string) $item->keyName] = [ 'type' => $this->getTypeFromNode($item->valueType, $typeContext), 'optional' => $item->optional, ]; @@ -149,7 +157,13 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Bas if ($node instanceof ConstTypeNode) { if ($node->constExpr instanceof ConstFetchNode) { - $className = match (strtolower($node->constExpr->className)) { + $classKeyword = strtolower($node->constExpr->className); + + if (null === $typeContext && \in_array($classKeyword, ['self', 'static', 'parent'], true)) { + throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "%s".', TypeContext::class, $classKeyword)); + } + + $className = match ($classKeyword) { 'self' => $typeContext->getDeclaringClass(), 'static' => $typeContext->getCalledClass(), 'parent' => $typeContext->getParentClass(), @@ -177,6 +191,10 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Bas } } + if (!$types) { + throw new \DomainException(\sprintf('No "%s" constant found in "%s".', $node->constExpr->name, $className)); + } + return CollectionType::mergeCollectionValueTypes($types); } diff --git a/upload/api/vendor/symfony/console/Formatter/OutputFormatter.php b/upload/api/vendor/symfony/console/Formatter/OutputFormatter.php index 6e402ec4..1e757183 100644 --- a/upload/api/vendor/symfony/console/Formatter/OutputFormatter.php +++ b/upload/api/vendor/symfony/console/Formatter/OutputFormatter.php @@ -123,10 +123,6 @@ public function formatAndWrap(?string $message, int $width): string return ''; } - // For ASCII-only strings, byte positions equal character positions, - // so we can use native strlen/substr which is much faster than Helper::length/substr. - $isAscii = !preg_match('/[\x80-\xFF]/', $message); - $offset = 0; $output = ''; $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; @@ -141,17 +137,10 @@ public function formatAndWrap(?string $message, int $width): string continue; } - if ($isAscii) { - // For ASCII, byte position = character position, no conversion needed - $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); - $offset = $pos + \strlen($text); - } else { - // convert byte position to character position. - $pos = Helper::length(substr($message, 0, $pos)); - // add the text up to the next tag - $output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); - $offset = $pos + Helper::length($text); - } + // tags are delimited by "<" and ">", which are ASCII, so a tag offset can never + // fall inside a multibyte sequence: slicing by bytes is safe here + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); + $offset = $pos + \strlen($text); // opening tag? if ($open = '/' !== $text[1]) { @@ -172,7 +161,7 @@ public function formatAndWrap(?string $message, int $width): string } } - $output .= $this->applyCurrentStyle($isAscii ? substr($message, $offset) : Helper::substr($message, $offset), $output, $width, $currentLineLength); + $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } diff --git a/upload/api/vendor/symfony/finder/Glob.php b/upload/api/vendor/symfony/finder/Glob.php index d950e48f..ac11e8e0 100644 --- a/upload/api/vendor/symfony/finder/Glob.php +++ b/upload/api/vendor/symfony/finder/Glob.php @@ -77,7 +77,10 @@ public static function toRegex(string $glob, bool $strictLeadingDot = true, bool if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { $regex .= "\\$car"; } elseif ('*' === $car) { - $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + if ($escaping || $strictWildcardSlash || !str_ends_with($regex, '.*')) { + // ".*.*" matches the same as ".*" but backtracks quadratically + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } } elseif ('?' === $car) { $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); } elseif ('{' === $car) { diff --git a/upload/api/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/upload/api/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index 9e69d0e3..3715e269 100644 --- a/upload/api/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/upload/api/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -39,7 +39,7 @@ public function __construct(\Iterator $iterator, string $baseDir) $this->baseDir = $this->normalizePath(realpath($baseDir) ?: $baseDir); foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) { - if (@is_dir("{$directory}/.git")) { + if (@file_exists("{$directory}/.git")) { $this->baseDir = $directory; break; } diff --git a/upload/api/vendor/symfony/polyfill-intl-grapheme/Grapheme.php b/upload/api/vendor/symfony/polyfill-intl-grapheme/Grapheme.php index cad3b838..94d080ac 100644 --- a/upload/api/vendor/symfony/polyfill-intl-grapheme/Grapheme.php +++ b/upload/api/vendor/symfony/polyfill-intl-grapheme/Grapheme.php @@ -40,6 +40,10 @@ final class Grapheme // This regular expression is a work around for http://bugs.exim.org/1279 public const GRAPHEME_CLUSTER_RX = '(?:\r\n|[\x{1F1E6}-\x{1F1FF}][\x{1F1E6}-\x{1F1FF}]?|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*(?:\x{200D}(?:[\x{1F1E6}-\x{1F1FF}]|[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가-힣]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*)*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + // Table 3-7 of the Unicode standard for the well-formed byte sequences (group 1), + // then the maximal subparts an ill-formed sequence is made of (group 2). + private const UTF8_CHAR_OR_SUBPART_RX = '/([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|[\xEE-\xEF][\x80-\xBF]{2}|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|(\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|\xE0[\xA0-\xBF]|[\xE1-\xEC][\x80-\xBF]|\xED[\x80-\x9F]|[\xEE-\xEF][\x80-\xBF]|[\x80-\xFF])/s'; + private const CASE_FOLD = [ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], @@ -130,6 +134,10 @@ public static function grapheme_substr($s, $start, $len = null) $len = 2147483647; } + if (!self::isUtf8($s)) { + return false; + } + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); $slen = \count($s[0]); @@ -189,33 +197,47 @@ public static function grapheme_strripos($s, $needle, $offset = 0) public static function grapheme_stristr($s, $needle, $beforeNeedle = false) { + if (!self::isUtf8($s) || !self::isUtf8($needle)) { + return false; + } + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); } public static function grapheme_strstr($s, $needle, $beforeNeedle = false) { + if (!self::isUtf8($s) || !self::isUtf8($needle)) { + return false; + } + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); } public static function grapheme_str_split($s, $len = 1) { - if (0 > $len || 1073741823 < $len) { - throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.'); + if (0 >= $len || 1073741823 < $len) { + throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823'); } if ('' === $s) { return []; } - if (!preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) { + if (preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) { + $graphemes = $matches[0]; + } else { + $graphemes = self::splitIllFormed($s); + } + + if (!$graphemes) { return false; } if (1 === $len) { - return $matches[0]; + return $graphemes; } - $chunks = array_chunk($matches[0], $len); + $chunks = array_chunk($graphemes, $len); foreach ($chunks as &$chunk) { $chunk = implode('', $chunk); @@ -224,14 +246,20 @@ public static function grapheme_str_split($s, $len = 1) return $chunks; } - public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1) + public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1, $locale = '') { - if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) { - return false; + if ($insertion_cost <= 0 || $insertion_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost) must be greater than 0 and less than or equal to 1073741823'); + } + if ($replacement_cost <= 0 || $replacement_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #4 ($replacement_cost) must be greater than 0 and less than or equal to 1073741823'); + } + if ($deletion_cost <= 0 || $deletion_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #5 ($deletion_cost) must be greater than 0 and less than or equal to 1073741823'); } - if (0 > $insertion_cost || 0 > $replacement_cost || 0 > $deletion_cost) { - throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost), #4 ($replacement_cost), and #5 ($deletion_cost) must be greater than or equal to 0'); + if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) { + return false; } preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s1, $s1); @@ -239,33 +267,58 @@ public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $repl $s1 = $s1[0]; $s2 = $s2[0]; + $l1 = \count($s1); $l2 = \count($s2); - if (0 === $l1) { - return $l2 * $insertion_cost; + // Keep the rows as short as possible. Reversing the transformation + // swaps the meaning of insertion and deletion. + if ($l1 < $l2) { + [$s1, $s2] = [$s2, $s1]; + [$l1, $l2] = [$l2, $l1]; + [$insertion_cost, $deletion_cost] = [$deletion_cost, $insertion_cost]; } + if (0 === $l2) { return $l1 * $deletion_cost; } - $dp = array_fill(0, $l1 + 1, array_fill(0, $l2 + 1, 0)); - - for ($i = 1; $i <= $l1; ++$i) { - $dp[$i][0] = $dp[$i - 1][0] + $deletion_cost; + if (\extension_loaded('intl')) { + // Graphemes are equal when the collator says so, as with ucol_strcoll() in intl + try { + $collator = new \Collator('' === $locale ? 'root' : $locale); + } catch (\IntlException $e) { + return false; + } + $s1 = array_map([$collator, 'getSortKey'], $s1); + $s2 = array_map([$collator, 'getSortKey'], $s2); + } elseif (class_exists(\Normalizer::class)) { + // Without a collator, at least treat canonically equivalent graphemes as equal + foreach ($s1 as $i => $g) { + $s1[$i] = \Normalizer::normalize($g, \Normalizer::FORM_D); + } + foreach ($s2 as $i => $g) { + $s2[$i] = \Normalizer::normalize($g, \Normalizer::FORM_D); + } } + + $previousRow = $currentRow = array_fill(0, $l2 + 1, 0); for ($j = 1; $j <= $l2; ++$j) { - $dp[0][$j] = $dp[0][$j - 1] + $insertion_cost; + $previousRow[$j] = $previousRow[$j - 1] + $insertion_cost; } for ($i = 1; $i <= $l1; ++$i) { + $currentRow[0] = $previousRow[0] + $deletion_cost; + for ($j = 1; $j <= $l2; ++$j) { $cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost; - $dp[$i][$j] = min($dp[$i - 1][$j] + $deletion_cost, $dp[$i][$j - 1] + $insertion_cost, $dp[$i - 1][$j - 1] + $cost); + $currentRow[$j] = min($previousRow[$j] + $deletion_cost, $currentRow[$j - 1] + $insertion_cost, $previousRow[$j - 1] + $cost); } + + [$previousRow, $currentRow] = [$currentRow, $previousRow]; } - return $dp[$l1][$l2]; + return $previousRow[$l2]; } private static function grapheme_position($s, $needle, $offset, $mode) @@ -335,16 +388,69 @@ private static function grapheme_position($s, $needle, $offset, $mode) public static function grapheme_strrev(string $string) { - if (!preg_match('//u', $string)) { - return false; + if (\extension_loaded('intl')) { + $iterator = \IntlBreakIterator::createCharacterInstance(); + $iterator->setText($string); + $reversed = ''; + $end = $iterator->last(); + + while (\IntlBreakIterator::DONE !== $start = $iterator->previous()) { + $reversed .= substr($string, $start, $end - $start); + $end = $start; + } + + return $reversed; } - $units = grapheme_str_split($string); + return implode('', array_reverse(self::grapheme_str_split($string))); + } - if (false === $units) { - return false; + private static function isUtf8(?string $s) + { + $s = $s ?? ''; + + return '' === $s || preg_match('/./us', $s); + } + + /** + * Splits a string that is not valid UTF-8 into grapheme clusters. + * + * The intl extension keeps the ill-formed bytes and breaks around them the + * way it would around the U+FFFD each maximal subpart stands for. Standing + * one in for real makes the cluster regexp applicable, then each cluster is + * mapped back to the bytes it was made of. + */ + private static function splitIllFormed($s) + { + preg_match_all(self::UTF8_CHAR_OR_SUBPART_RX, $s, $matches, \PREG_SET_ORDER); + + $segments = []; + $segmentAt = []; + $sanitized = ''; + + foreach ($matches as $m) { + $segmentAt[\strlen($sanitized)] = \count($segments); + + if (isset($m[2]) && '' !== $m[2]) { + $segments[] = $m[2]; + $sanitized .= "\xEF\xBF\xBD"; + } else { + $segments[] = $m[1]; + $sanitized .= $m[1]; + } + } + + $segmentAt[\strlen($sanitized)] = \count($segments); + + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $sanitized, $clusters, \PREG_OFFSET_CAPTURE); + + $graphemes = []; + + foreach ($clusters[0] as $cluster) { + $start = $segmentAt[$cluster[1]]; + $graphemes[] = implode('', \array_slice($segments, $start, $segmentAt[$cluster[1] + \strlen($cluster[0])] - $start)); } - return implode('', array_reverse($units)); + return $graphemes; } } diff --git a/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap.php b/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap.php index 4fd87741..d6795031 100644 --- a/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap.php +++ b/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap.php @@ -62,7 +62,7 @@ function grapheme_substr($string, $offset, $length = null) { return p\Grapheme:: function grapheme_str_split(string $string, int $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); } } if (!function_exists('grapheme_levenshtein')) { - function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); } + function grapheme_levenshtein(string $string1, string $string2, ?int $insertion_cost = 1, ?int $replacement_cost = 1, ?int $deletion_cost = 1, string $locale = '') { return p\Grapheme::grapheme_levenshtein($string1, $string2, (int) $insertion_cost, (int) $replacement_cost, (int) $deletion_cost, $locale); } } if (!function_exists('grapheme_strrev')) { function grapheme_strrev(string $string) { return p\Grapheme::grapheme_strrev($string); } diff --git a/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php b/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php index b05cfde5..c913b00b 100644 --- a/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php +++ b/upload/api/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -15,7 +15,7 @@ function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); } } if (!function_exists('grapheme_levenshtein')) { - function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ''): int|false { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); } + function grapheme_levenshtein(string $string1, string $string2, ?int $insertion_cost = 1, ?int $replacement_cost = 1, ?int $deletion_cost = 1, string $locale = ''): int|false { return p\Grapheme::grapheme_levenshtein($string1, $string2, (int) $insertion_cost, (int) $replacement_cost, (int) $deletion_cost, $locale); } } if (!function_exists('grapheme_strrev')) { function grapheme_strrev(string $string): string|false { return p\Grapheme::grapheme_strrev($string); } diff --git a/upload/api/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/upload/api/vendor/symfony/polyfill-intl-normalizer/Normalizer.php index d8c71b87..3a3312d8 100644 --- a/upload/api/vendor/symfony/polyfill-intl-normalizer/Normalizer.php +++ b/upload/api/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -41,29 +41,33 @@ class Normalizer private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; - public static function isNormalized(string $s, int $form = self::FORM_C) + public static function isNormalized(string $string, int $form = self::FORM_C) { if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_is_normalized(): Argument #2 ($form) must be a '.(80600 > \PHP_VERSION_ID ? 'a ' : '').'valid normalization form'); } - if (!isset($s[strspn($s, self::$ASCII)])) { + if (!isset($string[strspn($string, self::$ASCII)])) { return true; } - if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + if (self::NFC == $form && preg_match('//u', $string) && !preg_match('/[^\x00-\x{2FF}]/u', $string)) { return true; } - return self::normalize($s, $form) === $s; + return self::normalize($string, $form) === $string; } - public static function getRawDecomposition(string $s, int $form = self::FORM_C) + public static function getRawDecomposition(string $string, int $form = self::FORM_C) { - if ('' === $s || !preg_match('//u', $s)) { + if ('' === $string || !preg_match('//u', $string)) { return null; } - $ulen = $s[0] < "\x80" ? 1 : (self::$ulenMask[$s[0] & "\xF0"] ?? 0); - if (!$ulen || \strlen($s) !== $ulen) { + $ulen = $string[0] < "\x80" ? 1 : (self::$ulenMask[$string[0] & "\xF0"] ?? 0); + if (!$ulen || \strlen($string) !== $ulen) { return null; } @@ -71,8 +75,8 @@ public static function getRawDecomposition(string $s, int $form = self::FORM_C) return ''; } - if ($s >= "\xEA\xB0\x80" && $s <= "\xED\x9E\xA3") { - $u = unpack('C*', $s); + if ($string >= "\xEA\xB0\x80" && $string <= "\xED\x9E\xA3") { + $u = unpack('C*', $string); $j = (($u[1] - 224) << 12) + (($u[2] - 128) << 6) + $u[3] - 0xAC80; if ($t = $j % 28) { @@ -94,8 +98,8 @@ public static function getRawDecomposition(string $s, int $form = self::FORM_C) self::$rawD = self::getData('rawCanonicalDecomposition'); } - if (isset(self::$rawD[$s])) { - return self::$rawD[$s]; + if (isset(self::$rawD[$string])) { + return self::$rawD[$string]; } if (self::NFKC === $form || self::NFKD === $form) { @@ -103,18 +107,14 @@ public static function getRawDecomposition(string $s, int $form = self::FORM_C) self::$rawKD = self::getData('rawCompatibilityDecomposition'); } - return self::$rawKD[$s] ?? null; + return self::$rawKD[$string] ?? null; } return null; } - public static function normalize(string $s, int $form = self::FORM_C) + public static function normalize(string $string, int $form = self::FORM_C) { - if (!preg_match('//u', $s)) { - return false; - } - switch ($form) { case self::NFC: $C = true; $K = false; break; case self::NFD: $C = false; $K = false; break; @@ -122,7 +122,7 @@ public static function normalize(string $s, int $form = self::FORM_C) case self::NFKD: $C = false; $K = true; break; default: if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { - return $s; + return preg_match('//u', $string) ? $string : false; } if (80000 > \PHP_VERSION_ID) { @@ -133,7 +133,11 @@ public static function normalize(string $s, int $form = self::FORM_C) throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a '.(80600 > \PHP_VERSION_ID ? 'a ' : '').'valid normalization form'); } - if ('' === $s) { + if (!preg_match('//u', $string)) { + return false; + } + + if ('' === $string) { return ''; } @@ -150,7 +154,7 @@ public static function normalize(string $s, int $form = self::FORM_C) mb_internal_encoding('8bit'); } - $r = self::decompose($s, $K); + $r = self::decompose($string, $K); if ($C) { if (null === self::$C) { diff --git a/upload/api/vendor/symfony/polyfill-mbstring/Mbstring.php b/upload/api/vendor/symfony/polyfill-mbstring/Mbstring.php index e3d1f965..bc9e6acf 100644 --- a/upload/api/vendor/symfony/polyfill-mbstring/Mbstring.php +++ b/upload/api/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -79,20 +79,58 @@ final class Mbstring ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], ]; + // Encodings rejected by mb_ord() and mb_chr(), with their aliases + private const UNSUPPORTED_CODEPOINT_ENCODINGS = [ + 'BASE64' => 'BASE64', + 'UUENCODE' => 'UUENCODE', + 'X-UUENCODE' => 'UUENCODE', + 'HTML-ENTITIES' => 'HTML-ENTITIES', + 'HTML' => 'HTML-ENTITIES', + 'QUOTED-PRINTABLE' => 'Quoted-Printable', + 'QPRINT' => 'Quoted-Printable', + 'UTF-7' => 'UTF-7', + 'UTF7' => 'UTF-7', + 'UTF7-IMAP' => 'UTF7-IMAP', + 'MUTF-7' => 'UTF7-IMAP', + 'JIS' => 'JIS', + 'ISO-2022-JP' => 'ISO-2022-JP', + 'ISO-2022-JP-2004' => 'ISO-2022-JP-2004', + 'ISO-2022-JP-MOBILE#KDDI' => 'ISO-2022-JP-MOBILE#KDDI', + 'ISO-2022-JP-KDDI' => 'ISO-2022-JP-MOBILE#KDDI', + 'ISO-2022-JP-MS' => 'ISO-2022-JP-MS', + 'ISO2022JPMS' => 'ISO-2022-JP-MS', + 'CP50220' => 'CP50220', + 'CP50220RAW' => 'CP50220', + 'CP50220-RAW' => 'CP50220', + 'JIS-MS' => 'CP50220', + 'CP50221' => 'CP50221', + 'CP50222' => 'CP50222', + ]; + + // Well-formed characters in group 1, then the maximal subparts of ill-formed sequences + private const UTF8_CHAR_REGEX = '/([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|\xE0[\xA0-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]|\xED[\x80-\x9F]|\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|[\x80-\xFF]/'; + private static $encodingList = ['ASCII', 'UTF-8']; private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; private static $iconvSupportsIgnore; + private static $convertibleEncodings = ['UTF-8' => true]; public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { - if (\is_array($s)) { - $r = []; - foreach ($s as $str) { - $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); - } + if (\is_string($fromEncoding) && 2 < \strlen($fromEncoding) && '"' === $fromEncoding[0] && '"' === $fromEncoding[-1]) { + $fromEncoding = substr($fromEncoding, 1, -1); + } - return $r; + if (80000 <= \PHP_VERSION_ID + && (!isset(self::$convertibleEncodings[$toEncoding]) || (null !== $fromEncoding && !(\is_string($fromEncoding) && isset(self::$convertibleEncodings[$fromEncoding])))) + && null !== $error = self::getConversionError('mb_convert_encoding', 2, $toEncoding, $fromEncoding) + ) { + throw new \ValueError($error); + } + + if (\is_array($s)) { + return self::convertArray($s, $toEncoding, $fromEncoding); } if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { @@ -118,6 +156,8 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null } if ('UTF-8' !== $fromEncoding) { $s = self::iconv($fromEncoding, 'UTF-8', $s); + } elseif (!preg_match('//u', $s)) { + $s = self::scrubUtf8($s); } return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); @@ -157,17 +197,42 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null $fromEncoding = 'UTF-8'; } + if ('UTF-8' === $fromEncoding && !preg_match('//u', $s)) { + $s = self::scrubUtf8($s); + } + return self::iconv($fromEncoding, $toEncoding, $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { + if (\is_string($fromEncoding) && 2 < \strlen($fromEncoding) && '"' === $fromEncoding[0] && '"' === $fromEncoding[-1]) { + $fromEncoding = substr($fromEncoding, 1, -1); + } + + if (80000 <= \PHP_VERSION_ID && null !== $error = self::getConversionError('mb_convert_variables', 1, $toEncoding, $fromEncoding)) { + throw new \ValueError($error); + } + $ok = true; - array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) { - if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { - $ok = false; + foreach ($vars as $i => &$var) { + $convert = static function (&$v) use (&$ok, $toEncoding, $fromEncoding, $i) { + if (\is_string($v)) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + } elseif (80600 <= \PHP_VERSION_ID && !\is_object($v)) { + trigger_error(\sprintf('mb_convert_variables(): Argument #%d must be of type string|array|object or only contain entries of type string|array|object, %s given', 3 + $i, strtok(get_debug_type($v), ' ')), \E_USER_WARNING); + } + }; + + if (\is_array($var)) { + array_walk_recursive($var, $convert); + } else { + $convert($var); } - }); + } + unset($var); return $ok ? $fromEncoding : false; } @@ -210,7 +275,7 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @self::iconv('UTF-8', 'UTF-8', $s); + $s = self::scrubUtf8($s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); @@ -276,7 +341,7 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @self::iconv('UTF-8', 'UTF-8', $s); + $s = self::scrubUtf8($s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); @@ -290,10 +355,15 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ $result = ''; while ($i < $len) { - $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"] ?? 1; $uchr = substr($s, $i, $ulen); $i += $ulen; - $c = self::mb_ord($uchr); + + // code points above U+10FFFF are outside any convmap, but mb_ord() rejects them + if (false === $c = self::mb_ord($uchr)) { + $result .= $uchr; + continue; + } for ($j = 0; $j < $cnt; $j += 4) { if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { @@ -324,7 +394,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @self::iconv('UTF-8', 'UTF-8', $s); + $s = self::scrubUtf8($s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); @@ -365,7 +435,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) $len = \strlen($s); while ($i < $len) { - $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"] ?? 1; $uchr = substr($s, $i, $ulen); $i += $ulen; @@ -462,6 +532,10 @@ public static function mb_check_encoding($var = null, $encoding = null) } if (!\is_array($var)) { + if ('UTF-8' === self::getEncoding($encoding)) { + return (bool) preg_match('//u', $var); + } + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); } @@ -562,50 +636,16 @@ public static function mb_strlen($s, $encoding = null) public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strpos($haystack, $needle, $offset); - } - - $needle = (string) $needle; - if ('' === $needle) { - if (80000 > \PHP_VERSION_ID) { - trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); - - return false; - } - - return 0; - } - - return iconv_strpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, false, false); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strrpos($haystack, $needle, $offset); - } - if ($offset != (int) $offset) { $offset = 0; - } elseif ($offset = (int) $offset) { - if ($offset < 0) { - if (0 > $offset += self::mb_strlen($needle)) { - $haystack = self::mb_substr($haystack, 0, $offset, $encoding); - } - $offset = 0; - } else { - $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); - } } - $pos = '' !== $needle || 80000 > \PHP_VERSION_ID - ? iconv_strrpos($haystack, $needle, $encoding) - : self::mb_strlen($haystack, $encoding); - - return false !== $pos ? $offset + $pos : false; + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, true, false); } public static function mb_str_split($string, $split_length = 1, $encoding = null) @@ -626,17 +666,27 @@ public static function mb_str_split($string, $split_length = 1, $encoding = null throw new \ValueError('Argument #2 ($length) must be greater than 0'); } + if (80300 <= \PHP_VERSION_ID && 0x3FFFFFFF < $split_length) { + throw new \ValueError('Argument #2 ($length) is too large'); + } + if (null === $encoding) { $encoding = mb_internal_encoding(); } if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $string = (string) $string; + + if (\strlen($string) <= $split_length) { + return '' === $string ? [] : [$string]; + } + $rx = '/('; while (65535 < $split_length) { $rx .= '.{65535}'; $split_length -= 65535; } - $rx .= '.{'.$split_length.'})/us'; + $rx .= '.{'.$split_length.'})/Aus'; return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } @@ -707,64 +757,32 @@ public static function mb_substr($s, $start, $length = null, $encoding = null) public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { - [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ - self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), - self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), - ]); - - return self::mb_strpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, false, true); } public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) { - $pos = self::mb_stripos($haystack, $needle, 0, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, false, true); } public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - $pos = strrpos($haystack, $needle); - } else { - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = iconv_strrpos($haystack, $needle, $encoding); - } - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, true, false); } public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) { - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = self::mb_strripos($haystack, $needle, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, true, true); } public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { - $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); - $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); - - $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); - $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); - - return self::mb_strrpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, true, true); } public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) { - $pos = strpos($haystack, $needle); - if (false === $pos) { - return false; - } - if ($part) { - return substr($haystack, 0, $pos); - } - - return substr($haystack, $pos); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, false, false); } public static function mb_get_info($type = 'all') @@ -809,19 +827,37 @@ public static function mb_http_output($encoding = null) public static function mb_strwidth($s, $encoding = null) { $encoding = self::getEncoding($encoding); + $wideChars = '/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u'; if ('UTF-8' !== $encoding) { $s = self::iconv($encoding, 'UTF-8', $s); + } elseif (!preg_match('//u', $s)) { + // Each maximal subpart of an ill-formed sequence is one column wide + return preg_match_all(self::UTF8_CHAR_REGEX, $s) + preg_match_all($wideChars, self::scrubUtf8($s)); } - $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + $s = preg_replace($wideChars, '', $s, -1, $wide); return ($wide << 1) + iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) { - return substr_count($haystack, $needle); + if ('' === $needle = (string) $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('mb_substr_count(): Empty substring', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_substr_count(): Argument #2 ($needle) must not be empty'); + } + + if (!$search = self::prepareSearch((string) $haystack, $needle, self::getEncoding($encoding), false, true)) { + return 0; + } + + return substr_count($search[0], $search[1]); } public static function mb_output_handler($contents, $status) @@ -831,6 +867,10 @@ public static function mb_output_handler($contents, $status) public static function mb_chr($code, $encoding = null) { + if (null !== $encoding && !self::assertCodepointEncoding($encoding, 'mb_chr')) { + return false; + } + if (0x80 > $code %= 0x200000) { $s = \chr($code); } elseif (0x800 > $code) { @@ -850,26 +890,64 @@ public static function mb_chr($code, $encoding = null) public static function mb_ord($s, $encoding = null) { + if ('' === (string) $s) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('mb_ord(): Empty string', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_ord(): Argument #1 ($string) must not be empty'); + } + + if (null !== $encoding && !self::assertCodepointEncoding($encoding, 'mb_ord')) { + return false; + } + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, 'UTF-8', $encoding); } - if (1 === \strlen($s)) { - return \ord($s); + $s = unpack('C*', substr($s, 0, 4)); + $code = $s[1] ?? 0; + if (0x80 > $code) { + return $code; } - $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; - if (0xF0 <= $code) { - return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + if (0xC2 > $code || 0xF4 < $code) { + return false; } - if (0xE0 <= $code) { - return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + + $c2 = $s[2] ?? 0; + if (0x80 !== ($c2 & 0xC0)) { + return false; } - if (0xC0 <= $code) { - return (($code - 0xC0) << 6) + $s[2] - 0x80; + + if (0xE0 > $code) { + return (($code & 0x1F) << 6) | ($c2 & 0x3F); } - return $code; + $c3 = $s[3] ?? 0; + if (0x80 !== ($c3 & 0xC0) + || 0xE0 === $code && 0xA0 > $c2 + || 0xED === $code && 0xA0 <= $c2 + ) { + return false; + } + + if (0xF0 > $code) { + return (($code & 0x0F) << 12) | (($c2 & 0x3F) << 6) | ($c3 & 0x3F); + } + + $c4 = $s[4] ?? 0; + if (0x80 !== ($c4 & 0xC0) + || 0xF0 === $code && 0x90 > $c2 + || 0xF4 === $code && 0x90 <= $c2 + ) { + return false; + } + + return (($code & 0x07) << 18) | (($c2 & 0x3F) << 12) | (($c3 & 0x3F) << 6) | ($c4 & 0x3F); } /** @return string|false */ @@ -893,7 +971,7 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin return false; } - if (self::mb_strlen($pad_string, $encoding) <= 0) { + if (0 >= $padStringLength = self::mb_strlen($pad_string, $encoding)) { if (\PHP_VERSION_ID < 80000) { trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); @@ -921,15 +999,20 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin switch ($pad_type) { case \STR_PAD_LEFT: - return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + $leftPaddingLength = $paddingRequired; + break; case \STR_PAD_RIGHT: - return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + $leftPaddingLength = 0; + break; default: - $leftPaddingLength = floor($paddingRequired / 2); - $rightPaddingLength = $paddingRequired - $leftPaddingLength; - - return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + $leftPaddingLength = intdiv($paddingRequired, 2); } + + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return str_repeat($pad_string, intdiv($leftPaddingLength, $padStringLength)).self::mb_substr($pad_string, 0, $leftPaddingLength % $padStringLength, $encoding) + .$string + .str_repeat($pad_string, intdiv($rightPaddingLength, $padStringLength)).self::mb_substr($pad_string, 0, $rightPaddingLength % $padStringLength, $encoding); } /** @return string|false */ @@ -980,16 +1063,288 @@ public static function mb_rtrim(string $string, ?string $characters = null, ?str return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); } - private static function getSubpart($pos, $part, $haystack, $encoding) + /** + * Finds the position of $needle in $haystack like native mbstring. + * + * @return int|false + */ + private static function find(string $function, $haystack, $needle, $offset, $encoding, bool $reverse, bool $fold) + { + $needle = (string) $needle; + + if (80000 > \PHP_VERSION_ID && '' === $needle && 'mb_stripos' === $function) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + + return false; + } + + if (!$search = self::prepareSearch((string) $haystack, $needle, self::searchEncoding($encoding, $fold), $fold, $fold)) { + return false; + } + + [$haystack, $needle, $bytes, $dropped] = $search; + + if (80000 > \PHP_VERSION_ID && $fold && ('' === $haystack || '' === $needle)) { + return false; + } + + if (false === $start = self::utf8Offset($haystack, $offset = (int) $offset, $bytes)) { + // Native mbstring counts the ill-formed sequences that iconv() dropped: the offset could be in range + if ($dropped && abs($offset) <= self::utf8Length($haystack) + $dropped) { + return false; + } + + if (80000 > \PHP_VERSION_ID) { + trigger_error($function.'(): '.($reverse ? 'Offset is greater than the length of haystack string' : 'Offset not contained in string'), \E_USER_WARNING); + + return false; + } + + throw new \ValueError($function.'(): Argument #3 ($offset) must be contained in argument #1 ($haystack)'); + } + + if (80000 > \PHP_VERSION_ID && '' === $needle) { + if ('mb_strpos' === $function) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + } + + return false; + } + + if (!$reverse) { + $pos = strpos($haystack, $needle, $start); + } else { + $pos = strrpos($haystack, $needle, 0 > $offset ? $start - \strlen($haystack) : $start); + } + + return false === $pos || $bytes ? $pos : self::utf8Length(substr($haystack, 0, $pos)); + } + + /** + * Returns the part of $haystack before or from $needle like native mbstring. + * + * @return string|false + */ + private static function findPart(string $function, $haystack, $needle, $part, $encoding, bool $reverse, bool $fold) + { + $needle = (string) $needle; + + if (80000 > \PHP_VERSION_ID && '' === $needle) { + if (!$reverse) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + } + + return false; + } + + $haystack = (string) $haystack; + + if (!$search = self::prepareSearch($haystack, $needle, $encoding = self::searchEncoding($encoding, $fold), $fold, $fold)) { + return false; + } + + [$h, $n, $bytes] = $search; + + if (false === $pos = $reverse ? strrpos($h, $n) : strpos($h, $n)) { + return false; + } + + if ($bytes) { + return (string) ($part ? substr($haystack, 0, $pos) : substr($haystack, $pos)); + } + + if ($fold || ('UTF-8' === $encoding && (!preg_match('//u', $haystack) || !preg_match('//u', $needle)))) { + // Cut after as many characters as native mbstring counts before the match + $pos = self::utf8Length(substr($h, 0, $pos)); + $h = 'UTF-8' === $encoding ? self::markIllFormedUtf8($haystack) : self::utf8($haystack, $encoding); + $pos = self::utf8Offset($h, $pos, false); + } + + $h = (string) ($part ? substr($h, 0, $pos) : substr($h, $pos)); + + if (false !== strpos($h, "\xFF")) { + $h = str_replace("\xFF", 'none' === mb_substitute_character() ? '' : '?', $h); + } + + return 'UTF-8' === $encoding ? $h : (string) @self::iconv('UTF-8', $encoding, $h); + } + + /** + * @return array{string, string, bool, int}|null The haystack and the needle to search, whether offsets in them count bytes rather than UTF-8 characters, and how many bytes of ill-formed sequences iconv() dropped from the haystack + */ + private static function prepareSearch(string $haystack, string $needle, string $encoding, bool $fold, bool $markIllFormed) + { + if ('ASCII' === $encoding || ('CP850' === $encoding && !$fold)) { + if ('ASCII' === $encoding) { + // Native mbstring turns every byte above 0x7F into the same error marker + $haystack = preg_replace('/[\x80-\xFF]/', "\xFF", $haystack); + $needle = preg_replace('/[\x80-\xFF]/', "\xFF", $needle); + } + + if ($fold) { + $haystack = strtr($haystack, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + $needle = strtr($needle, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + return [$haystack, $needle, true, 0]; + } + + $dropped = 0; + + if ('UTF-8' !== $encoding) { + if (false === ($haystack = self::utf8($haystack, $encoding, $dropped)) || false === $needle = self::utf8($needle, $encoding)) { + return null; + } + } elseif ($markIllFormed) { + $haystack = self::markIllFormedUtf8($haystack); + $needle = self::markIllFormedUtf8($needle); + } + + return $fold ? [self::foldCase($haystack), self::foldCase($needle), false, $dropped] : [$haystack, $needle, false, $dropped]; + } + + private static function searchEncoding($encoding, bool $fold): string + { + $normalizedEncoding = self::getEncoding($encoding); + + // Native mbstring folds the case of 8bit strings as Latin-1 + return $fold && 'CP850' === $normalizedEncoding && null !== $encoding && 'CP850' !== strtoupper($encoding) ? 'ISO-8859-1' : $normalizedEncoding; + } + + /** + * Converts $s to UTF-8 to search it, ending with "\xFF", a byte that is invalid in UTF-8, when its last sequence is truncated, like native mbstring. + * + * @param int $dropped Set to the number of bytes of the ill-formed sequences that iconv() dropped elsewhere + * + * @return string|false False when the encoding is unknown + */ + private static function utf8(string $s, string $encoding, &$dropped = 0) { - if (false === $pos) { + if ('UTF-8' === $encoding || '' === $s) { + return $s; + } + + if (false !== $u = @iconv($encoding, 'UTF-8', $s)) { + return $u; + } + + // Let iconv() warn about an unknown encoding + if (false === iconv($encoding, 'UTF-8', '')) { return false; } - if ($part) { - return self::mb_substr($haystack, 0, $pos, $encoding); + + // iconv() drops the ill-formed sequences, but fails on a truncated one at the end + for ($i = 0; $i < 4; ++$i) { + if (false !== $u = @self::iconv($encoding, 'UTF-8', $i ? substr($s, 0, -$i) : $s)) { + $dropped = \strlen($s) - $i - \strlen((string) @iconv('UTF-8', $encoding, $u)); + + return $i || '' === $u ? $u."\xFF" : $u; + } + } + + $dropped = \strlen($s); + + return "\xFF"; + } + + /** + * Replaces each maximal subpart of ill-formed UTF-8 sequences with "\xFF", like native mbstring before searching case-insensitively. + */ + private static function markIllFormedUtf8(string $s): string + { + if (preg_match('//u', $s)) { + return $s; + } + + return preg_replace('/\G(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*+\K(?:\xE0[\xA0-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]|\xED[\x80-\x9F]|\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|[\x80-\xFF])/', "\xFF", $s); + } + + /** + * Folds the case of UTF-8 $s like the simple case folding of native mbstring. + */ + private static function foldCase(string $s): string + { + static $map = null; + + if (null === $map) { + $map = array_combine(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1]) + self::getData('lowerCase'); + unset($map["\xC4\xB0"]); // U+0130 has no simple case folding + } + + return strtr($s, $map); + } + + /** + * Counts the characters of UTF-8 $s like native mbstring, even when ill-formed: one per byte that is not a continuation byte. + */ + private static function utf8Length(string $s): int + { + return \strlen($s) - array_sum(\array_slice(count_chars($s), 0x80, 0x40)); + } + + /** + * @return int|false The offset in bytes of the character at $offset, false when it is out of range, like native mbstring + */ + private static function utf8Offset(string $s, int $offset, bool $bytes) + { + if (0 === $offset) { + return 0; + } + + if ($bytes || preg_match('//u', $u = str_replace("\xFF", '?', $s))) { + $length = $bytes ? \strlen($s) : self::utf8Length($s); + + if (0 > $offset) { + $offset += $length; + } + + if (0 > $offset || $offset > $length) { + return false; + } + + if ($bytes) { + return $offset; + } + + for ($rx = ''; 65535 < $offset; $offset -= 65535) { + $rx .= '.{65535}'; + } + preg_match('/^'.$rx.'.{'.$offset.'}\K/su', $u, $m, \PREG_OFFSET_CAPTURE); + + return $m[0][1]; } - return self::mb_substr($haystack, $pos, null, $encoding); + // Walk ill-formed UTF-8 like native mbstring: backward over the bytes that are not continuation bytes, forward by the lengths that lead bytes announce + $length = \strlen($s); + + if (0 > $offset) { + $i = $length; + + while (0 > $offset) { + if (0 === $i) { + return false; + } + + if (0x80 !== (\ord($s[--$i]) & 0xC0)) { + ++$offset; + } + } + + return $i; + } + + $i = 0; + + while (0 < $offset--) { + if ($i >= $length) { + return false; + } + + $c = \ord($s[$i]); + $i += $c < 0xC2 ? 1 : ($c < 0xE0 ? 2 : ($c < 0xF0 ? 3 : ($c < 0xF5 ? 4 : 1))); + } + + return min($i, $length); } private static function html_encoding_callback(array $m) @@ -1083,16 +1438,13 @@ private static function mb_internal_trim(string $regex, string $string, ?string } if ('' === $characters) { - return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); + return $string; } - if ('UTF-8' === $encoding) { + if ('UTF-8' === self::getEncoding($encoding)) { $encoding = null; - if (!preg_match('//u', $string)) { - $string = @self::iconv('UTF-8', 'UTF-8', $string); - } - if (null !== $characters && !preg_match('//u', $characters)) { - $characters = @self::iconv('UTF-8', 'UTF-8', $characters); + if (!preg_match('//u', $string) || (null !== $characters && !preg_match('//u', $characters))) { + return self::mb_trim_invalid_utf8($string, $characters, $function); } } else { $string = self::iconv($encoding, 'UTF-8', $string); @@ -1117,6 +1469,139 @@ private static function mb_internal_trim(string $regex, string $string, ?string return self::iconv('UTF-8', $encoding, $string); } + /** + * Trims ill-formed UTF-8 like mbstring: each maximal subpart of an ill-formed + * sequence counts as one character, and the string is returned as is when + * nothing is trimmed, else re-encoded with the substitute character. + */ + private static function mb_trim_invalid_utf8(string $string, ?string $characters, string $function): string + { + $regex = self::UTF8_CHAR_REGEX; + + // Ill-formed sequences leave group 1 empty: they are trimmed when $characters has one too + preg_match_all($regex, $characters ?? "\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", $m); + $trimmed = array_flip($m[1]); + + $start = 0; + $end = \strlen($string); + + while ('mb_rtrim' !== $function && $start < $end && preg_match($regex, $string, $m, 0, $start) && isset($trimmed[$m[1] ?? ''])) { + $start += \strlen($m[0]); + } + + while ('mb_ltrim' !== $function && $start < $end) { + // Characters are at most 4 bytes long, and only continuation bytes don't start one + for ($i = $end - 1; $i > $start && $i > $end - 4 && 0x80 === (\ord($string[$i]) & 0xC0); --$i) { + } + + preg_match_all($regex, substr($string, $i, $end - $i), $m); + + if (!isset($trimmed[end($m[1])])) { + break; + } + + $end -= \strlen(end($m[0])); + } + + if (0 === $start && \strlen($string) === $end) { + return $string; + } + + $string = substr($string, $start, $end - $start); + + return 'none' === mb_substitute_character() ? self::scrubUtf8($string) : mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + } + + private static function scrubUtf8(string $s): string + { + return preg_replace(self::UTF8_CHAR_REGEX, '$1', $s); + } + + /** + * @param true[] $referenceIds The ids of the references followed to reach $array + */ + private static function convertArray(array $array, $toEncoding, $fromEncoding, array $referenceIds = []): array + { + $r = []; + + foreach ($array as $key => $v) { + if (null !== $v && !\is_scalar($v) && !\is_array($v)) { + trigger_error('mb_convert_encoding(): Object is not supported', \E_USER_WARNING); + continue; + } + + $k = \is_string($key) ? self::mb_convert_encoding($key, $toEncoding, $fromEncoding) : $key; + + if (\is_string($v)) { + $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding); + } elseif (\is_array($v)) { + // Only a reference can close a cycle: following the same one twice means one was found + $id = 70400 <= \PHP_VERSION_ID && ($reference = \ReflectionReference::fromArrayElement($array, $key)) ? $reference->getId() : null; + + if (null !== $id && isset($referenceIds[$id])) { + trigger_error('mb_convert_encoding(): Cannot convert recursively referenced values', \E_USER_WARNING); + $v = []; + } else { + $v = self::convertArray($v, $toEncoding, $fromEncoding, null === $id ? $referenceIds : $referenceIds + [$id => true]); + } + } + + // Keys that collide once converted keep their first value + $r += [$k => $v]; + } + + return $r; + } + + /** + * Returns the error native mbstring throws for these encoding arguments, if any. + */ + private static function getConversionError(string $function, int $argument, $toEncoding, $fromEncoding): ?string + { + if (!self::isConvertibleEncoding((string) $toEncoding)) { + return \sprintf('%s(): Argument #%d ($to_encoding) must be a valid encoding, "%s" given', $function, $argument, $toEncoding); + } + + if (null === $fromEncoding) { + return null; + } + + if (!$list = \is_array($fromEncoding) ? $fromEncoding : ('' === $fromEncoding ? [] : explode(',', $fromEncoding))) { + return \sprintf('%s(): Argument #%d ($from_encoding) must specify at least one encoding', $function, 1 + $argument); + } + + foreach ($list as $encoding) { + $encoding = \is_array($fromEncoding) ? (string) $encoding : trim($encoding, " \t"); + + // PHP 8.4 takes an empty entry of a comma-separated list for "auto" + if ('' === $encoding && !\is_array($fromEncoding) && 80400 <= \PHP_VERSION_ID) { + continue; + } + + if (0 !== strcasecmp($encoding, 'auto') && !self::isConvertibleEncoding($encoding)) { + return \sprintf('%s(): Argument #%d ($from_encoding) contains invalid encoding "%s"', $function, 1 + $argument, $encoding); + } + } + + return null; + } + + private static function isConvertibleEncoding(string $encoding): bool + { + if (isset(self::$convertibleEncodings[$encoding])) { + return true; + } + + $normalizedEncoding = self::getEncoding($encoding); + + // Native encoding names use no other characters, while iconv() also accepts suffixes like //TRANSLIT + if ('' === $normalizedEncoding || preg_match('/[^\w#\-.:]/', $encoding) || (null === (self::UNSUPPORTED_CODEPOINT_ENCODINGS[$normalizedEncoding] ?? null) && false === @iconv($normalizedEncoding, $normalizedEncoding, ''))) { + return false; + } + + return self::$convertibleEncodings[$encoding] = true; + } + private static function assertEncoding(string $encoding, string $errorFormat): bool { try { @@ -1135,4 +1620,31 @@ private static function assertEncoding(string $encoding, string $errorFormat): b return $validEncoding; } + + private static function assertCodepointEncoding(string $encoding, string $function): bool + { + if (null !== $name = self::UNSUPPORTED_CODEPOINT_ENCODINGS[strtoupper($encoding)] ?? null) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('%s(): Unsupported encoding "%s"', $function, $name), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('%s() does not support the "%s" encoding', $function, $name)); + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, '')) { + return true; + } + + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('%s(): Unknown encoding "%s"', $function, $encoding), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('%s(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); + } } diff --git a/upload/api/vendor/symfony/type-info/Type.php b/upload/api/vendor/symfony/type-info/Type.php index 43f1771c..175cd66a 100644 --- a/upload/api/vendor/symfony/type-info/Type.php +++ b/upload/api/vendor/symfony/type-info/Type.php @@ -84,7 +84,7 @@ public function accepts(mixed $value): bool } /** - * Traverses the whole type tree. + * Traverses the type and its direct composed or wrapped parts. * * @return iterable */ diff --git a/upload/api/vendor/symfony/type-info/Type/ArrayShapeType.php b/upload/api/vendor/symfony/type-info/Type/ArrayShapeType.php index a5277e2f..2a3d5eea 100644 --- a/upload/api/vendor/symfony/type-info/Type/ArrayShapeType.php +++ b/upload/api/vendor/symfony/type-info/Type/ArrayShapeType.php @@ -125,7 +125,7 @@ public function __toString(): string $items = []; foreach ($this->shape as $key => $value) { - $itemKey = \is_int($key) ? (string) $key : \sprintf("'%s'", $key); + $itemKey = \is_int($key) ? (string) $key : \sprintf("'%s'", addcslashes($key, "'\\")); if ($value['optional'] ?? false) { $itemKey = \sprintf('%s?', $itemKey); } diff --git a/upload/api/vendor/symfony/type-info/Type/IntersectionType.php b/upload/api/vendor/symfony/type-info/Type/IntersectionType.php index c8f011da..bb23c52f 100644 --- a/upload/api/vendor/symfony/type-info/Type/IntersectionType.php +++ b/upload/api/vendor/symfony/type-info/Type/IntersectionType.php @@ -75,6 +75,17 @@ public function composedTypesAreSatisfiedBy(callable $specification): bool return true; } + public function accepts(mixed $value): bool + { + foreach ($this->types as $type) { + if (!$type->accepts($value)) { + return false; + } + } + + return true; + } + public function __toString(): string { $string = ''; diff --git a/upload/api/vendor/symfony/type-info/Type/UnionType.php b/upload/api/vendor/symfony/type-info/Type/UnionType.php index bf74c7e6..3a167f59 100644 --- a/upload/api/vendor/symfony/type-info/Type/UnionType.php +++ b/upload/api/vendor/symfony/type-info/Type/UnionType.php @@ -95,6 +95,17 @@ public function composedTypesAreSatisfiedBy(callable $specification): bool return false; } + public function accepts(mixed $value): bool + { + foreach ($this->types as $type) { + if ($type->accepts($value)) { + return true; + } + } + + return false; + } + public function __toString(): string { $string = ''; diff --git a/upload/api/vendor/symfony/type-info/TypeContext/TypeContext.php b/upload/api/vendor/symfony/type-info/TypeContext/TypeContext.php index 9a044df0..e7018394 100644 --- a/upload/api/vendor/symfony/type-info/TypeContext/TypeContext.php +++ b/upload/api/vendor/symfony/type-info/TypeContext/TypeContext.php @@ -65,7 +65,7 @@ public function normalize(string $name): string return \sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); } - if (null !== $this->namespace) { + if ($this->namespace) { return \sprintf('%s\\%s', $this->namespace, $name); } diff --git a/upload/api/vendor/symfony/type-info/TypeContext/TypeContextFactory.php b/upload/api/vendor/symfony/type-info/TypeContext/TypeContextFactory.php index 070a1cf4..b66021a7 100644 --- a/upload/api/vendor/symfony/type-info/TypeContext/TypeContextFactory.php +++ b/upload/api/vendor/symfony/type-info/TypeContext/TypeContextFactory.php @@ -52,6 +52,11 @@ final class TypeContextFactory */ private array $typeContextCache = []; + /** + * @var array + */ + private array $collectingTypeAliases = []; + private ?Lexer $phpstanLexer = null; private ?PhpDocParser $phpstanParser = null; @@ -290,7 +295,17 @@ private function collectTypeAliases(\ReflectionClass $reflection, TypeContext $t throw new LogicException(\sprintf('Type alias "%s" is not imported from a valid class name.', $tag->value->importedAlias)); } - $importedFromContext = $this->createFromClassName($importedFromType->getClassName()); + if (isset($this->collectingTypeAliases[$importedFromType->getClassName()])) { + throw new LogicException(\sprintf('Cannot import "%s" type alias from "%s" as it is recursive.', $tag->value->importedAlias, $importedFromType->getClassName())); + } + + $this->collectingTypeAliases[$typeContext->declaringClassName] = true; + + try { + $importedFromContext = $this->createFromClassName($importedFromType->getClassName()); + } finally { + unset($this->collectingTypeAliases[$typeContext->declaringClassName]); + } $typeAlias = $importedFromContext->typeAliases[$tag->value->importedAlias] ?? null; if (!$typeAlias) { @@ -366,7 +381,7 @@ private function getPhpDocNode(string $rawDocNode): PhpDocNode $this->phpstanParser ??= new PhpDocParser($config, new TypeParser($config, new ConstExprParser($config)), new ConstExprParser($config)); } else { $this->phpstanLexer ??= new Lexer(); - $this->phpstanParser ??= new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); + $this->phpstanParser ??= new PhpDocParser(new TypeParser($constExprParser = new ConstExprParser(true, true), true), $constExprParser); } return $this->phpstanParser->parse(new TokenIterator($this->phpstanLexer->tokenize($rawDocNode))); diff --git a/upload/api/vendor/symfony/type-info/TypeFactoryTrait.php b/upload/api/vendor/symfony/type-info/TypeFactoryTrait.php index d814497d..b3736ab8 100644 --- a/upload/api/vendor/symfony/type-info/TypeFactoryTrait.php +++ b/upload/api/vendor/symfony/type-info/TypeFactoryTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\TypeInfo; +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; use Symfony\Component\TypeInfo\Type\ArrayShapeType; use Symfony\Component\TypeInfo\Type\BackedEnumType; use Symfony\Component\TypeInfo\Type\BuiltinType; @@ -324,6 +325,10 @@ public static function union(Type ...$types): UnionType } if (1 === \count($unionTypes)) { + if ($unionTypes[0] instanceof BuiltinType && $unionTypes[0]->getTypeIdentifier()->isStandalone()) { + throw new InvalidArgumentException(\sprintf('Cannot create union with "%s" standalone type.', $unionTypes[0])); + } + return self::nullable($unionTypes[0]); } diff --git a/upload/api/vendor/symfony/type-info/TypeResolver/PhpDocAwareReflectionTypeResolver.php b/upload/api/vendor/symfony/type-info/TypeResolver/PhpDocAwareReflectionTypeResolver.php index 7d6562ea..47f4a507 100644 --- a/upload/api/vendor/symfony/type-info/TypeResolver/PhpDocAwareReflectionTypeResolver.php +++ b/upload/api/vendor/symfony/type-info/TypeResolver/PhpDocAwareReflectionTypeResolver.php @@ -52,7 +52,7 @@ public function __construct( } else { $this->lexer = $lexer ?? new Lexer(); $this->phpDocParser = $phpDocParser ?? new PhpDocParser( - new TypeParser($constExprParser = new ConstExprParser()), + new TypeParser($constExprParser = new ConstExprParser(true, true), true), $constExprParser, ); } diff --git a/upload/api/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php b/upload/api/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php index 72dff850..105311d9 100644 --- a/upload/api/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php +++ b/upload/api/vendor/symfony/type-info/TypeResolver/StringTypeResolver.php @@ -37,6 +37,7 @@ use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\ParserConfig; use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Exception\LogicException; use Symfony\Component\TypeInfo\Exception\UnsupportedException; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\BackedEnumType; @@ -60,6 +61,11 @@ final class StringTypeResolver implements TypeResolverInterface */ private static array $classExistCache = []; + /** + * @var array + */ + private array $resolvingExtraTypeAliases = []; + private readonly Lexer $lexer; private readonly TypeParser $parser; @@ -76,7 +82,7 @@ public function __construct( $this->parser = $parser ?? new TypeParser($config = new ParserConfig([]), new ConstExprParser($config)); } else { $this->lexer = $lexer ?? new Lexer(); - $this->parser = $parser ?? new TypeParser(new ConstExprParser()); + $this->parser = $parser ?? new TypeParser(new ConstExprParser(true, true), true); } } @@ -113,10 +119,18 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ if ($node instanceof ArrayShapeNode) { $shape = []; foreach ($node->items as $item) { - $shape[(string) $item->keyName] = [ + $shapeItem = [ 'type' => $this->getTypeFromNode($item->valueType, $typeContext), 'optional' => $item->optional, ]; + + if (null === $item->keyName) { + $shape[] = $shapeItem; + + continue; + } + + $shape[$item->keyName instanceof ConstExprStringNode ? $item->keyName->value : (string) $item->keyName] = $shapeItem; } return Type::arrayShape( @@ -141,7 +155,13 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ if ($node instanceof ConstTypeNode) { if ($node->constExpr instanceof ConstFetchNode) { - $className = match (strtolower($node->constExpr->className)) { + $classKeyword = strtolower($node->constExpr->className); + + if (null === $typeContext && \in_array($classKeyword, ['self', 'static', 'parent'], true)) { + throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "%s".', TypeContext::class, $classKeyword)); + } + + $className = match ($classKeyword) { 'self' => $typeContext->getDeclaringClass(), 'static' => $typeContext->getCalledClass(), 'parent' => $typeContext->getParentClass(), @@ -169,17 +189,21 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ } } + if (!$types) { + throw new \DomainException(\sprintf('No "%s" constant found in "%s".', $node->constExpr->name, $className)); + } + return CollectionType::mergeCollectionValueTypes($types); } - return match ($node->constExpr::class) { - ConstExprArrayNode::class => Type::array(), - ConstExprFalseNode::class => Type::false(), - ConstExprFloatNode::class => Type::float(), - ConstExprIntegerNode::class => Type::int(), - ConstExprNullNode::class => Type::null(), - ConstExprStringNode::class => Type::string(), - ConstExprTrueNode::class => Type::true(), + return match (true) { + $node->constExpr instanceof ConstExprArrayNode => Type::array(), + $node->constExpr instanceof ConstExprFalseNode => Type::false(), + $node->constExpr instanceof ConstExprFloatNode => Type::float(), + $node->constExpr instanceof ConstExprIntegerNode => Type::int(), + $node->constExpr instanceof ConstExprNullNode => Type::null(), + $node->constExpr instanceof ConstExprStringNode => Type::string(), + $node->constExpr instanceof ConstExprTrueNode => Type::true(), default => throw new \DomainException(\sprintf('Unhandled "%s" constant expression.', $node->constExpr::class)), }; } @@ -360,7 +384,17 @@ private function resolveCustomIdentifier(string $identifier, ?TypeContext $typeC } if (isset($this->extraTypeAliases[$identifier])) { - return $this->resolve($this->extraTypeAliases[$identifier]); + if (isset($this->resolvingExtraTypeAliases[$identifier])) { + throw new LogicException(\sprintf('Cannot resolve "%s" type alias as it is recursive.', $identifier)); + } + + $this->resolvingExtraTypeAliases[$identifier] = true; + + try { + return $this->resolve($this->extraTypeAliases[$identifier]); + } finally { + unset($this->resolvingExtraTypeAliases[$identifier]); + } } throw new \DomainException(\sprintf('Unhandled "%s" identifier.', $identifier)); diff --git a/upload/api/vendor/symfony/yaml/Inline.php b/upload/api/vendor/symfony/yaml/Inline.php index 0c1d454e..3c24c20f 100644 --- a/upload/api/vendor/symfony/yaml/Inline.php +++ b/upload/api/vendor/symfony/yaml/Inline.php @@ -69,6 +69,16 @@ public static function parse(string $value, int $flags = 0, array &$references = $i = 0; $isQuoted = null; $tag = self::parseTag($value, $i, $flags); + + // a tag without value, possibly followed by a comment + if (null !== $tag && '' !== $tag && (!isset($value[$i]) || '#' === $value[$i])) { + if (str_starts_with($tag, 'php/')) { + throw new ParseException(\sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return new TaggedValue($tag, ''); + } + switch ($value[$i]) { case '[': $result = self::parseSequence($state, $value, $flags, $i, $references); @@ -402,6 +412,10 @@ private static function parseSequence(ParserState $state, string $sequence, int continue; } + if (!isset($sequence[$i])) { + break; + } + switch ($sequence[$i]) { case '[': // nested sequence @@ -522,7 +536,7 @@ private static function parseMapping(ParserState $state, string $mapping, int $f if (!$isKeyQuoted) { $evaluatedKey = self::evaluateScalar($state, $key, $flags, $references); - if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + if ('' !== $key && $evaluatedKey !== $key && (!\is_string($evaluatedKey) || '!' === $key[0]) && !\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); } } @@ -561,6 +575,10 @@ private static function parseMapping(ParserState $state, string $mapping, int $f continue 2; } + if (!isset($mapping[$i])) { + break; + } + switch ($mapping[$i]) { case '[': // nested sequence @@ -687,6 +705,9 @@ private static function evaluateScalar(ParserState $state, string $scalar, int $ return false; case '!' === $scalar[0]: switch (true) { + case '!!str' === $scalar: + case '!!binary' === $scalar: + return ''; case str_starts_with($scalar, '!!str '): $s = substr($scalar, 6); @@ -894,10 +915,6 @@ private static function parseTag(string $value, int &$i, int $flags): ?string 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; } @@ -907,7 +924,11 @@ private static function parseTag(string $value, int &$i, int $flags): ?string public static function evaluateBinaryScalar(string $scalar): string { - $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + $parsedBinaryData = preg_replace('/\s/', '', $scalar); + + if ('' === $parsedBinaryData || '' === $parsedBinaryData = self::parseScalar($parsedBinaryData)) { + return ''; + } if (!\is_scalar($parsedBinaryData ?? '') && !$parsedBinaryData instanceof \Stringable) { throw new ParseException(\sprintf('The "!!binary" tag only supports a base64 encoded string, got "%s".', get_debug_type($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); diff --git a/upload/api/vendor/symfony/yaml/Parser.php b/upload/api/vendor/symfony/yaml/Parser.php index 948fd743..c35b1818 100644 --- a/upload/api/vendor/symfony/yaml/Parser.php +++ b/upload/api/vendor/symfony/yaml/Parser.php @@ -24,7 +24,7 @@ class Parser { public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; - public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P[ \t]+#.*)?'; public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; public const DEFAULT_MAX_NESTING_LEVEL = 128; public const DEFAULT_MAX_ALIASES_FOR_COLLECTIONS = 128; @@ -129,7 +129,7 @@ private function doParse(string $value, int $flags): mixed { $this->currentLineNb = -1; $this->currentLine = ''; - $value = $this->cleanup($value); + $value = $this->cleanup($value, $hasContentOnStartMarkerLine); $this->lines = explode("\n", $value); $this->numberOfParsedLines = \count($this->lines); $this->locallySkippedLineNumbers = []; @@ -150,8 +150,12 @@ private function doParse(string $value, int $flags): mixed } // 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, ''); + if (null !== $tag = $this->getLineTag($this->currentLine, $flags, false)) { + do { + if (!$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + } while ($this->isCurrentLineEmpty()); } do { @@ -173,6 +177,14 @@ private function doParse(string $value, int $flags): mixed } $context = 'sequence'; + if ($hasContentOnStartMarkerLine && 0 === $this->currentLineNb) { + throw new ParseException('A block collection cannot start on the line of the document start marker (---).', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($values['value']) && '!' === $values['value'][0]) { + $values['value'] = $this->moveAnchorBeforeTag($values['value']); + } + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; @@ -190,9 +202,14 @@ private function doParse(string $value, int $flags): mixed $sequenceIndentation = \strlen($values['leadspaces']) + 1; $sequenceYaml = substr($this->currentLine, $sequenceIndentation); - $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + $embedBlock = $this->getNextEmbedBlock($sequenceIndentation, true); + + // keep trailing empty lines, they can be part of a block scalar + if ($currentLineNumber !== $this->getRealCurrentLineNb()) { + $sequenceYaml .= "\n".$embedBlock; + } - $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + $data[] = $this->parseBlock($currentLineNumber, $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)) { @@ -200,7 +217,7 @@ private function doParse(string $value, int $flags): mixed $subTag, $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) ); - } elseif (self::preg_match('/^'.self::TAG_PATTERN.' +'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $values['value'])) { + } elseif (self::preg_match('/^'.self::TAG_PATTERN.'[ \t]+'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $values['value'])) { $data[] = $this->parseValue($values['value'], $flags, $context); } else { if ( @@ -210,12 +227,14 @@ private function doParse(string $value, int $flags): mixed || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) ) ) { + $currentLineNumber = $this->getRealCurrentLineNb(); + $block = $values['value']; - if ($this->isNextLineIndented() || isset($matches['value']) && '>-' === $matches['value']) { + if ($this->isNextLineIndented() || $this->isNextLineInBlockScalar($block)) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); } - $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + $data[] = $this->parseBlock($currentLineNumber, $block, $flags); } else { $data[] = $this->parseValue($values['value'], $flags, $context); } @@ -225,7 +244,7 @@ private function doParse(string $value, int $flags): mixed array_pop($this->refsBeingParsed); } } elseif ( - self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{!].*?)|![^\s]++(?= +\:)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"], true)) ) { if ($context && 'sequence' == $context) { @@ -233,6 +252,14 @@ private function doParse(string $value, int $flags): mixed } $context = 'mapping'; + if ($hasContentOnStartMarkerLine && 0 === $this->currentLineNb) { + throw new ParseException('A block collection cannot start on the line of the document start marker (---).', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($values['value'][0]) && '!' === $values['value'][0]) { + $values['value'] = $this->moveAnchorBeforeTag($values['value']); + } + try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { @@ -284,12 +311,14 @@ private function doParse(string $value, int $flags): mixed $data += $refValue; // array union } else { + $currentLineNumber = $this->getRealCurrentLineNb(); + if (isset($values['value']) && '' !== $values['value']) { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } - $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + $parsed = $this->parseBlock($currentLineNumber + 1, $value, $flags); if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { $parsed = (array) $parsed; @@ -450,11 +479,6 @@ private function doParse(string $value, int $flags): mixed 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 (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) { throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } @@ -773,17 +797,18 @@ private function parseValue(string $value, int $flags, string $context): mixed return $this->refs[$value]; } - if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.'[ \t]+)?'.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 match ($matches['tag']) { + '!!binary' => Inline::evaluateBinaryScalar($data), + '!!str' => $data, + '!!float' => (float) $data, + default => new TaggedValue(substr($matches['tag'], 1), $data), + }; } return $data; @@ -880,9 +905,12 @@ private function parseBlockScalar(string $style, string $chomping = '', int $ind $isCurrentLineBlank = $this->isCurrentLineBlank(); $blockLines = []; + $longestBlankLine = 0; // leading blank lines are consumed before determining indentation - while ($notEOF && $isCurrentLineBlank) { + while ($notEOF && $isCurrentLineBlank && (!$indentation || \strlen($this->currentLine) <= $indentation)) { + $longestBlankLine = max($longestBlankLine, \strlen($this->currentLine)); + // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $blockLines[] = ''; @@ -897,32 +925,44 @@ private function parseBlockScalar(string $style, string $chomping = '', int $ind for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { ++$indentation; } + + // a comment less indented than the leading empty lines is not content, the indentation is then the one of the longest empty line + if ($indentation < $longestBlankLine && $this->isCurrentLineComment()) { + $indentation = $longestBlankLine; + } } - if ($indentation > 0) { - $pattern = \sprintf('/^ {%d}(.*)$/', $indentation); + if (!$notEOF || !$indentation || strspn($this->currentLine, ' ') < $indentation) { + if ($notEOF) { + $this->moveToPreviousLine(); + } elseif (!$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } - 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]; - } + // without content, the line breaks of the empty lines are only kept with the "+" chomping indicator + return '+' === $chomping ? str_repeat("\n", \count($blockLines)) : ''; + } - // newline only if not EOF - if ($notEOF = $this->moveToNextLine()) { - $isCurrentLineBlank = $this->isCurrentLineBlank(); - } + $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) { @@ -935,16 +975,15 @@ private function parseBlockScalar(string $style, string $chomping = '', int $ind // folded style if ('>' === $style) { $text = ''; - $previousLineIndented = false; + $previousLineIndented = null; $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]; + } elseif (' ' === $blockLines[$i][0] || "\t" === $blockLines[$i][0]) { + $text .= (null === $previousLineIndented ? '' : "\n").$blockLines[$i]; $previousLineIndented = true; $previousLineBlank = false; } elseif ($previousLineIndented) { @@ -1031,37 +1070,81 @@ private function isCurrentLineLastLineInDocument(): bool return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); } - private function cleanup(string $value): string + private function cleanup(string $value, ?bool &$hasContentOnStartMarkerLine = null): string { + $hasContentOnStartMarkerLine = false; + $value = str_replace(["\r\n", "\r"], "\n", $value); - // strip YAML header - $count = 0; - $value = preg_replace('#^%YAML[: ][\d.]++[^\n]*+\n#u', '', $value, -1, $count); - $this->offset += $count; - - // remove leading comments - $trimmedValue = preg_replace('#^(?:\#[^\n]*+\n)++#', '', $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 the YAML header, and the comments and empty lines before the content + $value = $this->removeLeadingLines('#^(?:[ \t]*+(?:\#[^\n]*+)?+\n)*+(?:%YAML[: ][\d.]++[^\n]*+\n(?:[ \t]*+(?:\#[^\n]*+)?+\n)*+)?+#', $value); + + // document markers and the properties of the root node are only found at the top level, not in nested blocks + if (null !== $this->totalNumberOfLines) { + return $value; } - // remove start of the document marker (---) - $trimmedValue = preg_replace('#^---[^\n]*+\n#', '', $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 (---), with the YAML 1.0 directives, or the node properties and block scalar header that can follow it alone, as they were always ignored + $value = preg_replace('#^---(?:[ \t]++(?:%[^\n]*+|(?:(?:[!&][^ \t\n]*+|[|>][-+0-9]*+)[ \t]*+)++(?=(?:\#[^\n]*+)?+(?:\n|\z)))?+|(?=\n)|\z)#', '', $value, -1, $count); + + // remove the anchor of the root node, as nothing can refer to it + $value = preg_replace('#^((?:![^! \t\n][^ \t\n]*+[ \t]++)?+)&[^ \t\n]++(?:[ \t]++|(?=\n)|\z)#', '$1', $value); - // remove end of the document marker (...) - $value = preg_replace('#\.\.\.\s*+$#', '', $value); + // remove the lines left without content + $offset = $this->offset; + $value = $this->removeLeadingLines('#^(?:[ \t]*+(?:\#[^\n]*+)?+\n)++#', $value); + $hasContentOnStartMarkerLine = 1 === $count && $offset === $this->offset; + + // remove end of the document marker (...), which can only be followed by comments + $value = preg_replace('#^\.\.\.(?:[ \t]++(?:\#[^\n]*+)?+)?+(?:\n[ \t]*+(?:\#[^\n]*+)?+)*+\z#m', '', $value); + + if (self::preg_match('#^(?:---|\.\.\.)(?:[ \t][^\n]*+)?+$#m', $value, $matches, \PREG_OFFSET_CAPTURE)) { + throw new ParseException('Multiple documents are not supported.', $this->offset + substr_count($value, "\n", 0, $matches[0][1]) + 1, $matches[0][0], $this->filename); } return $value; } + /** + * Tells whether the next line belongs to a block scalar whose header ends the given value. + * + * Unlike isNextLineIndented(), empty and comment-like lines are not skipped, as they can be part of the block scalar. + */ + private function isNextLineInBlockScalar(string $value): bool + { + if (!isset($this->lines[$this->currentLineNb + 1]) || !self::preg_match('/:[ \t]+(?:'.self::TAG_PATTERN.'[ \t]+)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value)) { + return false; + } + + $nextLine = $this->lines[$this->currentLineNb + 1]; + + return '' === trim($nextLine, ' ') || strspn($nextLine, ' ') > $this->getCurrentLineIndentation(); + } + + /** + * Turns "!tag &anchor value" into "&anchor !tag value", as both orders are allowed. + * + * Built-in tags are left alone: "!!str &foo" is the "&foo" string, as in flow collections. + */ + private function moveAnchorBeforeTag(string $value): string + { + if (str_starts_with($value, '!!')) { + return $value; + } + + return preg_replace('#^'.self::TAG_PATTERN.' ++(&[^ ]++)#u', '$2 $1', $value); + } + + private function removeLeadingLines(string $pattern, string $value): string + { + $trimmedValue = preg_replace($pattern, '', $value); + + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + + return $trimmedValue; + } + private function isNextLineUnIndentedCollection(): bool { $currentIndentation = $this->getCurrentLineIndentation(); @@ -1075,11 +1158,7 @@ private function isNextLineUnIndentedCollection(): bool } } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); - if ($EOF) { - return false; - } - - $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + $ret = !$EOF && $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); @@ -1134,7 +1213,8 @@ private function getLineTag(string $value, int $flags, bool $nextLineCheck = tru return null; } - if ($nextLineCheck && !$this->isNextLineIndented()) { + // unlike a sequence item, the value of a mapping key can also be a sequence that is not indented + if ($nextLineCheck && !$this->isNextLineIndented() && ($this->isStringUnIndentedCollectionItem() || !$this->isNextLineUnIndentedCollection())) { return null; } @@ -1142,6 +1222,11 @@ private function getLineTag(string $value, int $flags, bool $nextLineCheck = tru // Built-in tags if ($tag && '!' === $tag[0]) { + // "!!str" and "!!binary" can be resolved as inline scalars when they don't apply to a nested block + if (!$nextLineCheck && \in_array($tag, ['!str', '!binary'], true) && !$this->isNextLineIndented()) { + return null; + } + throw new ParseException(\sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } diff --git a/upload/api/vendor/zircote/swagger-php/CONTRIBUTING.md b/upload/api/vendor/zircote/swagger-php/CONTRIBUTING.md index e5c79c69..8ce38a40 100644 --- a/upload/api/vendor/zircote/swagger-php/CONTRIBUTING.md +++ b/upload/api/vendor/zircote/swagger-php/CONTRIBUTING.md @@ -61,6 +61,43 @@ Commit subjects follow the same `type(Scope): subject` shape as the title, with of `feat`, `fix`, `docs`, `test`, `chore` or `refactor`. A commit message body documents what the diff does; the reasoning belongs in the pull request description. +## Versioning + +swagger-php follows [semantic versioning](https://semver.org). What a change earns: + +**Patch** — a fix that changes generated output only where the old output was wrong, an +internal refactor, a documentation or CI change. Anything a consumer can take without +reading the release notes. + +**Minor** — new public API, a new annotation or attribute, a new field on an existing one, +and **any widening of a `require` constraint**. A dependency change is a minor even when it +only widens: a patch is expected to be a drop-in with no effect on resolution, and widening +lets consumers install combinations that were impossible before. + +**Major** — removing or re-signing public API, narrowing a `require` constraint, raising the +PHP floor, or changing generated output that a correct document could have depended on. + +**The commit type does not decide this.** #2196 carried no `feat` — its subject is *"Drop +symfony/console 7.4 requirement"*, which reads like a fix — but it is a minor, because +applications on Symfony 6.4 that could not install the library at all now can. Read the +effect on a consumer, not the prefix. The reverse holds too: a `feat` that only adds a test +helper changes nothing for anyone and is not a minor on its own. + +**Generated output is the case that needs care**, because most fixes here change it. Correcting +output that was invalid, or that no valid document could have relied on, is a patch — that is +the bug being fixed. Changing output that was already valid is at least a minor, even when the +new output is better, because a consumer's committed spec file or downstream generator will +see a diff it did not ask for. + +**Public API is every class under `src/` that a consumer can reach**, which today means all of +them — nothing is marked `@internal`. In practice the classes people build on are `Generator`, +the analysers and processors, the annotations and attributes, and the `Spec` classes; the +`Console` classes exist for `bin/openapi` and are reached through the command line rather than +extended. That is a description of how things are used, not a promise, so when a change touches +a class outside that list, say so in the pull request rather than assuming nobody is on it. + +If a release mixes levels, the highest one wins. + ## Documentation The documentation website is build from the [docs](docs/) folder with [vitepress](https://vitepress.vuejs.org). diff --git a/upload/api/vendor/zircote/swagger-php/README.md b/upload/api/vendor/zircote/swagger-php/README.md index 7e94e062..a9d8cb36 100644 --- a/upload/api/vendor/zircote/swagger-php/README.md +++ b/upload/api/vendor/zircote/swagger-php/README.md @@ -20,7 +20,7 @@ attributes and annotations. - Extracts information from code and existing phpdoc comments. - Can be used programmatically or via command-line tool. - Error reporting (with hints, context). -- 🧪 **Spec attributes pipeline (beta)** — a new processing mode with typed DTOs, grouped augmenters, and version-aware compilers. +- 🧪 **Spec attributes (beta)** — attributes you can also build by hand and compile without scanning, and a pipeline that tells you when an attribute is wrong; see [Why spec attributes?](https://zircote.github.io/swagger-php/guide/why-spec). ## OpenAPI version support diff --git a/upload/api/vendor/zircote/swagger-php/ROADMAP.md b/upload/api/vendor/zircote/swagger-php/ROADMAP.md index 84f5de06..0b2f0f65 100644 --- a/upload/api/vendor/zircote/swagger-php/ROADMAP.md +++ b/upload/api/vendor/zircote/swagger-php/ROADMAP.md @@ -1,23 +1,85 @@ # Roadmap ## Overview -This is a high level roadmap predominantly for **v6**, **v7** and **v8**. +This is a high level roadmap for **v6**, **v7** and **v8** - what happens to the classic pipeline +and the new _Spec_ attributes and processing pipeline, and when. -In particular this is in relation to the introduction of the new _Spec_ attributes and processing pipeline and what may/will happen when. +The discussion and the tick list of what has shipped are in +[#1953](https://github.com/zircote/swagger-php/issues/1953). This file is the plan; the issue +tracks it. + +## Deprecation rule +A `@deprecated` marker names the version that removes the thing: + +```php +/** @deprecated since 6.11, removed in 8.0 - use ... instead */ +``` + +Nothing is removed before the version its marker names, and nothing is removed without a marker. +Something marked for `8.0` keeps working through **v7** unchanged. That is what "no cliff" means +here: the marker is the promise. ## Timeline ### v6 -`6.5.0` saw the introduction of the new `Spec` system. Right now this is considered not yet production ready. -The remainder of **v6** will be defined by improving and completing the new system. +`6.5.0` introduced the `Spec` system as opt-in beta. The remainder of **v6** is about completing +it and getting people onto it: + +* `Spec` attributes declared frozen, and the **beta** label dropped in that minor +* a rector rule set for `OpenApi\Attributes` -> `OpenApi\Spec` +* a seam to contribute pre-built attributes to a `Specification` before resolution, for + framework integrations that do not scan +* docblock annotation support marked `@deprecated`, removed in `8.0` - the README has said so + since 4.8, the code now agrees, and parsing a docblock annotation triggers a runtime + deprecation once per run +* the relocation shims already marked (`Pipeline`, `SourceFinder`, `Analysers\TokenScanner`, + `Utils\TypeMapper`, `LegacyTypeResolver`) are removed in `7.0` +* `Generator::UNDEFINED` and `Generator::isDefault()` stay until `8.0` - every classic custom + processor uses them, so they go with classic and not before + +Classic attributes, the `Generator` and the processors are **not** marked in v6. The replacement +is still beta for part of the major, and a stable API is not deprecated in favour of a beta one. ### v7 -This is where things will start to turn: -* Default mode will switch to `hybrid` - this means existing `classic` projects should keep working via the bridge (probably with the exception of `NelmioApiDocBundle`, as that relies on a lot of actual `classic` features), although they will be routed through the new `spec` pipeline via a custom bridge. -* All classic code - annotations + attributes and related pipeline code will be marked `deprecated` -* All code marked `depecated` in **v6** will be removed -* Bridge is marked `deprecated` -* `Builder::setMode()` is marked `deprecated` -* `nikic/php-parser` is raised to `^5.0` — the `^4.19` branch parses no further than +This is where things turn: +* default mode switches to `hybrid` - existing `classic` projects keep working, routed through + the new pipeline via the bridge, with the exception of integrations that build annotation + objects directly instead of scanning for them, which the bridge has nothing to scan for +* all classic code - annotations, attributes and the related pipeline code - is marked + `@deprecated`, removed in `8.0` +* the bridge and mode selection are marked `@deprecated`, removed in `8.0` - that is + `Builder::setMode()`, the `Builder\Mode` enum, `Console\GenerateInput::$mode` and the CLI's + `-m`/`--mode` option, which go together because there is one pipeline in `8.0` and nothing + left to select. These markers name no replacement, unlike every other marker here +* everything whose marker says `7.0` is removed +* `nikic/php-parser` is raised to `^5.0` - the `^4.19` branch parses no further than PHP 8.3 syntax -## v8 -`classic` is removed from the codebase, leaving only the spec pipeline. Annotations are no longer supported at all. +* the v7 migration guide states the v8 namespace move below, so nobody meets it as a surprise + +Open: whether the default **output** version follows the default mode. `classic` defaults to +OpenAPI 3.0.0 and `hybrid` to 3.1.0 today, so switching the default mode also switches the +default output version for everyone unless v7 pins 3.0.0. Decided in #1953 before v7. + +### v8 +`classic` is removed from the codebase, leaving only the spec pipeline. Annotations are no longer +supported at all. + +The spec attributes move from `OpenApi\Spec` to `OpenApi\Attributes`. With one pipeline left, +"Spec" is a mode name that means nothing, and `OpenApi\Attributes` is the better home. For code +written against `OpenApi\Spec` that is one `use` line per file, and the rector set does it. + +Open: whether `OpenApi\Spec` stays as deprecated aliases for one major, so the move is optional +until v9. + +## Finding what to remove +Code carries `@deprecated` with the version that removes it, which is the primary marker and +the one tooling understands. Documentation, fixture layout and test-matrix structure cannot +carry it - nothing consumes a docblock in a markdown file or a yaml expectation - so those are +marked inline with the mode they exist to serve: `[classic]`, `[hybrid]`, or `[classic/hybrid]` +for what serves both. `grep -rnE '\[(classic|hybrid)'` lists them, and all of it goes when +`classic` does. + +The marker names the mode rather than the release on purpose. A thing is `[hybrid]` for as +long as hybrid exists, which stays true whatever happens to this timeline; `[v8: drop]` would +be a claim about a schedule, and would need revisiting if the schedule moved. The two markers +do not overlap with `@deprecated`: if a thing can carry a docblock, it gets `@deprecated` and +not this. diff --git a/upload/api/vendor/zircote/swagger-php/bin/openapi b/upload/api/vendor/zircote/swagger-php/bin/openapi index 26a5a43f..3d0f5797 100755 --- a/upload/api/vendor/zircote/swagger-php/bin/openapi +++ b/upload/api/vendor/zircote/swagger-php/bin/openapi @@ -41,6 +41,6 @@ $options = $definition->getOptions(); unset($options['version'], $options['no-interaction']); $definition->setOptions($options); -$app->addCommand(new GenerateCommand($logger)); +$app->addCommands([new GenerateCommand($logger)]); $app->setDefaultCommand('openapi', true); $app->run($input, $output); diff --git a/upload/api/vendor/zircote/swagger-php/composer.json b/upload/api/vendor/zircote/swagger-php/composer.json index 68e6332e..aa6d5c6f 100644 --- a/upload/api/vendor/zircote/swagger-php/composer.json +++ b/upload/api/vendor/zircote/swagger-php/composer.json @@ -49,7 +49,7 @@ "phpstan/phpdoc-parser": "^2.0", "psr/log": "^1.1 || ^2.0 || ^3.0", "radebatz/type-info-extras": "^1.0.2", - "symfony/console": "^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", "symfony/deprecation-contracts": "^2 || ^3", "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" diff --git a/upload/api/vendor/zircote/swagger-php/package-lock.json b/upload/api/vendor/zircote/swagger-php/package-lock.json index 4ed46815..98470f89 100644 --- a/upload/api/vendor/zircote/swagger-php/package-lock.json +++ b/upload/api/vendor/zircote/swagger-php/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "Apache-2.0", "devDependencies": { - "@redocly/cli": "^2.37.0" + "@redocly/cli": "^2.53.3" } }, "node_modules/@redocly/cli": { - "version": "2.37.0", - "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.37.0.tgz", - "integrity": "sha512-2xKvVy2re6xiWfrWHJj633oEoVAYHisPsxEwoxtSxMqB+RhW2uqM98+JPjrLQPpj2m1bXrj08KXwpSFfojV0/g==", + "version": "2.53.3", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.53.3.tgz", + "integrity": "sha512-hzNAWzHCOZ05vwRx0ehTxNeJaxxizjGV505eKtfs9MR/8ieD/8lYhAK4GF5FaqRRwOEpLO0PvzQwBSKYXmxhRA==", "dev": true, "license": "MIT", "bin": { diff --git a/upload/api/vendor/zircote/swagger-php/package.json b/upload/api/vendor/zircote/swagger-php/package.json index b2796972..74adbc39 100644 --- a/upload/api/vendor/zircote/swagger-php/package.json +++ b/upload/api/vendor/zircote/swagger-php/package.json @@ -6,6 +6,6 @@ "redocly": "redocly" }, "devDependencies": { - "@redocly/cli": "^2.37.0" + "@redocly/cli": "^2.53.3" } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php b/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php index a5b17765..347413f5 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php +++ b/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockAnnotationFactory.php @@ -11,12 +11,20 @@ use OpenApi\Generator; use OpenApi\GeneratorAwareTrait; +/** + * Builds annotations from docblock comments. + * + * @deprecated since 6.11, removed in 8.0 - use attributes instead + */ class DocBlockAnnotationFactory implements AnnotationFactoryInterface { use GeneratorAwareTrait; protected ?DocBlockParser $docBlockParser = null; + /** Reset per generator run, so a scan of a thousand annotated files reports once. */ + protected bool $deprecationReported = false; + public function __construct(?DocBlockParser $docBlockParser = null) { $this->docBlockParser = $docBlockParser ?: new DocBlockParser(); @@ -30,6 +38,7 @@ public function isSupported(): bool public function setGenerator(Generator $generator): static { $this->generator = $generator; + $this->deprecationReported = false; $this->docBlockParser->setAliases($generator->getAliases()); @@ -60,6 +69,7 @@ public function build(\Reflector $reflector, Context $context): array $annotations = []; foreach ($this->docBlockParser->fromComment($comment, $context) as $instance) { if ($instance instanceof OA\AbstractAnnotation) { + $this->reportDeprecation(); $annotations[] = $instance; } else { if ($context->is('other') === false) { @@ -74,4 +84,18 @@ public function build(\Reflector $reflector, Context $context): array return []; } + + /** + * Reported on the first docblock annotation actually parsed, not on the first docblock + * seen: a project that has migrated to attributes still has docblocks everywhere. + */ + protected function reportDeprecation(): void + { + if ($this->deprecationReported) { + return; + } + + $this->deprecationReported = true; + trigger_deprecation('zircote/swagger-php', '6.11', 'Docblock annotations are deprecated and will be removed in 8.0; use attributes instead'); + } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php b/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php index ae34859c..5b84cacb 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php +++ b/upload/api/vendor/zircote/swagger-php/src/Analysers/DocBlockParser.php @@ -14,19 +14,24 @@ /** * Extract swagger-php annotations from a [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) using Doctrine's DocParser. */ +/** + * @deprecated since 6.11, removed in 8.0 - use attributes instead + */ class DocBlockParser { protected DocParser $docParser; /** - * @param array $aliases + * @param array $aliases */ public function __construct(array $aliases = []) { if (DocBlockParser::isEnabled()) { $docParser = new DocParser(); $docParser->setIgnoreNotImportedAnnotations(true); - $docParser->setImports($aliases); + /** @var array $importedAliases doctrine wants class-string; ours may be bare namespaces too */ + $importedAliases = $aliases; + $docParser->setImports($importedAliases); $this->docParser = $docParser; } } @@ -40,11 +45,13 @@ public static function isEnabled(): bool } /** - * @param array $aliases + * @param array $aliases */ public function setAliases(array $aliases): void { - $this->docParser->setImports($aliases); + /** @var array $importedAliases doctrine wants class-string; ours may be bare namespaces too */ + $importedAliases = $aliases; + $this->docParser->setImports($importedAliases); } /** @@ -69,7 +76,7 @@ public function fromComment(string $comment, Context $context): array if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string) $context, '/') . '\.$/', $exception->getMessage(), $matches)) { $errorMessage = $matches[1]; $errorPos = (int) $matches[2]; - $atPos = strpos($comment, '@'); + $atPos = strpos($comment, '@') ?: 0; $context->line -= substr_count($comment, "\n", $atPos + $errorPos) + 1; $lines = explode("\n", substr($comment, $atPos, $errorPos)); $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 diff --git a/upload/api/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php b/upload/api/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php index 6a0654e9..f8aa8f2c 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php +++ b/upload/api/vendor/zircote/swagger-php/src/Analysers/ReflectionAnalyser.php @@ -12,6 +12,7 @@ use OpenApi\Generator; use OpenApi\GeneratorAwareTrait; use OpenApi\OpenApiException; +use OpenApi\Utils\ClassReflector; use OpenApi\Utils\TokenScanner; /** @@ -20,6 +21,11 @@ * Can read either PHP DocBlocks or Attributes. * * Due to the nature of reflection, this requires all related classes to be auto-loadable. + * + * @phpstan-import-type ClassDefinition from Analysis + * @phpstan-import-type InterfaceDefinition from Analysis + * @phpstan-import-type TraitDefinition from Analysis + * @phpstan-import-type EnumDefinition from Analysis */ class ReflectionAnalyser implements AnalyserInterface { @@ -68,8 +74,12 @@ public function fromFile(string $filename, Context $context): Analysis return $analysis; } + /** + * @param class-string $fqdn + */ public function fromFqdn(string $fqdn, Analysis $analysis): Analysis { + /** @var class-string $fqdn ltrim() does not change that */ $fqdn = ltrim($fqdn, '\\'); $rc = new \ReflectionClass($fqdn); @@ -85,15 +95,35 @@ public function fromFqdn(string $fqdn, Analysis $analysis): Analysis return $analysis; } + /** + * @param array $details + */ 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); + [$rc, $reason] = ClassReflector::tryReflect($fqdn); + if (!$rc instanceof \ReflectionClass) { + $analysis->context->logger->warning($reason === null + ? 'Skipping unknown ' . $fqdn + : "Skipping unloadable {$fqdn}: {$reason}"); return $analysis; } - $rc = new \ReflectionClass($fqdn); + try { + return $this->buildDefinition($rc, $analysis, $details); + } catch (\Throwable $throwable) { + $analysis->context->logger->warning("Skipping unloadable {$fqdn}: {$throwable->getMessage()}"); + + return $analysis; + } + } + + /** + * @param \ReflectionClass $rc + * @param array $details + */ + protected function buildDefinition(\ReflectionClass $rc, Analysis $analysis, array $details): Analysis + { $contextType = $rc->isInterface() ? 'interface' : ($rc->isTrait() ? 'trait' : ($rc->isEnum() ? 'enum' : 'class')); @@ -179,8 +209,28 @@ protected function analyzeFqdn(string $fqdn, Analysis $analysis, array $details) } } - $addDefinition = 'add' . ucfirst($contextType) . 'Definition'; - $analysis->{$addDefinition}($definition); + switch ($contextType) { + case 'interface': + /** @var InterfaceDefinition $interfaceDefinition */ + $interfaceDefinition = $definition; + $analysis->addInterfaceDefinition($interfaceDefinition); + break; + case 'trait': + /** @var TraitDefinition $traitDefinition */ + $traitDefinition = $definition; + $analysis->addTraitDefinition($traitDefinition); + break; + case 'enum': + /** @var EnumDefinition $enumDefinition */ + $enumDefinition = $definition; + $analysis->addEnumDefinition($enumDefinition); + break; + default: + /** @var ClassDefinition $classDefinition */ + $classDefinition = $definition; + $analysis->addClassDefinition($classDefinition); + break; + } return $analysis; } diff --git a/upload/api/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php b/upload/api/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php index c93b408a..c5ab964b 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php +++ b/upload/api/vendor/zircote/swagger-php/src/Analysers/TokenScanner.php @@ -7,7 +7,7 @@ namespace OpenApi\Analysers; /** - * @deprecated use {@see \OpenApi\Utils\TokenScanner} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see \OpenApi\Utils\TokenScanner} instead */ class TokenScanner extends \OpenApi\Utils\TokenScanner { diff --git a/upload/api/vendor/zircote/swagger-php/src/Analysis.php b/upload/api/vendor/zircote/swagger-php/src/Analysis.php index 701c5e64..68972dd1 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Analysis.php +++ b/upload/api/vendor/zircote/swagger-php/src/Analysis.php @@ -12,6 +12,51 @@ * Result of the analyser. * * Pretends to be an array of annotations but also contains detected classes and helper functions for the processors. + * + * The four definition shapes differ only in which name key they carry, and in `extends` — a + * single parent for a class, a list of parents for an interface — which is why they are spelled + * out separately rather than as one shape with optional keys. + * + * @phpstan-type ClassDefinition array{ + * class: string, + * extends: string|null, + * implements: list, + * traits: list, + * properties: array, + * methods: array, + * constants?: array, + * context: Context, + * } + * @phpstan-type InterfaceDefinition array{ + * interface: string, + * extends: list, + * implements: list, + * traits: list, + * properties: array, + * methods: array, + * constants?: array, + * context: Context, + * } + * @phpstan-type TraitDefinition array{ + * trait: string, + * extends: string|null, + * implements: list, + * traits: list, + * properties: array, + * methods: array, + * constants?: array, + * context: Context, + * } + * @phpstan-type EnumDefinition array{ + * enum: string, + * extends: string|null, + * implements: list, + * traits: list, + * properties: array, + * methods: array, + * constants?: array, + * context: Context, + * } */ class Analysis { @@ -20,21 +65,29 @@ class Analysis /** * Class definitions. + * + * @var array */ public array $classes = []; /** * Interface definitions. + * + * @var array */ public array $interfaces = []; /** * Trait definitions. + * + * @var array */ public array $traits = []; /** * Enum definitions. + * + * @var array */ public array $enums = []; @@ -147,27 +200,51 @@ public function mergeAnnotations(OA\AbstractAnnotation $parent, array $annotatio return $unmerged; } + /** + * @param ClassDefinition $definition + */ public function addClassDefinition(array $definition): void { $class = $definition['context']->fullyQualifiedName($definition['class']); + if (null === $class) { + throw new OpenApiException('Class definition has no class name'); + } $this->classes[$class] = $definition; } + /** + * @param InterfaceDefinition $definition + */ public function addInterfaceDefinition(array $definition): void { $interface = $definition['context']->fullyQualifiedName($definition['interface']); + if (null === $interface) { + throw new OpenApiException('Interface definition has no interface name'); + } $this->interfaces[$interface] = $definition; } + /** + * @param TraitDefinition $definition + */ public function addTraitDefinition(array $definition): void { $trait = $definition['context']->fullyQualifiedName($definition['trait']); + if (null === $trait) { + throw new OpenApiException('Trait definition has no trait name'); + } $this->traits[$trait] = $definition; } + /** + * @param EnumDefinition $definition + */ public function addEnumDefinition(array $definition): void { $enum = $definition['context']->fullyQualifiedName($definition['enum']); + if (null === $enum) { + throw new OpenApiException('Enum definition has no enum name'); + } $this->enums[$enum] = $definition; } @@ -190,7 +267,7 @@ public function addAnalysis(Analysis $analysis): void * * @param class-string $parent the parent class * - * @return array map of class => definition pairs of sub-classes + * @return array map of class => definition pairs of sub-classes */ public function getSubClasses(string $parent): array { @@ -208,10 +285,11 @@ public function getSubClasses(string $parent): array /** * Get a list of all super classes for the given class. * - * @param class-string|null $class the class name - * @param bool $direct flag to find only the actual class parents + * @param string|null $class the class name; also called with a trait/interface name by + * getTraitsOfClass()/getInterfacesOfClass(), so not class-string + * @param bool $direct flag to find only the actual class parents * - * @return array map of class => definition pairs of parent classes + * @return array map of class => definition pairs of parent classes */ public function getSuperClasses(?string $class, bool $direct = false): array { @@ -221,6 +299,11 @@ public function getSuperClasses(?string $class, bool $direct = false): array return []; } + if (!is_string($classDefinition['extends'])) { + // a class extends at most one class; a list here means this definition is an interface's + return []; + } + /** @var class-string $extends */ $extends = $classDefinition['extends']; $extendsDefinition = $this->classes[$extends] ?? null; if (!$extendsDefinition) { @@ -242,7 +325,7 @@ public function getSuperClasses(?string $class, bool $direct = false): array * @param class-string|null $class the class name * @param bool $direct flag to find only the actual class interfaces * - * @return array map of class => definition pairs of interfaces + * @return array map of class => definition pairs of interfaces */ public function getInterfacesOfClass(?string $class, bool $direct = false): array { @@ -266,11 +349,15 @@ public function getInterfacesOfClass(?string $class, bool $direct = false): arra if (!$direct) { // expand recursively for interfaces extending other interfaces - $collect = function ($interfaces, $cb) use (&$definitions): void { + /** + * @param list $interfaces + */ + $collect = function (array $interfaces, callable $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) { + $parentInterfaces = $this->interfaces[$interface]['extends'] ?? null; + if (is_array($parentInterfaces)) { + $cb($parentInterfaces, $cb); + foreach ($parentInterfaces as $fqdn) { $definitions[$fqdn] = $this->interfaces[$fqdn]; } } @@ -288,7 +375,7 @@ public function getInterfacesOfClass(?string $class, bool $direct = false): arra * @param string|null $source the source name * @param bool $direct flag to find only the actual class traits * - * @return array map of class => definition pairs of traits + * @return array map of class => definition pairs of traits */ public function getTraitsOfClass(?string $source, bool $direct = false): array { @@ -298,6 +385,9 @@ public function getTraitsOfClass(?string $source, bool $direct = false): array $definitions = []; foreach ($sources as $sourze) { + if (null === $sourze) { + continue; + } if (isset($this->classes[$sourze]) || isset($this->traits[$sourze])) { $definition = $this->classes[$sourze] ?? $this->traits[$sourze]; if (isset($definition['traits'])) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php b/upload/api/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php index eac1c542..ee94f6df 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php +++ b/upload/api/vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php @@ -41,7 +41,7 @@ abstract class AbstractAnnotation implements \JsonSerializable /** * Annotations that couldn't be merged by mapping or postprocessing. * - * @var array + * @var list */ public $_unmerged = []; @@ -81,7 +81,7 @@ abstract class AbstractAnnotation implements \JsonSerializable /** * Reverse mapping of $_nested with the allowed parent annotations. * - * @var array> + * @var list> */ public static $_parents = []; diff --git a/upload/api/vendor/zircote/swagger-php/src/Annotations/Components.php b/upload/api/vendor/zircote/swagger-php/src/Annotations/Components.php index 85061a5a..ef3d9862 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Annotations/Components.php +++ b/upload/api/vendor/zircote/swagger-php/src/Annotations/Components.php @@ -139,10 +139,12 @@ public function jsonSerialize(): \stdClass * Returns a list of component annotation types. * * Each may be used as a root to resolve component refs + * + * @return list> */ public static function componentTypes(): array { - return array_filter(array_keys(self::$_nested), static fn (string $value): bool => $value !== Attachable::class); + return array_values(array_filter(array_keys(self::$_nested), static fn (string $value): bool => $value !== Attachable::class)); } /** @@ -156,8 +158,8 @@ 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)) { + // exclude attachables; the [property, key] form, a bare string is a single slot + if (is_array($nested) && 2 === count($nested)) { if ($component instanceof $type) { $type = $nested[0]; $name = $component->{$nested[1]}; diff --git a/upload/api/vendor/zircote/swagger-php/src/Annotations/JsonSchemaTrait.php b/upload/api/vendor/zircote/swagger-php/src/Annotations/JsonSchemaTrait.php index efd62c2a..953d0ac3 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Annotations/JsonSchemaTrait.php +++ b/upload/api/vendor/zircote/swagger-php/src/Annotations/JsonSchemaTrait.php @@ -12,6 +12,7 @@ * 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) + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema */ trait JsonSchemaTrait { @@ -111,7 +112,7 @@ trait JsonSchemaTrait * A property instance is valid against this attribute if its value is one of the values specified in this * list. * - * @var list|class-string + * @var list|class-string */ public $enum = Undefined::UNDEFINED; @@ -234,7 +235,7 @@ trait JsonSchemaTrait * ** TYPE-HINTS: - * @param list|class-string|null $enum + * @param list|class-string|null $enum ** PARAMETERS: diff --git a/upload/api/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php b/upload/api/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php index 303f32cd..409cc050 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php +++ b/upload/api/vendor/zircote/swagger-php/src/Annotations/ServerVariable.php @@ -14,6 +14,7 @@ * @see [Server Variable Object](https://spec.openapis.org/oas/v3.1.1.html#server-variable-object) * * @Annotation + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema */ class ServerVariable extends AbstractAnnotation { @@ -27,7 +28,9 @@ class ServerVariable extends AbstractAnnotation /** * An enumeration of values to be used if the substitution options are from a limited set. * - * @var list|class-string + * All allowed types are cast to string in the specification; a bool is dropped with a warning. + * + * @var list|string */ public $enum = Undefined::UNDEFINED; diff --git a/upload/api/vendor/zircote/swagger-php/src/Assembler/AbstractAttributeTranslator.php b/upload/api/vendor/zircote/swagger-php/src/Assembler/AbstractAttributeTranslator.php index 405e9c26..1eb4d06f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Assembler/AbstractAttributeTranslator.php +++ b/upload/api/vendor/zircote/swagger-php/src/Assembler/AbstractAttributeTranslator.php @@ -30,10 +30,12 @@ public function getAttributes(\ReflectionClass|\ReflectionMethod|\ReflectionProp } /** - * @param array $attributes current attributes - * @param array $created newly created attribute instances - * @param AttributeReflector $reflector - * @return array + * @param array $attributes current attributes; a previous translator's + * output, so not yet narrowed to AttributeInterface + * @param array $created newly created attribute instances + * @param AttributeReflector $reflector + * @return array attributes to carry forward; AttributeFactory filters + * the final result down to AttributeInterface */ public function translate(array $attributes, array $created, \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): array { diff --git a/upload/api/vendor/zircote/swagger-php/src/Assembler/OptionalPropertyAttributeTranslator.php b/upload/api/vendor/zircote/swagger-php/src/Assembler/OptionalPropertyAttributeTranslator.php index 8d4c0436..d5dd78a9 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Assembler/OptionalPropertyAttributeTranslator.php +++ b/upload/api/vendor/zircote/swagger-php/src/Assembler/OptionalPropertyAttributeTranslator.php @@ -23,10 +23,12 @@ class OptionalPropertyAttributeTranslator extends AbstractAttributeTranslator { /** - * @param array $attributes current attributes - * @param array $created newly created attribute instances - * @param AttributeReflector $reflector - * @return array + * @param array $attributes current attributes; a previous translator's + * output, so not yet narrowed to AttributeInterface + * @param array $created newly created attribute instances + * @param AttributeReflector $reflector + * @return array attributes to carry forward; AttributeFactory filters + * the final result down to AttributeInterface */ public function translate(array $attributes, array $created, \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): array { diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php index 29e26391..bfae5653 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/AdditionalProperties.php @@ -9,21 +9,24 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_CLASS)] class AdditionalProperties extends OA\AdditionalProperties { /** - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( // Schema diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/Items.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/Items.php index fdffd7b1..c89a3cdf 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/Items.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/Items.php @@ -9,21 +9,24 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Items extends OA\Items { /** - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( // Schema diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/JsonContent.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/JsonContent.php index bfb88b02..85139c8f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/JsonContent.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/JsonContent.php @@ -29,23 +29,24 @@ * ``` * * @Annotation + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema */ #[\Attribute(\Attribute::TARGET_CLASS)] class JsonContent extends OA\JsonContent { /** - * @param list $encoding - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param list $encoding + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( ?array $encoding = null, diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/Property.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/Property.php index 08597b12..d92e207a 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/Property.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/Property.php @@ -9,21 +9,24 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::TARGET_CLASS_CONSTANT | \Attribute::IS_REPEATABLE)] class Property extends OA\Property { /** - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( ?string $property = null, diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/Schema.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/Schema.php index 2d5b02bb..a19e626f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/Schema.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/Schema.php @@ -9,21 +9,24 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class Schema extends OA\Schema { /** - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( // Schema diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php index 9cb500af..984f8021 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/ServerVariable.php @@ -9,13 +9,16 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_CLASS)] class ServerVariable extends OA\ServerVariable { /** - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param list|string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( ?string $serverVariable = null, diff --git a/upload/api/vendor/zircote/swagger-php/src/Attributes/XmlContent.php b/upload/api/vendor/zircote/swagger-php/src/Attributes/XmlContent.php index 1601973e..1df11ab1 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Attributes/XmlContent.php +++ b/upload/api/vendor/zircote/swagger-php/src/Attributes/XmlContent.php @@ -9,22 +9,25 @@ use OpenApi\Annotations as OA; use OpenApi\Undefined; +/** + * @phpstan-import-type EnumValue from \OpenApi\Spec\Schema + */ #[\Attribute(\Attribute::TARGET_CLASS)] class XmlContent extends OA\XmlContent { /** - * @param list $encoding - * @param string|class-string|object|null $ref - * @param list $required - * @param list $properties - * @param string|non-empty-array|null $type - * @param array $examples - * @param array $allOf - * @param array $anyOf - * @param array $oneOf - * @param list|class-string|null $enum - * @param array|null $x - * @param list|null $attachables + * @param list $encoding + * @param string|class-string|object|null $ref + * @param list $required + * @param list $properties + * @param string|non-empty-array|null $type + * @param array $examples + * @param array $allOf + * @param array $anyOf + * @param array $oneOf + * @param list|class-string|null $enum + * @param array|null $x + * @param list|null $attachables */ public function __construct( ?array $encoding = null, diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Enums.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Enums.php index a76a8a14..3a2ef305 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Enums.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Enums.php @@ -10,12 +10,16 @@ use OpenApi\Specification; use OpenApi\Utils\Config; use OpenApi\Utils\PipeInterface; +use OpenApi\Utils\ServerVariableEnum; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; /** * Expands PHP enums into schema enum values. * * For schemas attached to a PHP enum, determines schema name, type, and enum values. - * Also resolves UnitEnum instances and enum class-strings in any schema's enum array. + * Also resolves UnitEnum instances and enum class-strings in any schema's or server + * variable's enum array. * * Rules for name vs. value: * - Unit enums (not backed): always use case names, type becomes "string" @@ -26,8 +30,10 @@ * * @implements PipeInterface */ -class Enums implements PipeInterface +class Enums implements PipeInterface, LoggerAwareInterface { + use LoggerAwareTrait; + public function __construct( #[Config('If set, stores enum case names in a vendor extension with this key (e.g. x-enum-varnames).')] protected ?string $enumNames = null, @@ -62,18 +68,20 @@ protected function expandEnumSchemas(Specification $specification): void continue; } - $reflector = new \ReflectionEnum($reflector->getName()); + /** @var class-string<\UnitEnum> $enumName isEnum() above is the guarantee */ + $enumName = $reflector->getName(); + $reflector = new \ReflectionEnum($enumName); $schema->schema ??= $reflector->getShortName(); $useName = $this->shouldUseName($schema, $reflector); - $schema->enum = array_map( + $schema->enum = array_values(array_map( static fn (\ReflectionEnumUnitCase $case): int|string => ($useName || !$case instanceof \ReflectionEnumBackedCase) ? $case->name : $case->getBackingValue(), $reflector->getCases(), - ); + )); if ($useName) { $schema->type = 'string'; @@ -112,24 +120,44 @@ protected function shouldUseName(OA\Schema $schema, \ReflectionEnum $reflector): protected function resolveEnumValues(Specification $specification): void { $specification->getWalker()->visit(OA\Schema::class, function (OA\Schema $schema): void { - if ($schema->enum === null) { - return; + if ($schema->enum !== null) { + $schema->enum = $this->resolveEnumArray($schema->enum); } + }); + + $specification->getWalker()->visit(OA\ServerVariable::class, function (OA\ServerVariable $variable): void { + if ($variable->enum !== null) { + // resolveEnumArray() is shared with Schema, whose enum may hold any JSON value. + // Only a server variable's is narrowed, and only after the cases are resolved. + $variable->enum = ServerVariableEnum::asStrings( + $this->resolveEnumArray($variable->enum), + $this->logger, + $variable->serverVariable + ); + } + }); + } - $resolved = []; - foreach ($schema->enum as $value) { - if ($value instanceof \UnitEnum) { - $resolved[] = $value instanceof \BackedEnum ? $value->value : $value->name; - } elseif (is_string($value) && function_exists('enum_exists') && enum_exists($value)) { - foreach ($value::cases() as $case) { - $resolved[] = $case instanceof \BackedEnum ? $case->value : $case->name; - } - } else { - $resolved[] = $value; + /** + * @param list $enum + * + * @return list + */ + protected function resolveEnumArray(array $enum): array + { + $resolved = []; + foreach ($enum as $value) { + if ($value instanceof \UnitEnum) { + $resolved[] = $value instanceof \BackedEnum ? $value->value : $value->name; + } elseif (is_string($value) && function_exists('enum_exists') && enum_exists($value)) { + foreach ($value::cases() as $case) { + $resolved[] = $case instanceof \BackedEnum ? $case->value : $case->name; } + } else { + $resolved[] = $value; } + } - $schema->enum = $resolved; - }); + return $resolved; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Inheritance/Operations.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Inheritance/Operations.php index 283cf2ff..ee51b8b8 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Inheritance/Operations.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Inheritance/Operations.php @@ -34,7 +34,7 @@ public function __construct( public function resolve(Specification $specification): mixed { - $pathItemClasses = $this->indexPathItemClasses($specification); + $pathItemClasses = $specification->buildPathItemHierarchy()->classes(); if ($pathItemClasses === []) { return null; @@ -47,27 +47,10 @@ public function resolve(Specification $specification): mixed return null; } - /** - * @return array - */ - protected function indexPathItemClasses(Specification $specification): array - { - $map = []; - - foreach ($specification->pathItems as $pathItem) { - $className = $pathItem->getClassName(); - if ($className !== null) { - $map[$className] = true; - } - } - - return $map; - } - /** * Find operations whose owning class has no PathItem — these are inheritable. * - * @param array $pathItemClasses + * @param array $pathItemClasses * @return array> Grouped by declaring class */ protected function findInheritableOperations(Specification $specification, array $pathItemClasses): array @@ -89,7 +72,7 @@ protected function findInheritableOperations(Specification $specification, array /** * For each PathItem class, walk ancestors and clone or discover inheritable operations. * - * @param array $pathItemClasses + * @param array $pathItemClasses * @param array> $ancestorOperations Already-assembled operations */ protected function cloneToChildren(Specification $specification, array $pathItemClasses, array $ancestorOperations): void diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/MediaTypes.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/MediaTypes.php index fb26945a..1b9d8eb3 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/MediaTypes.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/MediaTypes.php @@ -43,8 +43,13 @@ protected function mergePropertyEncodings(Specification $specification, Componen return; } - if ($mediaType->schema->ref !== null) { - $refSchema = $index->findSchema($mediaType->schema->ref); + // Refs runs in Group::Resolve, before this augmenter, so a Schema\Ref object has + // already been collapsed into its string; narrow defensively rather than assume + $ref = $mediaType->schema->ref instanceof OA\Schema\Ref + ? $mediaType->schema->ref->ref + : $mediaType->schema->ref; + if (is_string($ref)) { + $refSchema = $index->findSchema($ref); if ($refSchema instanceof OA\Schema && $refSchema->properties !== null) { $this->mergeEncoded($mediaType, $refSchema->properties); } diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/PathItems.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/PathItems.php index 24f6ae28..7b3b06ff 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/PathItems.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/PathItems.php @@ -8,15 +8,18 @@ use OpenApi\Spec as OA; use OpenApi\Specification; +use OpenApi\Specification\PathItemHierarchy; use OpenApi\Utils\PipeInterface; /** * Resolves PathItem prefixes, clones metadata to operations, and sets path-level output. * - * Walks the class hierarchy to compose path prefixes from ancestor PathItems, - * prepends them to operation paths, clones tags/security/responses to operations - * that don't declare their own, and marks PathItems that have spec-level output - * (parameters, summary, description, servers) with their resolved path. + * Composes path prefixes from the PathItems governing each operation's class, prepends them + * to operation paths, clones tags/security/responses to operations that don't declare their + * own, and marks PathItems that have spec-level output (parameters, summary, description, + * servers) with their resolved path. + * + * The ancestor walk itself belongs to `Specification\PathItemHierarchy`. * * @implements PipeInterface */ @@ -24,35 +27,28 @@ class PathItems implements PipeInterface { public function __invoke(mixed $payload): mixed { - $classToPathItem = $this->indexPathItems($payload); + $hierarchy = $payload->buildPathItemHierarchy(); - if ($classToPathItem === []) { + if ($hierarchy->classes() === []) { return null; } - $prefixCache = []; - foreach ($payload->operations as $operation) { - $className = $operation->getClassName(); - if ($className === null) { - continue; - } - - $pathItem = $this->findGoverningPathItem($className, $classToPathItem); - if (!$pathItem instanceof OA\PathItem) { + $chain = $hierarchy->forOperation($operation); + if ($chain === []) { continue; } - $prefix = $this->resolvePrefix($pathItem, $classToPathItem, $prefixCache); + $prefix = $this->resolvePrefix($chain); if ($prefix !== '' && $operation->path !== null) { $operationPath = ltrim($operation->path, '/'); $operation->path = $operationPath !== '' ? $prefix . '/' . $operationPath : $prefix; } - $this->cloneMetadata($pathItem, $operation, $classToPathItem); + $this->cloneMetadata($chain, $operation); } - $this->resolvePathItemPaths($payload, $classToPathItem, $prefixCache); + $this->resolvePathItemPaths($payload, $hierarchy); return null; } @@ -63,89 +59,28 @@ public function group(): string|\BackedEnum } /** - * @return array - */ - protected function indexPathItems(Specification $specification): array - { - $map = []; - - foreach ($specification->pathItems as $pathItem) { - $className = $pathItem->getClassName(); - if ($className !== null) { - $map[$className] = $pathItem; - } - } - - return $map; - } - - /** - * @param array $classToPathItem - * @param array $cache + * Compose the path prefix declared across a chain, outermost first. + * + * @param list $chain */ - protected function resolvePrefix(OA\PathItem $pathItem, array $classToPathItem, array &$cache): string + protected function resolvePrefix(array $chain): string { - $reflector = $pathItem->getClassReflector(); - if (!$reflector instanceof \ReflectionClass) { - return $pathItem->prefix ?? ''; - } - - $className = $reflector->getName(); - if (isset($cache[$className])) { - return $cache[$className]; - } - $parts = []; - $current = $reflector; - while ($current !== false) { - if (isset($classToPathItem[$current->getName()])) { - $pi = $classToPathItem[$current->getName()]; - if ($pi->prefix !== null) { - $parts[] = trim($pi->prefix, '/'); - } + foreach ($chain as $pathItem) { + if ($pathItem->prefix !== null && ($part = trim($pathItem->prefix, '/')) !== '') { + $parts[] = $part; } - $current = $current->getParentClass(); } - $parts = array_filter(array_reverse($parts), static fn (string $p): bool => $p !== ''); - $prefix = $parts !== [] ? '/' . implode('/', $parts) : ''; - $cache[$className] = $prefix; - - return $prefix; + return $parts !== [] ? '/' . implode('/', $parts) : ''; } /** - * @param array $classToPathItem + * @param list $chain */ - protected function findGoverningPathItem(string $className, array $classToPathItem): ?OA\PathItem + protected function cloneMetadata(array $chain, OA\Operation $operation): void { - if (isset($classToPathItem[$className])) { - return $classToPathItem[$className]; - } - - try { - $rc = new \ReflectionClass($className); - } catch (\ReflectionException) { - return null; - } - - $parent = $rc->getParentClass(); - while ($parent !== false) { - if (isset($classToPathItem[$parent->getName()])) { - return $classToPathItem[$parent->getName()]; - } - $parent = $parent->getParentClass(); - } - - return null; - } - - /** - * @param array $classToPathItem - */ - protected function cloneMetadata(OA\PathItem $pathItem, OA\Operation $operation, array $classToPathItem): void - { - $merged = $this->collectMergedMetadata($pathItem, $classToPathItem); + $merged = $this->collectMergedMetadata($chain); if ($merged['tags'] !== null) { $operation->tags = array_values(array_unique([...$operation->tags ?? [], ...$merged['tags']])); @@ -182,42 +117,28 @@ protected function cloneMetadata(OA\PathItem $pathItem, OA\Operation $operation, } /** - * Collect merged metadata walking up the class hierarchy. + * Collect merged metadata across a chain. * All collections merge additively — tags, security, and responses accumulate from all ancestors. * - * @param array $classToPathItem + * @param list $chain * @return array{tags: list|null, security: list|null, responses: list|null} */ - protected function collectMergedMetadata(OA\PathItem $pathItem, array $classToPathItem): array + protected function collectMergedMetadata(array $chain): array { - $reflector = $pathItem->getClassReflector(); - if (!$reflector instanceof \ReflectionClass) { - return [ - 'tags' => $pathItem->tags, - 'security' => $pathItem->security, - 'responses' => $pathItem->responses, - ]; - } - $tags = []; $security = []; $responses = []; - $current = $reflector; - while ($current !== false) { - $pi = $classToPathItem[$current->getName()] ?? null; - if ($pi !== null) { - if ($pi->tags !== null) { - array_push($tags, ...$pi->tags); - } - if ($pi->security !== null) { - array_push($security, ...$pi->security); - } - if ($pi->responses !== null) { - array_push($responses, ...$pi->responses); - } + foreach ($chain as $pathItem) { + if ($pathItem->tags !== null) { + array_push($tags, ...$pathItem->tags); + } + if ($pathItem->security !== null) { + array_push($security, ...$pathItem->security); + } + if ($pathItem->responses !== null) { + array_push($responses, ...$pathItem->responses); } - $current = $current->getParentClass(); } return [ @@ -227,20 +148,18 @@ protected function collectMergedMetadata(OA\PathItem $pathItem, array $classToPa ]; } - /** - * @param array $classToPathItem - * @param array $prefixCache - */ - protected function resolvePathItemPaths(Specification $specification, array $classToPathItem, array $prefixCache): void + protected function resolvePathItemPaths(Specification $specification, PathItemHierarchy $hierarchy): void { + $pathsByPathItem = $this->collectOperationPaths($specification, $hierarchy); + foreach ($specification->pathItems as $pathItem) { - $this->mergeAncestorParameters($pathItem, $classToPathItem); + $this->mergeAncestorParameters($pathItem, $hierarchy); if (!$this->hasSpecProperties($pathItem)) { continue; } - $paths = $this->findOperationPaths($pathItem, $specification, $classToPathItem); + $paths = $pathsByPathItem[spl_object_id($pathItem)] ?? []; if ($paths === []) { continue; @@ -259,71 +178,67 @@ protected function resolvePathItemPaths(Specification $specification, array $cla } /** - * @param array $classToPathItem + * The operation paths each PathItem governs, collected in one pass over the operations. + * + * @return array> keyed by `spl_object_id()` of the governing PathItem */ - protected function mergeAncestorParameters(OA\PathItem $pathItem, array $classToPathItem): void + protected function collectOperationPaths(Specification $specification, PathItemHierarchy $hierarchy): array { - $reflector = $pathItem->getClassReflector(); - if (!$reflector instanceof \ReflectionClass) { - return; - } + $paths = []; - $parent = $reflector->getParentClass(); - while ($parent !== false) { - $ancestorPi = $classToPathItem[$parent->getName()] ?? null; - if ($ancestorPi !== null && $ancestorPi->parameters !== null) { - $existingKeys = []; - foreach ($pathItem->parameters ?? [] as $param) { - $existingKeys[$param->name . ':' . ($param->in ?? '')] = true; - } + foreach ($specification->operations as $operation) { + if ($operation->path === null) { + continue; + } - foreach ($ancestorPi->parameters as $param) { - if (!isset($existingKeys[$param->name . ':' . ($param->in ?? '')])) { - $pathItem->parameters ??= []; - $pathItem->parameters[] = $param; - } - } + $chain = $hierarchy->forOperation($operation); + if ($chain === []) { + continue; + } + + $governing = spl_object_id($chain[count($chain) - 1]); + if (!in_array($operation->path, $paths[$governing] ?? [], true)) { + $paths[$governing][] = $operation->path; } - $parent = $parent->getParentClass(); } - } - protected function hasSpecProperties(OA\PathItem $pathItem): bool - { - return $pathItem->parameters !== null - || $pathItem->summary !== null - || $pathItem->description !== null - || $pathItem->servers !== null; + return $paths; } - /** - * @param array $classToPathItem - * @return list - */ - protected function findOperationPaths(OA\PathItem $pathItem, Specification $specification, array $classToPathItem): array + protected function mergeAncestorParameters(OA\PathItem $pathItem, PathItemHierarchy $hierarchy): void { - if (!$pathItem->getClassReflector() instanceof \ReflectionClass) { - return []; + $reflector = $pathItem->getClassReflector(); + if (!$reflector instanceof \ReflectionClass) { + return; } - $paths = []; + // the item's own parameters are what the ancestors merge into, so its own entry drops out + $ancestors = array_slice($hierarchy->chainFor($reflector), 0, -1); - foreach ($specification->operations as $operation) { - if ($operation->path === null) { + foreach (array_reverse($ancestors) as $ancestorPathItem) { + if ($ancestorPathItem->parameters === null) { continue; } - $opClass = $operation->getClassName(); - if ($opClass === null) { - continue; + $existingKeys = []; + foreach ($pathItem->parameters ?? [] as $param) { + $existingKeys[$param->name . ':' . ($param->in ?? '')] = true; } - $governing = $this->findGoverningPathItem($opClass, $classToPathItem); - if ($governing === $pathItem && !in_array($operation->path, $paths, true)) { - $paths[] = $operation->path; + foreach ($ancestorPathItem->parameters as $param) { + if (!isset($existingKeys[$param->name . ':' . ($param->in ?? '')])) { + $pathItem->parameters ??= []; + $pathItem->parameters[] = $param; + } } } + } - return $paths; + protected function hasSpecProperties(OA\PathItem $pathItem): bool + { + return $pathItem->parameters !== null + || $pathItem->summary !== null + || $pathItem->description !== null + || $pathItem->servers !== null; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Refs.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Refs.php index 7e060b1f..715c8a28 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Refs.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Refs.php @@ -184,11 +184,14 @@ protected function dedupAllOfRefs(OA\Schema $schema): void $unique = []; foreach ($schema->allOf as $ii => $allOf) { - if ($allOf->ref !== null) { - if (isset($unique[$allOf->ref])) { + // resolveRefRefs() has collapsed every Schema\Ref into its string by now, but only + // runs when the ref map is non-empty; key on the string form defensively + $ref = $allOf->ref instanceof OA\Schema\Ref ? $allOf->ref->ref : $allOf->ref; + if (is_string($ref)) { + if (isset($unique[$ref])) { continue; } - $unique[$allOf->ref] = $allOf; + $unique[$ref] = $allOf; } else { $unique[$ii] = $allOf; } diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Shortcuts.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Shortcuts.php index 70ed5648..7193ee59 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Shortcuts.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Shortcuts.php @@ -24,8 +24,6 @@ */ class Shortcuts implements PipeInterface { - private const MEDIA_TYPE_SCHEMA_PROPERTIES = ['ref', 'type', 'items', 'properties', 'required']; - private const ITEMS_KEEP_PROPERTIES = ['schema', 'title', 'description', 'deprecated', 'readOnly', 'writeOnly', 'xml', 'externalDocs', 'x', 'attachables']; public function __invoke(mixed $payload): mixed @@ -56,22 +54,49 @@ protected function processMediaTypes(Specification $specification): void protected function processMediaType(OA\MediaType\Json|OA\MediaType\Xml $mediaType): void { + // spelled out per property rather than looped over a list of property names: the + // shortcut properties mirror OA\Schema's one for one, but a dynamic + // `$x->{$prop} = $y->{$prop}` makes that invisible — phpstan can only see the union + // of all five types on each side, so every assignment looks like a type error if (!$mediaType->schema instanceof OA\Schema) { - $args = []; - foreach (self::MEDIA_TYPE_SCHEMA_PROPERTIES as $prop) { - if ($mediaType->{$prop} !== null) { - $args[$prop] = $mediaType->{$prop}; - $mediaType->{$prop} = null; - } - } - $mediaType->schema = new OA\Schema(...$args); - } else { - foreach (self::MEDIA_TYPE_SCHEMA_PROPERTIES as $prop) { - if ($mediaType->{$prop} !== null && $mediaType->schema->{$prop} === null) { - $mediaType->schema->{$prop} = $mediaType->{$prop}; - $mediaType->{$prop} = null; - } - } + $mediaType->schema = new OA\Schema( + ref: $mediaType->ref, + type: $mediaType->type, + items: $mediaType->items, + properties: $mediaType->properties, + required: $mediaType->required, + ); + $mediaType->ref = null; + $mediaType->type = null; + $mediaType->items = null; + $mediaType->properties = null; + $mediaType->required = null; + + return; + } + + // only a shortcut the schema has no value for is moved across; one the schema already + // sets is left on the media type untouched, as it was before + $schema = $mediaType->schema; + if ($mediaType->ref !== null && $schema->ref === null) { + $schema->ref = $mediaType->ref; + $mediaType->ref = null; + } + if ($mediaType->type !== null && $schema->type === null) { + $schema->type = $mediaType->type; + $mediaType->type = null; + } + if ($mediaType->items !== null && $schema->items === null) { + $schema->items = $mediaType->items; + $mediaType->items = null; + } + if ($mediaType->properties !== null && $schema->properties === null) { + $schema->properties = $mediaType->properties; + $mediaType->properties = null; + } + if ($mediaType->required !== null && $schema->required === null) { + $schema->required = $mediaType->required; + $mediaType->required = null; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Types.php b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Types.php index 4748e5f9..cc590a35 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Augmenter/Types.php +++ b/upload/api/vendor/zircote/swagger-php/src/Augmenter/Types.php @@ -239,10 +239,10 @@ protected function augmentMethodProperty(OA\Property $property, \ReflectionMetho } if (!$property->schema instanceof OA\Schema) { - $property->schema = new OA\Schema(); + $property->schema = $this->schemaTypeToSchema($resolved); + } else { + $this->mergeIntoSchema($property->schema, $resolved); } - - $this->applySchemaType($property->schema, $resolved); } protected function augmentOperationParameters(OA\Operation $operation): void diff --git a/upload/api/vendor/zircote/swagger-php/src/Builder.php b/upload/api/vendor/zircote/swagger-php/src/Builder.php index c71db729..47657fb4 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Builder.php +++ b/upload/api/vendor/zircote/swagger-php/src/Builder.php @@ -11,6 +11,7 @@ use OpenApi\Contracts\CompilerInterface; use OpenApi\Loggers\CollectingLogger; use OpenApi\Utils\AttributeFactory; +use OpenApi\Utils\ClassReflector; use OpenApi\Utils\PipeInterface; use OpenApi\Utils\SourceScanner; use Psr\Log\LoggerInterface; @@ -40,7 +41,7 @@ class Builder /** * @var list> */ - protected string|\SplFileInfo|\Reflector|iterable $sources = []; + protected array $sources = []; protected Mode $mode = Mode::CLASSIC; @@ -238,15 +239,22 @@ protected function doBuildSpec(bool $hybrid = false): Result foreach ($sourceScanner->getFiles() as $file) { foreach (array_keys($tokenScanner->scanFile($file)) as $class) { - if (class_exists($class) || interface_exists($class) || enum_exists($class) || trait_exists($class)) { - $assembler->collect(new \ReflectionClass($class)); + [$rc, $reason] = ClassReflector::tryReflect($class); + if (!$rc instanceof \ReflectionClass) { + $this->getLogger()->warning($reason === null + ? 'Skipping unknown ' . $class + : "Skipping unloadable {$class}: {$reason}"); + + continue; } + + $this->collectSafely($assembler, $rc); } } foreach ($sourceScanner->getReflectors() as $reflector) { if ($reflector instanceof \ReflectionClass) { - $assembler->collect($reflector); + $this->collectSafely($assembler, $reflector); } } @@ -274,6 +282,18 @@ protected function doBuildSpec(bool $hybrid = false): Result return Result::fromSpec($sourceScanner->getFiles(), $specification, $output, $diagnostics); } + /** + * @param \ReflectionClass $reflector + */ + protected function collectSafely(Assembler $assembler, \ReflectionClass $reflector): void + { + try { + $assembler->collect($reflector); + } catch (\Throwable $throwable) { + $this->getLogger()->warning("Skipping unloadable {$reflector->getName()}: {$throwable->getMessage()}"); + } + } + protected function doHybridAssemble(Specification $specification): void { $collectingLogger = new CollectingLogger($this->getLogger()); @@ -318,7 +338,7 @@ protected function resolveCompiler(string $version): CompilerInterface } /** - * @return list + * @return list> */ protected function getDefaultAugmenters(): array { diff --git a/upload/api/vendor/zircote/swagger-php/src/Compiler/OpenApi31Compiler.php b/upload/api/vendor/zircote/swagger-php/src/Compiler/OpenApi31Compiler.php index 26846150..721ebdc8 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Compiler/OpenApi31Compiler.php +++ b/upload/api/vendor/zircote/swagger-php/src/Compiler/OpenApi31Compiler.php @@ -347,7 +347,7 @@ protected function compileParameter(OA\Parameter $parameter): array return ['$ref' => $parameter->ref]; } - return $this->filter([ + $result = $this->filter([ 'name' => $parameter->name, 'in' => $parameter->in, 'description' => $parameter->description, @@ -358,10 +358,11 @@ protected function compileParameter(OA\Parameter $parameter): array 'explode' => $parameter->explode, 'allowReserved' => $parameter->allowReserved, 'schema' => $parameter->schema instanceof OA\Schema ? $this->compileSchema($parameter->schema) : null, - 'example' => $parameter->example, 'examples' => $this->compileKeyedMap($parameter->examples ?? [], 'example', $this->compileExample(...)), 'content' => $this->compileMediaTypes($parameter->content ?? []), ], $parameter); + + return $this->withDefined($result, ['example' => $parameter->example]); } /** @@ -421,17 +422,18 @@ protected function compileHeader(OA\Header $header): array return ['$ref' => $header->ref]; } - return $this->filter([ + $result = $this->filter([ 'description' => $header->description, 'required' => $header->required, 'deprecated' => $header->deprecated, 'style' => $header->style, 'explode' => $header->explode, 'schema' => $header->schema instanceof OA\Schema ? $this->compileSchema($header->schema) : null, - 'example' => $header->example, 'examples' => $this->compileKeyedMap($header->examples ?? [], 'example', $this->compileExample(...)), 'content' => $this->compileMediaTypes($header->content ?? []), ], $header); + + return $this->withDefined($result, ['example' => $header->example]); } /** @@ -448,12 +450,13 @@ protected function compileMediaTypes(array $mediaTypes): array */ protected function compileMediaType(OA\MediaType $mediaType): array { - return $this->filter([ + $result = $this->filter([ 'schema' => $mediaType->schema instanceof OA\Schema ? $this->compileSchema($mediaType->schema) : null, - 'example' => $mediaType->example, 'examples' => $this->compileKeyedMap($mediaType->examples ?? [], 'example', $this->compileExample(...)), 'encoding' => $this->compileKeyedMap($mediaType->encoding ?? [], 'encoding', $this->compileEncoding(...)), ], $mediaType); + + return $this->withDefined($result, ['example' => $mediaType->example]); } /** @@ -487,11 +490,7 @@ protected function compileLink(OA\Link $link): array 'server' => $link->server instanceof OA\Server ? $this->compileServer($link->server) : null, ], $link); - if ($link->requestBody !== Undefined::UNDEFINED) { - $result['requestBody'] = $link->requestBody; - } - - return $result; + return $this->withDefined($result, ['requestBody' => $link->requestBody]); } /** @@ -603,15 +602,11 @@ protected function compileSchema(OA\Schema|string $schema): array|\stdClass 'xml' => $schema->xml instanceof OA\Xml ? $this->compileXml($schema->xml) : null, ], $schema); - if ($schema->default !== Undefined::UNDEFINED) { - $result['default'] = $schema->default; - } - if ($schema->const !== Undefined::UNDEFINED) { - $result['const'] = $schema->const; - } - if ($schema->example !== Undefined::UNDEFINED) { - $result['example'] = $schema->example; - } + $result = $this->withDefined($result, [ + 'default' => $schema->default, + 'const' => $schema->const, + 'example' => $schema->example, + ]); return $result ?: new \stdClass(); } @@ -769,11 +764,7 @@ protected function compileExample(OA\Example $example): array 'externalValue' => $example->externalValue, ], $example); - if ($example->value !== Undefined::UNDEFINED) { - $result['value'] = $example->value; - } - - return $result; + return $this->withDefined($result, ['value' => $example->value]); } /** @@ -1003,7 +994,8 @@ protected function compileComponentMap(array $components, \Closure $compiler): a * the map into a JSON array, and OpenAPI requires `Map[string, Object]` everywhere one of * these appears. `validateNestedNames()` reports it. * - * @param list $items + * @param array $items only the values are read, so the caller's keys — a + * list, or a name-keyed map — make no difference here * @return array */ protected function compileKeyedMap(array $items, string $keyField, \Closure $compiler): array @@ -1105,4 +1097,27 @@ protected function filter(array $result, OA\AbstractAttribute|null $attribute = return $result; } + + /** + * Adds each of $values to $result unless it was left at its Undefined-sentinel default. + * + * For fields whose meaningful values overlap what {@see filter()} treats as "unset" + * (`null`, `[]`) — `example` is the case in point, since an explicitly declared `[]` is + * valid and distinct from not declaring it at all — bypass filter() and gate on the + * sentinel directly instead. + * + * @param array $result + * @param array $values + * @return array + */ + protected function withDefined(array $result, array $values): array + { + foreach ($values as $key => $value) { + if (!Undefined::isDefault($value)) { + $result[$key] = $value; + } + } + + return $result; + } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Console/GenerateCommand.php b/upload/api/vendor/zircote/swagger-php/src/Console/GenerateCommand.php index f3fc958b..f083f590 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Console/GenerateCommand.php +++ b/upload/api/vendor/zircote/swagger-php/src/Console/GenerateCommand.php @@ -12,7 +12,10 @@ use OpenApi\Utils\Pipeline; use OpenApi\Utils\SourceFinder; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Attribute\MapInput; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -21,18 +24,40 @@ name: 'openapi', description: 'Generate OpenAPI documentation', )] -class GenerateCommand +class GenerateCommand extends Command { public function __construct( private ConsoleLogger $logger, ) { + parent::__construct(); } - public function __invoke(#[MapInput] GenerateInput $input, SymfonyStyle $io): int + protected function configure(): void { - $io->setVerbosity($input->debug ? OutputInterface::VERBOSITY_DEBUG : $io->getVerbosity()); + $this + ->addArgument('paths', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Source path(s) to scan') + ->addOption('config', 'c', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Generator/Augmenter config; keys differ per mode, see -D (e.g. -c operationId.hash=false)') + ->addOption('defaults', 'D', InputOption::VALUE_NONE, 'Show default config') + ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Path to store the generated documentation (e.g. -o openapi.yaml)') + ->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Force yaml or json', GenerateFormat::AUTO->value) + ->addOption('exclude', 'e', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Exclude path(s) (e.g. -e vendor -e library/Zend)') + ->addOption('pattern', 'n', InputOption::VALUE_REQUIRED, 'Pattern of files to scan (e.g. -n "/\.(phps|php)$/")', '*.php') + ->addOption('bootstrap', 'b', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Bootstrap php file(s) for defining constants, etc. (e.g. -b config/constants.php)') + ->addOption('add-processor', 'a', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Register an additional processor') + ->addOption('remove-processor', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Remove an existing processor') + ->addOption('version', null, InputOption::VALUE_REQUIRED, 'The OpenAPI version') + ->addOption('mode', 'm', InputOption::VALUE_REQUIRED, 'Set mode classic, hybrid or spec', Builder\Mode::CLASSIC->value) + ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Show additional error information'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $generateInput = GenerateInput::fromInput($input); + + $io->setVerbosity($generateInput->debug ? OutputInterface::VERBOSITY_DEBUG : $io->getVerbosity()); - foreach ($input->getBootstrapFilenames() as $filename) { + foreach ($generateInput->getBootstrapFilenames() as $filename) { if ($io->isVerbose()) { $io->info('Bootstrapping: ' . $filename); } @@ -40,28 +65,28 @@ public function __invoke(#[MapInput] GenerateInput $input, SymfonyStyle $io): in require_once $filename; } - if ($input->defaults) { + if ($generateInput->defaults) { $io->title('Default config'); - $io->writeln(json_encode($this->getDefaultConfig($input), JSON_PRETTY_PRINT)); + $io->writeln(json_encode($this->getDefaultConfig($generateInput), JSON_PRETTY_PRINT) ?: '{}'); return 0; } - $result = $this->generate($input); + $result = $this->generate($generateInput); - if (!$input->output) { - if ($input->format->isJson()) { + if (!$generateInput->output) { + if ($generateInput->format->isJson()) { echo $result->toJson(); } else { echo $result->toYaml(); } echo "\n"; } else { - $outputPath = $input->output; + $outputPath = $generateInput->output; if (is_dir($outputPath)) { $outputPath .= '/openapi.yaml'; } - $result->saveAs($outputPath, $input->format->value); + $result->saveAs($outputPath, $generateInput->format->value); } return $this->logger->hasErrored() ? 1 : 0; @@ -96,7 +121,10 @@ protected function generate(GenerateInput $input): Result if ($input->config || $input->addProcessor || $input->removeProcessor) { $builder->withGenerator(function (Generator $generator) use ($input): void { if ($input->config && $input->mode !== Builder\Mode::SPEC) { - $generator->setConfig($input->config); + // -c takes `key=value` strings; Generator::setConfig() normalises them + /** @var array $config */ + $config = $input->config; + $generator->setConfig($config); } foreach ($input->addProcessor as $processor) { @@ -106,14 +134,20 @@ protected function generate(GenerateInput $input): Result } elseif (class_exists($processor)) { $processor = new $processor(); } - $generator->getProcessorPipeline()->add($processor); + // neither branch above matching leaves $processor as the raw -a string, + // which the pipeline cannot invoke; not validated here, as it never was + /** @var callable&object $pipe */ + $pipe = $processor; + $generator->getProcessorPipeline()->add($pipe); } foreach ($input->removeProcessor as $processor) { $class = class_exists($processor) ? $processor : '\OpenApi\Processors\\' . ucfirst((string) $processor); - $generator->getProcessorPipeline()->remove($class); + /** @var class-string $remove */ + $remove = $class; + $generator->getProcessorPipeline()->remove($remove); } }); } diff --git a/upload/api/vendor/zircote/swagger-php/src/Console/GenerateInput.php b/upload/api/vendor/zircote/swagger-php/src/Console/GenerateInput.php index 0df0be4c..5fbfa999 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Console/GenerateInput.php +++ b/upload/api/vendor/zircote/swagger-php/src/Console/GenerateInput.php @@ -7,51 +7,71 @@ namespace OpenApi\Console; use OpenApi\Builder\Mode; -use Symfony\Component\Console\Attribute\Argument; -use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; +use Symfony\Component\Console\Input\InputInterface; class GenerateInput { - #[Argument('Source path(s) to scan')] - public array $paths; + /** @var list */ + public array $paths = []; - #[Option('Generator/Augmenter config; keys differ per mode, see -D (e.g. -c operationId.hash=false)', shortcut: 'c')] + /** @var list */ public array $config = []; - #[Option('Show default config', shortcut: 'D')] public bool $defaults = false; - #[Option('Path to store the generated documentation (e.g. -o openapi.yaml)', shortcut: 'o')] public ?string $output = null; - #[Option('Force yaml or json', shortcut: 'f')] public GenerateFormat $format = GenerateFormat::AUTO; - #[Option('Exclude path(s) (e.g. -e vendor -e library/Zend)', shortcut: 'e')] + /** @var list */ public array $exclude = []; - #[Option('Pattern of files to scan (e.g. -n "/\.(phps|php)$/")', shortcut: 'n')] public string $pattern = '*.php'; - #[Option('Bootstrap php file(s) for defining constants, etc. (e.g. -b config/constants.php)', shortcut: 'b')] + /** @var list */ public array $bootstrap = []; - #[Option('Register an additional processor', shortcut: 'a')] + /** @var list */ public array $addProcessor = []; - #[Option('Remove an existing processor', shortcut: 'r')] + /** @var list */ public array $removeProcessor = []; - #[Option('The OpenAPI version')] public ?string $version = null; - #[Option('Set mode classic, hybrid or spec', shortcut: 'm')] public Mode $mode = Mode::CLASSIC; - #[Option('Show additional error information', shortcut: 'd')] public bool $debug = false; + /** + * Map the console input onto this data object. + * + * The console definition lives in {@see GenerateCommand::configure()}; the two + * are aligned by hand. Option names arrive kebab-cased, properties are camelCase. + */ + public static function fromInput(InputInterface $input): self + { + $generateInput = new self(); + + $generateInput->paths = $input->getArgument('paths'); + $generateInput->config = $input->getOption('config'); + $generateInput->defaults = (bool) $input->getOption('defaults'); + $generateInput->output = $input->getOption('output'); + $generateInput->format = self::enum(GenerateFormat::class, 'format', $input->getOption('format')); + $generateInput->exclude = $input->getOption('exclude'); + $generateInput->pattern = $input->getOption('pattern'); + $generateInput->bootstrap = $input->getOption('bootstrap'); + $generateInput->addProcessor = $input->getOption('add-processor'); + $generateInput->removeProcessor = $input->getOption('remove-processor'); + $generateInput->version = $input->getOption('version'); + $generateInput->mode = self::enum(Mode::class, 'mode', $input->getOption('mode')); + $generateInput->debug = (bool) $input->getOption('debug'); + + return $generateInput; + } + /** * @return iterable */ @@ -67,4 +87,29 @@ public function getBootstrapFilenames(): iterable yield from $filenames; } } + + /** + * Resolve a backed enum option, reporting unknown values the way Symfony does. + * + * @template T of \BackedEnum + * + * @param class-string $enum + * + * @return T + */ + protected static function enum(string $enum, string $name, string $value): \BackedEnum + { + $case = $enum::tryFrom($value); + + if ($case === null) { + throw new InvalidOptionException(sprintf( + 'The value "%s" is not valid for the "%s" option. Supported values are "%s".', + $value, + $name, + implode('", "', array_column($enum::cases(), 'value')) + )); + } + + return $case; + } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Context.php b/upload/api/vendor/zircote/swagger-php/src/Context.php index c6317c56..7b14dab5 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Context.php +++ b/upload/api/vendor/zircote/swagger-php/src/Context.php @@ -21,13 +21,11 @@ * @property int|null $line * @property int|null $character * @property string|null $namespace - * @property array|null $uses + * @property array|null $uses * @property string|null $class * @property string|null $interface * @property string|null $trait * @property string|null $enum - * @property array|string|null $extends Interfaces may extend a list of interfaces - * @property array|null $implements * @property string|null $method * @property string|null $property * @property \Reflector|null $reflector Optional reflection details @@ -36,9 +34,9 @@ * type resolver or serializer * @property OA\AbstractAnnotation|null $nested * @property list|null $annotations - * @property list|null $other Annotations not related to OpenApi + * @property list|null $other Annotations not related to OpenApi * @property LoggerInterface|null $logger Guaranteed to be set when using the Generator - * @property array|null $scanned Details of file scanner when using ReflectionAnalyser + * @property array|null $scanned Details of file scanner when using ReflectionAnalyser * @property string|null $version The OpenAPI version in use */ #[\AllowDynamicProperties] @@ -49,6 +47,9 @@ class Context implements \Stringable */ protected ?Context $parent; + /** + * @param array $properties + */ public function __construct(array $properties = [], ?Context $parent = null) { foreach ($properties as $property => $value) { @@ -70,6 +71,9 @@ public function __serialize(): array }); } + /** + * @param array $data + */ public function __unserialize(array $data): void { foreach ($data as $name => $value) { @@ -178,7 +182,7 @@ public function getVersion(): string /** * Check if one of the given version numbers matches the current OpenAPI version. * - * @param string|array $version The version to compare. Allows patch version placeholder `x`; e.g. `3.1.x`. + * @param string|list $version The version to compare. Allows patch version placeholder `x`; e.g. `3.1.x`. */ public function isVersion(string|array $version): bool { @@ -242,13 +246,13 @@ public function fullyQualifiedName(?string $source): ?string $thisSource = $this->class ?? $this->interface ?? $this->trait; if ($thisSource && strcasecmp($source, $thisSource) === 0) { - return $namespace . $thisSource; + return $this->asClassString($namespace . $thisSource); } $pos = strpos($source, '\\'); if ($pos !== false) { if ($pos === 0) { // Fully qualified name (\Foo\Bar) - return $source; + return $this->asClassString($source); } // Qualified name (Foo\Bar) if ($this->uses) { @@ -256,7 +260,7 @@ public function fullyQualifiedName(?string $source): ?string $alias .= '\\'; if (strcasecmp(substr($source, 0, strlen($alias)), $alias) === 0) { // Aliased namespace (use \Long\Namespace as Foo) - return '\\' . $aliasedNamespace . substr($source, strlen($alias) - 1); + return $this->asClassString('\\' . $aliasedNamespace . substr($source, strlen($alias) - 1)); } } } @@ -264,11 +268,25 @@ public function fullyQualifiedName(?string $source): ?string // Unqualified name (Foo) foreach ($this->uses as $alias => $aliasedNamespace) { if (strcasecmp((string) $alias, $source) === 0) { - return '\\' . $aliasedNamespace; + return $this->asClassString('\\' . $aliasedNamespace); } } } - return $namespace . $source; + return $this->asClassString($namespace . $source); + } + + /** + * A resolved name is syntactically a class name; not verified to exist, since a source's + * fully qualified name may name a class not yet loaded (or not autoloadable at all). + * + * @return class-string + */ + private function asClassString(string $name): string + { + /** @var class-string $className */ + $className = $name; + + return $className; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Contracts/AttributeTranslatorInterface.php b/upload/api/vendor/zircote/swagger-php/src/Contracts/AttributeTranslatorInterface.php index f5fa6ba5..997a0293 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Contracts/AttributeTranslatorInterface.php +++ b/upload/api/vendor/zircote/swagger-php/src/Contracts/AttributeTranslatorInterface.php @@ -45,10 +45,12 @@ public function getAttributes(\ReflectionClass|\ReflectionMethod|\ReflectionProp * instances and newly instantiated objects from the current translator's * `getAttributes()` call. * - * @param array $attributes current attributes - * @param array $created newly created attribute instances - * @param AttributeReflector $reflector - * @return array + * @param array $attributes current attributes; a previous translator's + * output, so not yet narrowed to AttributeInterface + * @param array $created newly created attribute instances + * @param AttributeReflector $reflector + * @return array attributes to carry forward; AttributeFactory filters + * the final result down to AttributeInterface */ public function translate(array $attributes, array $created, \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): array; } diff --git a/upload/api/vendor/zircote/swagger-php/src/Generator.php b/upload/api/vendor/zircote/swagger-php/src/Generator.php index 908d6c2f..d0c47c1f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Generator.php +++ b/upload/api/vendor/zircote/swagger-php/src/Generator.php @@ -21,10 +21,12 @@ * OpenApi spec generator. * * Scans PHP source code and generates OpenApi specifications from the found OpenApi annotations. + * + * @phpstan-import-type BuilderSource from Builder */ class Generator { - /** @deprecated Use {@see Undefined::UNDEFINED} instead. */ + /** @deprecated since 6.3.1, removed in 8.0 - use {@see Undefined::UNDEFINED} instead */ public const UNDEFINED = Undefined::UNDEFINED; /** @var array */ @@ -41,7 +43,7 @@ class Generator /** @var array Map of namespace aliases to be supported by doctrine. */ protected array $aliases; - /** @var array|null List of annotation namespaces to be autoloaded by doctrine. */ + /** @var list|null List of annotation namespaces to be autoloaded by doctrine. */ protected ?array $namespaces; protected ?AnalyserInterface $analyser = null; @@ -49,6 +51,7 @@ class Generator /** @var array */ protected array $config = []; + /** @var Pipeline|null */ protected ?Pipeline $processorPipeline = null; protected ?TypeResolverInterface $typeResolver = null; @@ -74,7 +77,9 @@ public function __construct(?LoggerInterface $logger = null) } /** - * @deprecated use {@see Undefined::isDefault()} instead + * @deprecated since 6.3.1, removed in 8.0 - use {@see Undefined::isDefault()} instead + * + * @param mixed ...$value */ public static function isDefault(...$value): bool { @@ -83,12 +88,17 @@ public static function isDefault(...$value): bool /** * @return array + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function getAliases(): array { return $this->aliases; } + /** + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function addAlias(string $alias, string $namespace): Generator { $this->aliases[$alias] = $namespace; @@ -96,6 +106,11 @@ public function addAlias(string $alias, string $namespace): Generator return $this; } + /** + * @param array $aliases + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function setAliases(array $aliases): Generator { $this->aliases = $aliases; @@ -105,20 +120,30 @@ public function setAliases(array $aliases): Generator /** * @return list|null + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function getNamespaces(): ?array { return $this->namespaces; } + /** + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function addNamespace(string $namespace): Generator { $namespaces = (array) $this->getNamespaces(); $namespaces[] = $namespace; - return $this->setNamespaces(array_unique($namespaces)); + return $this->setNamespaces(array_values(array_unique($namespaces))); } + /** + * @param list|null $namespaces + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function setNamespaces(?array $namespaces): Generator { $this->namespaces = $namespaces; @@ -145,6 +170,9 @@ public function setAnalyser(?AnalyserInterface $analyser): Generator return $this; } + /** + * @return array> + */ public function getDefaultConfig(): array { return [ @@ -177,6 +205,9 @@ public function getDefaultConfig(): array ]; } + /** + * @return array + */ public function getConfig(): array { return $this->config + $this->getDefaultConfig(); @@ -189,11 +220,16 @@ public function getConfig(): array */ public function setConfig(array $config): Generator { - $this->config = $this->normaliseConfig($config) + $this->config; + /** @var array $normalised */ + $normalised = $this->normaliseConfig($config); + $this->config = $normalised + $this->config; return $this; } + /** + * @return Pipeline + */ public function getProcessorPipeline(): Pipeline { if (!$this->processorPipeline instanceof Pipeline) { @@ -225,7 +261,7 @@ public function getProcessorPipeline(): Pipeline } $config = $this->getConfig(); - $walker = function (callable $pipe) use ($config): void { + $walker = function (object $pipe) use ($config): void { $rc = new \ReflectionClass($pipe); // apply config @@ -239,7 +275,7 @@ public function getProcessorPipeline(): Pipeline } } - if (is_a($pipe, GeneratorAwareInterface::class)) { + if ($pipe instanceof GeneratorAwareInterface) { $pipe->setGenerator($this); } }; @@ -247,12 +283,15 @@ public function getProcessorPipeline(): Pipeline return $this->processorPipeline->walk($walker); } + /** + * @param Pipeline|null $processor + */ public function setProcessorPipeline(?Pipeline $processor): Generator { $this->processorPipeline = $processor; - $walker = function (callable $pipe): void { - if (is_a($pipe, GeneratorAwareInterface::class)) { + $walker = function (object $pipe): void { + if ($pipe instanceof GeneratorAwareInterface) { $pipe->setGenerator($this); } }; @@ -331,13 +370,14 @@ public function withContext(callable $callable) /** * 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 + * @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 + * @param iterable> $sources */ public function generate(iterable $sources, ?Analysis $analysis = null, bool $validate = true): ?OpenApi { @@ -369,6 +409,11 @@ public function generate(iterable $sources, ?Analysis $analysis = null, bool $va return $analysis->openapi; } + /** + * @param array $config + * + * @return array + */ protected function normaliseConfig(array $config): array { $normalised = []; @@ -409,6 +454,9 @@ protected function normaliseConfig(array $config): array return $normalised; } + /** + * @param iterable> $sources + */ protected function scanSources(iterable $sources, Analysis $analysis, Context $rootContext): void { $analyser = $this->getAnalyser(); diff --git a/upload/api/vendor/zircote/swagger-php/src/HybridBridge.php b/upload/api/vendor/zircote/swagger-php/src/HybridBridge.php index ffcec8a5..cf8d876a 100644 --- a/upload/api/vendor/zircote/swagger-php/src/HybridBridge.php +++ b/upload/api/vendor/zircote/swagger-php/src/HybridBridge.php @@ -71,6 +71,12 @@ protected function isNestedSchemaType(OA\Schema $schema): bool protected function isClassMember(OA\Property $property): bool { + // A Property nested inside another annotation's `properties` array describes + // that annotation's payload, not the class member its reflector happens to be. + if ($property->_context->nested instanceof OA\AbstractAnnotation) { + return false; + } + $reflector = $property->_context->reflector; return $reflector instanceof \ReflectionProperty @@ -249,7 +255,7 @@ protected function convertServer(OA\Server $server): Spec\Server serverVariable: $this->val($variable->serverVariable), default: $this->val($variable->default), description: $this->val($variable->description), - enum: Undefined::isDefault($variable->enum) ? null : $variable->enum, + enum: Undefined::isDefault($variable->enum) ? null : (is_string($variable->enum) ? [$variable->enum] : $variable->enum), x: $this->extensions($variable), ); } @@ -430,7 +436,7 @@ protected function convertParameter(OA\Parameter $param): Spec\Parameter example: Undefined::isDefault($param->example) ? Undefined::UNDEFINED : $param->example, examples: Undefined::isDefault($param->examples) ? null - : array_map($this->convertExample(...), $param->examples), + : array_values(array_map($this->convertExample(...), $param->examples)), content: $this->resolveContent($param), x: $this->extensions($param), ); @@ -502,7 +508,7 @@ protected function convertMediaType(OA\MediaType $mediaType): Spec\MediaType example: Undefined::isDefault($mediaType->example) ? Undefined::UNDEFINED : $mediaType->example, examples: Undefined::isDefault($mediaType->examples) ? null - : array_map($this->convertExample(...), $mediaType->examples), + : array_values(array_map($this->convertExample(...), $mediaType->examples)), encoding: $encoding, x: $this->extensions($mediaType), ); @@ -511,11 +517,13 @@ protected function convertMediaType(OA\MediaType $mediaType): Spec\MediaType /** * @return list|null */ - protected function resolveContent(OA\Response|OA\RequestBody|OA\Parameter $parent): ?array + protected function resolveContent(OA\Response|OA\RequestBody|OA\Parameter|OA\Header $parent): ?array { $content = []; - if (!Undefined::isDefault($parent->content)) { + // a JsonContent/XmlContent set directly on $content (rather than left in _unmerged, + // which the loop below handles) is not a media type map; the declared type allows it + if (!Undefined::isDefault($parent->content) && is_array($parent->content)) { foreach ($parent->content as $mediaType) { if ($mediaType instanceof OA\MediaType) { $content[] = $this->convertMediaType($mediaType); @@ -551,7 +559,7 @@ protected function unwrapContentAnnotation(OA\Schema $annotation, string $mediaT example: Undefined::isDefault($annotation->example) ? Undefined::UNDEFINED : $annotation->example, examples: Undefined::isDefault($annotation->examples) ? null - : array_map($this->convertExample(...), $annotation->examples), + : array_values(array_map($this->convertExample(...), $annotation->examples)), encoding: $encoding, ); } @@ -570,10 +578,8 @@ protected function convertHeader(OA\Header $header): Spec\Header example: Undefined::isDefault($header->example) ? Undefined::UNDEFINED : $header->example, examples: Undefined::isDefault($header->examples) ? null - : array_map($this->convertExample(...), $header->examples), - content: Undefined::isDefault($header->content) - ? null - : array_map($this->convertMediaType(...), $header->content), + : array_values(array_map($this->convertExample(...), $header->examples)), + content: $this->resolveContent($header), x: $this->extensions($header), ); $this->copyReflector($header, $result); @@ -671,7 +677,7 @@ protected function convertSchema(OA\Schema $schema): Spec\Schema minItems: $this->val($schema->minItems), maxItems: $this->val($schema->maxItems), uniqueItems: $this->val($schema->uniqueItems), - prefixItems: Undefined::isDefault($schema->prefixItems) ? null : array_map($this->convertSchemaValue(...), $schema->prefixItems), + prefixItems: Undefined::isDefault($schema->prefixItems) ? null : array_values(array_map($this->convertSchemaValue(...), $schema->prefixItems)), contains: $this->convertSchemaOrBool($schema->contains), minContains: $this->val($schema->minContains), maxContains: $this->val($schema->maxContains), @@ -690,14 +696,14 @@ protected function convertSchema(OA\Schema $schema): Spec\Schema : $this->convertSchema($schema->propertyNames), dependentRequired: Undefined::isDefault($schema->dependentRequired) ? null : $schema->dependentRequired, dependentSchemas: Undefined::isDefault($schema->dependentSchemas) ? null : array_map($this->convertSchemaValue(...), $schema->dependentSchemas), - allOf: Undefined::isDefault($schema->allOf) ? null : array_map($this->convertSchema(...), $schema->allOf), - anyOf: Undefined::isDefault($schema->anyOf) ? null : array_map($this->convertSchema(...), $schema->anyOf), - oneOf: Undefined::isDefault($schema->oneOf) ? null : array_map($this->convertSchema(...), $schema->oneOf), + allOf: Undefined::isDefault($schema->allOf) ? null : array_values(array_map($this->convertSchema(...), $schema->allOf)), + anyOf: Undefined::isDefault($schema->anyOf) ? null : array_values(array_map($this->convertSchema(...), $schema->anyOf)), + oneOf: Undefined::isDefault($schema->oneOf) ? null : array_values(array_map($this->convertSchema(...), $schema->oneOf)), not: Undefined::isDefault($schema->not) ? null : $this->convertSchema($schema->not), if: Undefined::isDefault($schema->if) ? null : $this->convertSchemaValue($schema->if), then: Undefined::isDefault($schema->then) ? null : $this->convertSchemaValue($schema->then), else: Undefined::isDefault($schema->else) ? null : $this->convertSchemaValue($schema->else), - enum: Undefined::isDefault($schema->enum) ? null : $schema->enum, + enum: Undefined::isDefault($schema->enum) ? null : (is_string($schema->enum) ? [$schema->enum] : $schema->enum), const: Undefined::isDefault($schema->const) ? Undefined::UNDEFINED : $schema->const, example: Undefined::isDefault($schema->example) ? Undefined::UNDEFINED : $schema->example, examples: Undefined::isDefault($schema->examples) @@ -860,6 +866,8 @@ protected function convertExample(OA\Examples $example): Spec\Example } /** + * @param array $security classic security blocks: each entry is a scheme => scopes map + * * @return list */ protected function convertSecurityRequirements(array $security): array @@ -925,6 +933,11 @@ protected function convertComponents(OA\Components $components, Specification $s } } + /** + * @param array $callbacks + * + * @return array + */ protected function convertCallbacks(array $callbacks): array { $result = []; @@ -988,6 +1001,9 @@ protected function typeAsRef(mixed $type): ?string return null; } + /** + * @return list|string|null + */ protected function filterType(mixed $type): string|array|null { if (Undefined::isDefault($type)) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Loggers/CollectingLogger.php b/upload/api/vendor/zircote/swagger-php/src/Loggers/CollectingLogger.php index 8907f04c..af2d9d9b 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Loggers/CollectingLogger.php +++ b/upload/api/vendor/zircote/swagger-php/src/Loggers/CollectingLogger.php @@ -21,6 +21,9 @@ public function __construct(protected ?LoggerInterface $delegate = null) { } + /** + * @param string|\Stringable $message + */ public function log($level, $message, array $context = []): void { $this->entries[] = ['level' => (string) $level, 'message' => (string) $message]; diff --git a/upload/api/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php b/upload/api/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php index 75be1833..d1a0fc37 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php +++ b/upload/api/vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php @@ -12,6 +12,9 @@ class DefaultLogger extends AbstractLogger implements LoggerInterface { + /** + * @param string|\Stringable $message + */ public function log($level, $message, array $context = []): void { if (LogLevel::DEBUG == $level) { @@ -20,6 +23,6 @@ public function log($level, $message, array $context = []): void $error_level = in_array($level, [LogLevel::NOTICE, LogLevel::INFO]) ? E_USER_NOTICE : E_USER_WARNING; - trigger_error($message, $error_level); + trigger_error((string) $message, $error_level); } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Pipeline.php b/upload/api/vendor/zircote/swagger-php/src/Pipeline.php index 35a675da..303c379c 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Pipeline.php +++ b/upload/api/vendor/zircote/swagger-php/src/Pipeline.php @@ -7,7 +7,9 @@ namespace OpenApi; /** - * @deprecated use {@see Utils\Pipeline} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see Utils\Pipeline} instead + * + * @extends Utils\Pipeline */ class Pipeline extends Utils\Pipeline { diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentMediaType.php b/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentMediaType.php index e0fea8a5..4a74fe1f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentMediaType.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentMediaType.php @@ -25,7 +25,9 @@ public function __invoke(Analysis $analysis): void if ($schema instanceof OA\Schema) { if (!Undefined::isDefault($schema->properties)) { $this->mergePropertyEncodings($mediaType, $schema->properties); - } elseif (!Undefined::isDefault($schema->ref)) { + } elseif (!Undefined::isDefault($schema->ref) && is_string($schema->ref)) { + // classic's $ref may hold a class-string or an object; only a '#/' pointer + // resolves here, and ref() rejects anything else anyway try { $refSchema = $analysis->openapi->ref($schema->ref); } catch (OpenApiException) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentTags.php b/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentTags.php index f4e9f6e4..0865a072 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentTags.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/AugmentTags.php @@ -20,6 +20,9 @@ class AugmentTags protected bool $withDescription; + /** + * @param array $whitelist + */ public function __construct(array $whitelist = [], bool $withDescription = true) { $this->whitelist = $whitelist; @@ -78,6 +81,8 @@ public function __invoke(Analysis $analysis): void /** * Whitelist tags to keep even if not used. * may be used to keep all unused. + * + * @param array $whitelist */ public function setWhitelist(array $whitelist): AugmentTags { @@ -96,6 +101,10 @@ public function setWithDescription(bool $withDescription): AugmentTags return $this; } + /** + * @param array $usedTagNames + * @param array $declaredTags + */ private function removeUnusedTags(array $usedTagNames, array $declaredTags, Analysis $analysis): void { if (in_array('*', $this->whitelist)) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php b/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php index a7daf654..85f5ac9b 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnmerged.php @@ -20,11 +20,13 @@ public function __invoke(Analysis $analysis): void /** @var OA\AbstractAnnotation $annotation */ foreach ($analysis->annotations as $annotation) { if (property_exists($annotation, '_unmerged')) { - foreach ($annotation->_unmerged as $ii => $item) { - if ($merged->offsetExists($item)) { - unset($annotation->_unmerged[$ii]); // Property was merged + $kept = []; + foreach ($annotation->_unmerged as $item) { + if (!$merged->offsetExists($item)) { + $kept[] = $item; // drop the ones that were merged } } + $annotation->_unmerged = $kept; } } $analysis->openapi->_unmerged = []; diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php b/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php index 3573d7ac..9bfed671 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/CleanUnusedComponents.php @@ -53,6 +53,10 @@ public function setEnabled(bool $enabled): CleanUnusedComponents return $this; } + /** + * @param array $usedRefs + * @param \SplObjectStorage $visited + */ protected function collectAnnotationRefs(OA\AbstractAnnotation $annotation, array &$usedRefs, \SplObjectStorage $visited): void { if ($visited->offsetExists($annotation)) { @@ -92,7 +96,9 @@ protected function collectAnnotationRefs(OA\AbstractAnnotation $annotation, arra protected function cleanup(Analysis $analysis): bool { + /** @var array $usedRefs */ $usedRefs = []; + /** @var \SplObjectStorage $visited */ $visited = new \SplObjectStorage(); foreach ($analysis->annotations as $annotation) { $this->collectAnnotationRefs($annotation, $usedRefs, $visited); @@ -111,7 +117,8 @@ protected function cleanup(Analysis $analysis): bool $unusedComponents = []; $seen = []; foreach (OA\Components::$_nested as $nested) { - if (2 == count($nested)) { + // the [property, key] form; a bare string names a single-value slot + if (is_array($nested) && 2 === count($nested)) { [$componentType] = $nested; if (isset($seen[$componentType])) { continue; diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php b/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php index cba4b994..18d7ddf4 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/DocblockTrait.php @@ -53,6 +53,9 @@ public function isDocblockRoot(OA\AbstractAnnotation $annotation): bool return false; } + /** + * @param array{param?: array}|null $tags + */ public function parseDocblock(?string $docblock, ?array &$tags = null): string { return $this->docBlockParser()->parseDocblock($docblock, $tags); diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php b/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php index 6bf2763d..7bba7089 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/Concerns/MergePropertiesTrait.php @@ -19,6 +19,10 @@ * - inherit from first with schema; all other with scheme can be ignored * - merge from all without schema * => update all $ref that might reference a property merged. + * + * @phpstan-import-type ClassDefinition from Analysis + * @phpstan-import-type InterfaceDefinition from Analysis + * @phpstan-import-type TraitDefinition from Analysis */ trait MergePropertiesTrait { @@ -35,6 +39,10 @@ protected function inheritFrom(Analysis $analysis, OA\Schema $schema, OA\Schema $analysis->addAnnotation($refSchema, $refSchema->_context); } + /** + * @param ClassDefinition|InterfaceDefinition|TraitDefinition $from + * @param list $existing + */ protected function mergeProperties(OA\Schema $schema, array $from, array &$existing): void { foreach ($from['properties'] as $context) { @@ -49,6 +57,10 @@ protected function mergeProperties(OA\Schema $schema, array $from, array &$exist } } + /** + * @param ClassDefinition|InterfaceDefinition|TraitDefinition $from + * @param list $existing + */ protected function mergeMethods(OA\Schema $schema, array $from, array &$existing): void { foreach ($from['methods'] as $context) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php b/upload/api/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php index 51f56501..116cf9b8 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/DocBlockDescriptions.php @@ -47,7 +47,9 @@ public function __invoke(Analysis $analysis): void } if ($hasSummary && $hasDescription) { - $this->summaryAndDescription($annotation); + /** @var OA\Examples|OA\Info|OA\Operation|OA\PathItem|OA\Tag $summarised the property_exists() pair above is the guarantee */ + $summarised = $annotation; + $this->summaryAndDescription($summarised); } elseif ($hasDescription) { $this->description($annotation); } @@ -69,7 +71,10 @@ protected function description(OA\AbstractAnnotation $annotation): void } /** - * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation + * The annotations carrying both `summary` and `description`; the caller reaches this only + * after `property_exists()` confirms both, and skips `Parameter`/`Property` outright. + * + * @param OA\Examples|OA\Info|OA\Operation|OA\PathItem|OA\Tag $annotation */ protected function summaryAndDescription(OA\AbstractAnnotation $annotation): void { diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php b/upload/api/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php index 0e60e031..9c310380 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/ExpandEnums.php @@ -12,6 +12,7 @@ use OpenApi\GeneratorAwareTrait; use OpenApi\OpenApiException; use OpenApi\Undefined; +use OpenApi\Utils\ServerVariableEnum; /** * Expands PHP enums. @@ -67,7 +68,9 @@ protected function expandContextEnum(Analysis $analysis): void foreach ($schemas as $schema) { if ($schema->_context->is('enum')) { - $re = new \ReflectionEnum($schema->_context->fullyQualifiedName($schema->_context->enum) ?? ''); + /** @var class-string<\UnitEnum> $enumName the 'enum' context key is the guarantee */ + $enumName = $schema->_context->fullyQualifiedName($schema->_context->enum) ?? ''; + $re = new \ReflectionEnum($enumName); $schema->schema = Undefined::isDefault($schema->schema) ? $re->getShortName() : $schema->schema; $schemaType = $schema->type; @@ -79,7 +82,7 @@ protected function expandContextEnum(Analysis $analysis): void // no (or invalid) schema type means name $useName = Undefined::isDefault($schemaType) || ($enumType && $this->generator->getTypeResolver()->native2spec($enumType) != $schemaType); - $schema->enum = array_map(static fn (\ReflectionEnumUnitCase $case): int|string => ($useName || !($case instanceof \ReflectionEnumBackedCase)) ? $case->name : $case->getBackingValue(), $re->getCases()); + $schema->enum = array_values(array_map(static fn (\ReflectionEnumUnitCase $case): int|string => ($useName || !($case instanceof \ReflectionEnumBackedCase)) ? $case->name : $case->getBackingValue(), $re->getCases())); if ($this->enumNames !== null && !$useName) { $schemaX = Undefined::isDefault($schema->x) ? [] : $schema->x; @@ -132,7 +135,22 @@ protected function expandSchemaEnum(Analysis $analysis): void $enums = []; foreach ($cases as $enum) { - $enums[] = is_a($enum, \UnitEnum::class) ? $enum->value ?? $enum->name : $enum; + $enums[] = $enum instanceof \UnitEnum ? ($enum instanceof \BackedEnum ? $enum->value : $enum->name) : $enum; + } + + // A Schema enum may hold any JSON value; a ServerVariable enum may not. Only the + // latter narrows, which is why this sits here rather than in the loop above. + if ($schema instanceof OA\ServerVariable) { + $enums = ServerVariableEnum::asStrings($enums, $schema->_context->logger, Undefined::isDefault($schema->serverVariable) ? null : $schema->serverVariable); + + if ([] === $enums) { + // The spec says the array MUST NOT be empty, so an enum that normalised + // away is dropped rather than emitted empty. The spec compilers filter `[]` + // out on their own; this is classic catching up with them. + $schema->enum = Undefined::UNDEFINED; + + continue; + } } $schema->enum = $enums; diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php index 33ed3f1e..079787ab 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeIntoOpenApi.php @@ -76,7 +76,8 @@ public function __invoke(Analysis $analysis): void foreach ($componentsList as $components) { foreach (OA\Components::$_nested as $nested) { - if (2 == count($nested)) { + // the [property, key] form; a bare string names a single-value slot + if (is_array($nested) && 2 === count($nested)) { $property = $nested[0]; if (!Undefined::isDefault($components->{$property})) { $firstComponents->merge($components->{$property}); @@ -87,7 +88,7 @@ public function __invoke(Analysis $analysis): void $analysis->removeAnnotation($components); } - $merge = array_filter($merge, static fn (OA\AbstractAnnotation $annotation): bool => !$annotation instanceof OA\Components); + $merge = array_values(array_filter($merge, static fn (OA\AbstractAnnotation $annotation): bool => !$annotation instanceof OA\Components)); $merge[] = $firstComponents; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php index b72abb1a..96050b33 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeJsonContent.php @@ -30,7 +30,9 @@ public function __invoke(Analysis $analysis): void } continue; } - if (Undefined::isDefault($parent->content)) { + if (Undefined::isDefault($parent->content) || !is_array($parent->content)) { + // a nested JsonContent/XmlContent arrives in _unmerged, never on content + // itself, so this only ever replaces the UNDEFINED sentinel $parent->content = []; } $parent->content['application/json'] = $mediaType = new OA\MediaType([ diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php index 8caced6d..7e348fdf 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/MergeXmlContent.php @@ -30,7 +30,9 @@ public function __invoke(Analysis $analysis): void } continue; } - if (Undefined::isDefault($parent->content)) { + if (Undefined::isDefault($parent->content) || !is_array($parent->content)) { + // a nested JsonContent/XmlContent arrives in _unmerged, never on content + // itself, so this only ever replaces the UNDEFINED sentinel $parent->content = []; } $parent->content['application/xml'] = $mediaType = new OA\MediaType([ diff --git a/upload/api/vendor/zircote/swagger-php/src/Processors/PathFilter.php b/upload/api/vendor/zircote/swagger-php/src/Processors/PathFilter.php index 431f5e79..629bf766 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Processors/PathFilter.php +++ b/upload/api/vendor/zircote/swagger-php/src/Processors/PathFilter.php @@ -20,10 +20,20 @@ class PathFilter { use Concerns\AnnotationTrait; + /** + * @var array + */ protected array $tags; + /** + * @var array + */ protected array $paths; + /** + * @param array $tags + * @param array $paths + */ public function __construct(array $tags = [], array $paths = []) { $this->tags = $tags; @@ -67,6 +77,9 @@ public function __invoke(Analysis $analysis): void } } + /** + * @return array + */ public function getTags(): array { return $this->tags; @@ -84,6 +97,9 @@ public function setTags(array $tags): PathFilter return $this; } + /** + * @return array + */ public function getPaths(): array { return $this->paths; diff --git a/upload/api/vendor/zircote/swagger-php/src/Resolver.php b/upload/api/vendor/zircote/swagger-php/src/Resolver.php index 7096d6f1..2cc49b4e 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Resolver.php +++ b/upload/api/vendor/zircote/swagger-php/src/Resolver.php @@ -117,6 +117,9 @@ protected function findUnresolved(Specification $specification): array return array_values(array_unique($unresolved)); } + /** + * @param list $unresolved + */ protected function collectFromRefs(Specification $specification, ComponentIndex $index, array &$unresolved): void { $specification->getWalker()->eachRef(function (AttributeInterface $attribute) use ($index, &$unresolved): void { @@ -142,6 +145,9 @@ protected function collectFromRefs(Specification $specification, ComponentIndex }); } + /** + * @param list $unresolved + */ protected function collectFromReflectors(Specification $specification, ComponentIndex $index, array &$unresolved): void { foreach ($specification->schemas as $schema) { @@ -168,6 +174,8 @@ protected function collectFromReflectors(Specification $specification, Component } /** + * @param \ReflectionClass $reflector + * * @return list<\ReflectionNamedType> */ protected function getTypedReflectors(\ReflectionClass $reflector): array diff --git a/upload/api/vendor/zircote/swagger-php/src/Serializer.php b/upload/api/vendor/zircote/swagger-php/src/Serializer.php index 59445347..21832b8c 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Serializer.php +++ b/upload/api/vendor/zircote/swagger-php/src/Serializer.php @@ -14,6 +14,9 @@ */ class Serializer { + /** + * @var list> + */ private static array $VALID_ANNOTATIONS = [ OA\AdditionalProperties::class, OA\Attachable::class, @@ -87,10 +90,17 @@ public function deserializeFile(string $filename, string $format = 'json', strin } $contents = file_get_contents($filename); + if (false === $contents) { + throw new OpenApiException("Unable to read {$filename}"); + } $ext = pathinfo($filename, PATHINFO_EXTENSION); if ('yaml' === $format || in_array($ext, ['yml', 'yaml'], strict: true)) { - $contents = json_encode(Yaml::parse($contents)); + $encoded = json_encode(Yaml::parse($contents)); + if (false === $encoded) { + throw new OpenApiException("Unable to convert {$filename} from YAML to JSON"); + } + $contents = $encoded; } return $this->doDeserialize(json_decode($contents), $className, $context ?? new Context(['generated' => true])); @@ -137,6 +147,10 @@ protected function doDeserialize(\stdClass $c, string $className, Context $conte /** * Deserialize the annotation's property. + * + * @param mixed $value + * + * @return mixed */ protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, string $property, $value, Context $context) { @@ -151,7 +165,7 @@ protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, stri if (is_string($declaration)) { // property is an annotation if ($declaration === $property) { - if (is_object($value)) { + if ($value instanceof \stdClass) { return $this->doDeserialize($value, $nestedClass, $context); } @@ -184,20 +198,22 @@ protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, stri /** * Deserialize base annotation property. * - * @param array|string $type The property type - * @param mixed $value The value to deserialization + * @param array|string $type The property type + * @param mixed $value The value to deserialization * - * @return array|OA\AbstractAnnotation + * @return array|OA\AbstractAnnotation */ protected function doDeserializeBaseProperty(array|string $type, mixed $value, Context $context) { $isAnnotationClass = is_string($type) && is_subclass_of(trim($type, '[]'), OA\AbstractAnnotation::class); if ($isAnnotationClass) { + /** @var class-string $type */ $isArray = str_starts_with($type, '[') && str_ends_with($type, ']'); if ($isArray) { $annotationArr = []; + /** @var class-string $class */ $class = trim($type, '[]'); foreach ($value as $v) { diff --git a/upload/api/vendor/zircote/swagger-php/src/SourceFinder.php b/upload/api/vendor/zircote/swagger-php/src/SourceFinder.php index ac1a93e2..d46c53f7 100644 --- a/upload/api/vendor/zircote/swagger-php/src/SourceFinder.php +++ b/upload/api/vendor/zircote/swagger-php/src/SourceFinder.php @@ -3,7 +3,7 @@ namespace OpenApi; /** - * @deprecated use {@see Utils\SourceFinder} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see Utils\SourceFinder} instead */ class SourceFinder extends Utils\SourceFinder { diff --git a/upload/api/vendor/zircote/swagger-php/src/Spec/AbstractAttribute.php b/upload/api/vendor/zircote/swagger-php/src/Spec/AbstractAttribute.php index 217d6d4a..63a0e36e 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Spec/AbstractAttribute.php +++ b/upload/api/vendor/zircote/swagger-php/src/Spec/AbstractAttribute.php @@ -69,6 +69,9 @@ public function getClassReflector(): ?\ReflectionClass return null; } + /** + * @return class-string|null + */ public function getClassName(): ?string { return $this->getClassReflector()?->getName(); @@ -111,6 +114,13 @@ protected static function wrapList(mixed $value): ?array return null; } - return is_array($value) ? $value : [$value]; + if (!is_array($value)) { + return [$value]; + } + + /** @var list $list array_values() guarantees the list shape the return type declares */ + $list = array_values($value); + + return $list; } } diff --git a/upload/api/vendor/zircote/swagger-php/src/Spec/Schema.php b/upload/api/vendor/zircote/swagger-php/src/Spec/Schema.php index 219ce73a..76ab1e7c 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Spec/Schema.php +++ b/upload/api/vendor/zircote/swagger-php/src/Spec/Schema.php @@ -37,6 +37,8 @@ * Only a class supplies a name. On a method or a parameter, pass `schema:` explicitly; * without it the schema has no component key and is reported as missing one. * + * @phpstan-type EnumValue string|int|float|bool|\UnitEnum + * * @see [Schema Object](https://spec.openapis.org/oas/v3.1.1.html#schema-object) * @see [JSON Schema](https://json-schema.org/draft/2020-12/json-schema-validation) */ @@ -56,63 +58,63 @@ class Schema extends AbstractAttribute ]; /** - * @param string|null $schema Reusable schema identifier (component key) - * @param string|null $title A title for the schema - * @param string|null $description A description of the schema (CommonMark syntax) - * @param string|null $ref A JSON Reference to a reusable schema - * @param string|list|null $type The value type(s) (string, number, integer, boolean, array, object, null) - * @param string|null $format Further refines the type (e.g. int32, int64, float, double, date-time, email) - * @param bool|null $nullable Whether the value can be null (OAS 3.0 only; use type array in 3.1+) - * @param int|null $minLength Minimum string length - * @param int|null $maxLength Maximum string length - * @param string|null $pattern Regular expression pattern the string must match - * @param string|null $contentMediaType The media type of string content encoding - * @param string|null $contentEncoding The encoding used for string content (e.g. base64) - * @param Schema|null $contentSchema Schema for the decoded content described by contentMediaType - * @param int|float|null $minimum Minimum numeric value (inclusive) - * @param int|float|null $maximum Maximum numeric value (inclusive) - * @param int|float|bool|null $exclusiveMinimum Exclusive minimum value - * @param int|float|bool|null $exclusiveMaximum Exclusive maximum value - * @param int|float|null $multipleOf The value must be a multiple of this number - * @param Schema|string|null $items Schema for array items - * @param int|null $minItems Minimum number of array items - * @param int|null $maxItems Maximum number of array items - * @param bool|null $uniqueItems Whether array items must be unique - * @param list|null $prefixItems Schemas for positional array items (tuple validation) - * @param Schema|bool|null $contains Schema that at least one array item must match - * @param int|null $minContains Minimum number of items matching contains - * @param int|null $maxContains Maximum number of items matching contains - * @param Schema|bool|null $unevaluatedItems Schema for items not covered by other keywords - * @param list|null $properties Object property definitions - * @param list|null $required List of required property names - * @param Schema|Schema\AdditionalProperties|bool|null $additionalProperties Schema or boolean for additional properties - * @param array|null $patternProperties Schemas for properties matching regex patterns - * @param int|null $minProperties Minimum number of properties - * @param int|null $maxProperties Maximum number of properties - * @param Schema|bool|null $unevaluatedProperties Schema for properties not covered by other keywords - * @param Schema|null $propertyNames Schema that property names must validate against - * @param array>|null $dependentRequired Property-level required dependencies - * @param array|null $dependentSchemas Property-level schema dependencies - * @param list|null $allOf All schemas must match (AND composition) - * @param list|null $anyOf At least one schema must match (OR composition) - * @param list|null $oneOf Exactly one schema must match (XOR composition) - * @param Schema|null $not The schema must NOT match - * @param Schema|null $if Conditional schema (if-then-else) - * @param Schema|null $then Applied when 'if' succeeds - * @param Schema|null $else Applied when 'if' fails - * @param list|null>|null $enum Allowed values - * @param mixed $const A single allowed value - * @param mixed $example An example value - * @param list|null $examples A list of example values - * @param bool|null $deprecated Whether the schema is deprecated - * @param bool|null $readOnly Whether the value is read-only - * @param bool|null $writeOnly Whether the value is write-only - * @param mixed $default The default value - * @param Discriminator|null $discriminator Discriminator for polymorphism - * @param ExternalDocumentation|null $externalDocs Additional external documentation - * @param Xml|null $xml XML representation metadata - * @param array|null $x Vendor extensions (x-* properties) - * @param list|null $attachables Reusable custom attachable attributes + * @param string|null $schema Reusable schema identifier (component key) + * @param string|null $title A title for the schema + * @param string|null $description A description of the schema (CommonMark syntax) + * @param string|null $ref A JSON Reference to a reusable schema + * @param string|list|null $type The value type(s) (string, number, integer, boolean, array, object, null) + * @param string|null $format Further refines the type (e.g. int32, int64, float, double, date-time, email) + * @param bool|null $nullable Whether the value can be null (OAS 3.0 only; use type array in 3.1+) + * @param int|null $minLength Minimum string length + * @param int|null $maxLength Maximum string length + * @param string|null $pattern Regular expression pattern the string must match + * @param string|null $contentMediaType The media type of string content encoding + * @param string|null $contentEncoding The encoding used for string content (e.g. base64) + * @param Schema|null $contentSchema Schema for the decoded content described by contentMediaType + * @param int|float|null $minimum Minimum numeric value (inclusive) + * @param int|float|null $maximum Maximum numeric value (inclusive) + * @param int|float|bool|null $exclusiveMinimum Exclusive minimum value + * @param int|float|bool|null $exclusiveMaximum Exclusive maximum value + * @param int|float|null $multipleOf The value must be a multiple of this number + * @param Schema|string|null $items Schema for array items + * @param int|null $minItems Minimum number of array items + * @param int|null $maxItems Maximum number of array items + * @param bool|null $uniqueItems Whether array items must be unique + * @param list|null $prefixItems Schemas for positional array items (tuple validation) + * @param Schema|bool|null $contains Schema that at least one array item must match + * @param int|null $minContains Minimum number of items matching contains + * @param int|null $maxContains Maximum number of items matching contains + * @param Schema|bool|null $unevaluatedItems Schema for items not covered by other keywords + * @param list|null $properties Object property definitions + * @param list|null $required List of required property names + * @param Schema|Schema\AdditionalProperties|bool|null $additionalProperties Schema or boolean for additional properties + * @param array|null $patternProperties Schemas for properties matching regex patterns + * @param int|null $minProperties Minimum number of properties + * @param int|null $maxProperties Maximum number of properties + * @param Schema|bool|null $unevaluatedProperties Schema for properties not covered by other keywords + * @param Schema|null $propertyNames Schema that property names must validate against + * @param array>|null $dependentRequired Property-level required dependencies + * @param array|null $dependentSchemas Property-level schema dependencies + * @param list|null $allOf All schemas must match (AND composition) + * @param list|null $anyOf At least one schema must match (OR composition) + * @param list|null $oneOf Exactly one schema must match (XOR composition) + * @param Schema|null $not The schema must NOT match + * @param Schema|null $if Conditional schema (if-then-else) + * @param Schema|null $then Applied when 'if' succeeds + * @param Schema|null $else Applied when 'if' fails + * @param list|null>|null $enum Allowed values + * @param mixed $const A single allowed value + * @param mixed $example An example value + * @param list|null $examples A list of example values + * @param bool|null $deprecated Whether the schema is deprecated + * @param bool|null $readOnly Whether the value is read-only + * @param bool|null $writeOnly Whether the value is write-only + * @param mixed $default The default value + * @param Discriminator|null $discriminator Discriminator for polymorphism + * @param ExternalDocumentation|null $externalDocs Additional external documentation + * @param Xml|null $xml XML representation metadata + * @param array|null $x Vendor extensions (x-* properties) + * @param list|null $attachables Reusable custom attachable attributes */ public function __construct( // Identity diff --git a/upload/api/vendor/zircote/swagger-php/src/Spec/ServerVariable.php b/upload/api/vendor/zircote/swagger-php/src/Spec/ServerVariable.php index 59d6504f..544dccbb 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Spec/ServerVariable.php +++ b/upload/api/vendor/zircote/swagger-php/src/Spec/ServerVariable.php @@ -10,17 +10,18 @@ * The allowed and default substitutions for one template variable in a `Server` URL. * * @see [Server Variable Object](https://spec.openapis.org/oas/v3.1.1.html#server-variable-object) + * @phpstan-import-type EnumValue from Schema */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class ServerVariable extends AbstractAttribute { /** - * @param string|null $serverVariable The variable name - * @param string|null $default The default value to use for substitution - * @param string|null $description A description of the server variable (CommonMark syntax) - * @param list|null $enum Enumeration of allowed string values for substitution - * @param array|null $x Vendor extensions (x-* properties) - * @param list|null $attachables Reusable custom attachable attributes + * @param string|null $serverVariable The variable name + * @param string|null $default The default value to use for substitution + * @param string|null $description A description of the server variable (CommonMark syntax) + * @param list|null>|null $enum Allowed values for substitution; all cast to string in the specification + * @param array|null $x Vendor extensions (x-* properties) + * @param list|null $attachables Reusable custom attachable attributes */ public function __construct( public ?string $serverVariable = null, diff --git a/upload/api/vendor/zircote/swagger-php/src/Specification.php b/upload/api/vendor/zircote/swagger-php/src/Specification.php index 07f3fb3b..8ce48932 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Specification.php +++ b/upload/api/vendor/zircote/swagger-php/src/Specification.php @@ -9,6 +9,7 @@ use OpenApi\Contracts\AttributeInterface; use OpenApi\Spec as OA; use OpenApi\Specification\ComponentIndex; +use OpenApi\Specification\PathItemHierarchy; use OpenApi\Specification\Walker; /** @@ -108,6 +109,11 @@ public function buildComponentIndex(): ComponentIndex return new ComponentIndex($this); } + public function buildPathItemHierarchy(): PathItemHierarchy + { + return new PathItemHierarchy($this); + } + protected function addComponentsChildren(OA\Components $components): void { foreach ((new \ReflectionClass($components))->getProperties(\ReflectionProperty::IS_PUBLIC) as $rp) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Specification/PathItemHierarchy.php b/upload/api/vendor/zircote/swagger-php/src/Specification/PathItemHierarchy.php new file mode 100644 index 00000000..b886bb18 --- /dev/null +++ b/upload/api/vendor/zircote/swagger-php/src/Specification/PathItemHierarchy.php @@ -0,0 +1,128 @@ + */ + protected array $classToPathItem = []; + + /** @var array> */ + protected array $chains = []; + + public function __construct( + protected Specification $specification, + ) { + foreach ($specification->pathItems as $pathItem) { + $className = $pathItem->getClassName(); + if ($className !== null) { + $this->classToPathItem[$className] = $pathItem; + } + } + } + + /** + * Every class carrying a PathItem of its own. + * + * @return array + */ + public function classes(): array + { + return $this->classToPathItem; + } + + /** + * The PathItems governing a class, outermost ancestor first, including the class's own. + * + * @return list + */ + public function chain(?string $className): array + { + if ($className === null) { + return []; + } + + if (isset($this->chains[$className])) { + return $this->chains[$className]; + } + + // not `new \ReflectionClass()` in a try/catch: a class-string is a name, not a promise + // the class loads (see ClassReflector), and phpstan reads the catch as dead + [$reflector] = ClassReflector::tryReflect($className); + + return $this->chains[$className] = $reflector instanceof \ReflectionClass + ? $this->chainFor($reflector) + : []; + } + + /** + * The same chain, for a caller that already holds the reflector. + * + * @param \ReflectionClass $reflector + * @return list + */ + public function chainFor(\ReflectionClass $reflector): array + { + $className = $reflector->getName(); + if (isset($this->chains[$className])) { + return $this->chains[$className]; + } + + $chain = []; + $current = $reflector; + while ($current !== false) { + $pathItem = $this->classToPathItem[$current->getName()] ?? null; + if ($pathItem !== null) { + $chain[] = $pathItem; + } + $current = $current->getParentClass(); + } + + return $this->chains[$className] = array_reverse($chain); + } + + /** + * The nearest governing PathItem — the class's own, or the closest ancestor's. + */ + public function governing(?string $className): ?OA\PathItem + { + $chain = $this->chain($className); + + return $chain === [] ? null : $chain[count($chain) - 1]; + } + + /** + * The chain governing the class an operation is declared in. + * + * @return list + */ + public function forOperation(OA\Operation $operation): array + { + $reflector = $operation->getClassReflector(); + + return $reflector instanceof \ReflectionClass ? $this->chainFor($reflector) : []; + } +} diff --git a/upload/api/vendor/zircote/swagger-php/src/Specification/Walker.php b/upload/api/vendor/zircote/swagger-php/src/Specification/Walker.php index 3b327be4..115fec27 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Specification/Walker.php +++ b/upload/api/vendor/zircote/swagger-php/src/Specification/Walker.php @@ -81,8 +81,10 @@ public function visit(string $visitee, callable $visitor): void /** * @template T of AttributeInterface * - * @param class-string $visitee - * @param callable(T): void $visitor + * @param class-string $visitee + * @param callable(T): void $visitor + * @param array $candidates + * @param \SplObjectStorage|null $seen */ protected function walk(string $visitee, callable $visitor, array $candidates, ?\SplObjectStorage $seen = null): void { diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/AbstractTypeResolver.php b/upload/api/vendor/zircote/swagger-php/src/Type/AbstractTypeResolver.php index e1beee41..b7d5cfdf 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/AbstractTypeResolver.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/AbstractTypeResolver.php @@ -22,14 +22,16 @@ public function __construct() } /** - * @param string|array $type + * @param string|non-empty-array $type */ public function mapNativeType(OA\Schema $schema, $type): bool { if (is_array($type)) { - $schema->type = $this->typeMapper->toSpecTypes( + /** @var non-empty-array $mapped neither array_map() nor toSpecTypes() drop elements */ + $mapped = $this->typeMapper->toSpecTypes( array_map(static fn ($t): string => strtolower((string) $t), $type) ); + $schema->type = $mapped; return true; } @@ -71,6 +73,9 @@ public function augmentSchemaType(Analysis $analysis, OA\Schema $schema, string $this->mapNativeType($schema, $schema->type); } + /** + * @param class-string $sourceClass + */ protected function type2ref(OA\Schema $schema, Analysis $analysis, string $sourceClass = OA\Schema::class): void { if (!Undefined::isDefault($schema->type) && !is_array($schema->type)) { @@ -83,6 +88,7 @@ protected function type2ref(OA\Schema $schema, Analysis $analysis, string $sourc /** * @param \ReflectionParameter|\ReflectionProperty|\ReflectionMethod $reflector + * @param class-string $sourceClass */ abstract protected function doAugment(Analysis $analysis, OA\Schema $schema, \Reflector $reflector, string $sourceClass = OA\Schema::class): void; } diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/LegacyTypeResolver.php b/upload/api/vendor/zircote/swagger-php/src/Type/LegacyTypeResolver.php index b8fc00a8..a66d09c2 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/LegacyTypeResolver.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/LegacyTypeResolver.php @@ -12,7 +12,7 @@ use OpenApi\Undefined; /** - * @deprecated use `TypeInfoTypeResolver` instead + * @deprecated since 6.0.0, removed in 7.0 - use `TypeInfoTypeResolver` instead */ class LegacyTypeResolver extends AbstractTypeResolver { @@ -105,6 +105,11 @@ protected function augmentItems(OA\Schema $schema, Analysis $analysis): void $schema->type = 'array'; } + /** + * @param array|null $explicitDetails a min/max range shape, or a list of them for a disjoint range; + * shape varies by caller, only ever read back verbatim + * @param list $types + */ protected function normaliseTypeResult(?string $explicitType = null, ?array $explicitDetails = null, array $types = [], ?string $name = null, ?bool $nullable = null, ?bool $isArray = null, bool $unsupported = false, ?Context $context = null): \stdClass { $types = array_filter($types, static fn (string $t): bool => !in_array($t, ['null', ''], strict: true)); diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/SchemaType.php b/upload/api/vendor/zircote/swagger-php/src/Type/SchemaType.php index a7465f80..c373d0f5 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/SchemaType.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/SchemaType.php @@ -46,6 +46,9 @@ public function __construct( ) { } + /** + * @phpstan-assert-if-true string $this->type + */ public function isRef(): bool { return is_string($this->type) && !$this->isNativeType(); diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/TypeInfoTypeResolver.php b/upload/api/vendor/zircote/swagger-php/src/Type/TypeInfoTypeResolver.php index c5765033..684e705f 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/TypeInfoTypeResolver.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/TypeInfoTypeResolver.php @@ -23,6 +23,8 @@ public function __construct() /** * @inheritdoc + * + * @param class-string $sourceClass */ protected function doAugment(Analysis $analysis, OA\Schema $schema, \Reflector $reflector, string $sourceClass = OA\Schema::class): void { @@ -64,10 +66,15 @@ protected function handlePostAugment(OA\Schema $schema): void } } + /** + * @param class-string $sourceClass + */ protected function applyToAnnotation(OA\Schema $schema, SchemaType $schemaType, Analysis $analysis, string $sourceClass = OA\Schema::class): void { if ($schemaType->type !== null) { - $schema->type = $schemaType->type; + /** @var non-empty-array|string $type every SchemaType array type is built non-empty */ + $type = $schemaType->type; + $schema->type = $type; } if ($schemaType->format !== null && Undefined::isDefault($schema->format)) { @@ -102,16 +109,22 @@ protected function applyToAnnotation(OA\Schema $schema, SchemaType $schemaType, if ($schemaType->additionalProperties instanceof SchemaType) { $schema->type = 'object'; - if (Undefined::isDefault($schema->additionalProperties)) { - $schema->additionalProperties = new OA\AdditionalProperties(['_context' => new Context(['generated' => true], $schema->_context)]); - $this->applyToAnnotation($schema->additionalProperties, $schemaType->additionalProperties, $analysis, $sourceClass); - $this->type2ref($schema->additionalProperties, $analysis, $sourceClass); - $analysis->addAnnotation($schema->additionalProperties, $schema->additionalProperties->_context); - } elseif (Undefined::isDefault($schema->additionalProperties->type, $schema->additionalProperties->oneOf, $schema->additionalProperties->allOf, $schema->additionalProperties->anyOf)) { - $this->applyToAnnotation($schema->additionalProperties, $schemaType->additionalProperties, $analysis, $sourceClass); - $this->type2ref($schema->additionalProperties, $analysis, $sourceClass); + // An explicit `true|false` is the whole answer for this map — nothing for the + // inferred value type to attach to, and hybrid and spec already keep it. + // `isDefault()` only rules out the UNDEFINED sentinel, so without this guard a + // declared `false` is dereferenced as a Schema. + if (!is_bool($schema->additionalProperties)) { + if (Undefined::isDefault($schema->additionalProperties)) { + $schema->additionalProperties = new OA\AdditionalProperties(['_context' => new Context(['generated' => true], $schema->_context)]); + $this->applyToAnnotation($schema->additionalProperties, $schemaType->additionalProperties, $analysis, $sourceClass); + $this->type2ref($schema->additionalProperties, $analysis, $sourceClass); + $analysis->addAnnotation($schema->additionalProperties, $schema->additionalProperties->_context); + } elseif (Undefined::isDefault($schema->additionalProperties->type, $schema->additionalProperties->oneOf, $schema->additionalProperties->allOf, $schema->additionalProperties->anyOf)) { + $this->applyToAnnotation($schema->additionalProperties, $schemaType->additionalProperties, $analysis, $sourceClass); + $this->type2ref($schema->additionalProperties, $analysis, $sourceClass); + } + $this->mapNativeType($schema->additionalProperties, $schema->additionalProperties->type); } - $this->mapNativeType($schema->additionalProperties, $schema->additionalProperties->type); } elseif ($schemaType->additionalProperties === true) { if (Undefined::isDefault($schema->additionalProperties)) { $schema->additionalProperties = new OA\AdditionalProperties(['_context' => new Context(['generated' => true], $schema->_context)]); diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/TypeMapper.php b/upload/api/vendor/zircote/swagger-php/src/Type/TypeMapper.php index 766edd7c..e4a00890 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/TypeMapper.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/TypeMapper.php @@ -72,9 +72,9 @@ public function toSpecType(string $type): string /** * Map an array of native types to their spec type strings. * - * @param list $types + * @param array $types * - * @return list + * @return array */ public function toSpecTypes(array $types): array { diff --git a/upload/api/vendor/zircote/swagger-php/src/Type/TypeResolver.php b/upload/api/vendor/zircote/swagger-php/src/Type/TypeResolver.php index 2d9cd32b..7d0c50f9 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Type/TypeResolver.php +++ b/upload/api/vendor/zircote/swagger-php/src/Type/TypeResolver.php @@ -30,6 +30,7 @@ use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\Type\UnionType; use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\TypeInfo\TypeResolver\ReflectionTypeResolver; /** @@ -197,6 +198,9 @@ protected function mapType(Type $type): SchemaType return new SchemaType(); } + /** + * @param CompositeTypeInterface $type + */ protected function mapCompositeType(CompositeTypeInterface $type): SchemaType { $types = $type->getTypes(); @@ -249,6 +253,9 @@ protected function mapCompositeType(CompositeTypeInterface $type): SchemaType return new SchemaType(); } + /** + * @param BuiltinType $type + */ protected function mapBuiltinType(BuiltinType $type): SchemaType { $typeName = (string) $type; @@ -259,6 +266,9 @@ protected function mapBuiltinType(BuiltinType $type): SchemaType return new SchemaType(); } + /** + * @param CollectionType|BuiltinType|ObjectType|GenericType|BuiltinType|ObjectType>> $type + */ protected function mapCollectionType(CollectionType $type): SchemaType { if ($type->isList() || $type->getCollectionKeyType() instanceof UnionType) { @@ -328,14 +338,16 @@ protected function applyNativeTypeMapping(SchemaType $schema): void } } } elseif (is_array($schema->type)) { - $schema->type = $this->typeMapper->toSpecTypes( + /** @var list $mapped neither array_map() nor toSpecTypes() change a list's shape */ + $mapped = $this->typeMapper->toSpecTypes( array_map(static fn ($t): string => strtolower((string) $t), $schema->type), ); + $schema->type = $mapped; } } /** - * @param \ReflectionProperty|\ReflectionParameter|\ReflectionMethod|\ReflectionClass $reflector + * @param \ReflectionProperty|\ReflectionParameter|\ReflectionMethod|\ReflectionClass|\ReflectionClassConstant $reflector */ protected function getReflectionType(\Reflector $reflector): ?Type { diff --git a/upload/api/vendor/zircote/swagger-php/src/TypeResolverInterface.php b/upload/api/vendor/zircote/swagger-php/src/TypeResolverInterface.php index de7a6303..41b42685 100644 --- a/upload/api/vendor/zircote/swagger-php/src/TypeResolverInterface.php +++ b/upload/api/vendor/zircote/swagger-php/src/TypeResolverInterface.php @@ -11,9 +11,12 @@ interface TypeResolverInterface { - /** @deprecated Use TypeMapper::NATIVE_TYPE_MAP instead */ + /** @deprecated since 5.5.2, removed in 7.0 - use {@see TypeMapper::NATIVE_TYPE_MAP} instead */ public const NATIVE_TYPE_MAP = TypeMapper::NATIVE_TYPE_MAP; + /** + * @param string|non-empty-array $type + */ public function mapNativeType(OA\Schema $schema, $type): bool; public function native2spec(string $type): string; diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/AttributeFactory.php b/upload/api/vendor/zircote/swagger-php/src/Utils/AttributeFactory.php index 0dc26c8f..d5796194 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/AttributeFactory.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/AttributeFactory.php @@ -88,6 +88,8 @@ public function resetTranslators(): void * * For methods, also resolves parameter-level attributes into the method-level ones. * + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector + * * @return list */ public function fromReflector(\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): array @@ -124,6 +126,8 @@ public function fromReflector(\ReflectionClass|\ReflectionMethod|\ReflectionProp * Each member is fully resolved internally (merge + parameter absorption for methods), * then all results are returned for hierarchical absorption into class-level containers. * + * @param \ReflectionClass $class + * * @return list */ public function membersOf(\ReflectionClass $class): array @@ -168,6 +172,8 @@ public function membersOf(\ReflectionClass $class): array /** * Check whether a reflector has any `AttributeInterface` attributes. + * + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector */ public function hasAttributes(\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): bool { @@ -177,6 +183,8 @@ public function hasAttributes(\ReflectionClass|\ReflectionMethod|\ReflectionProp /** * Get methods directly implemented by a class (not inherited from parents). * + * @param \ReflectionClass $class + * * @return list<\ReflectionMethod> */ public function getDirectMethods(\ReflectionClass $class): array @@ -201,7 +209,9 @@ public function getDirectMethods(\ReflectionClass $class): array /** * Get interfaces directly implemented by a class (not inherited from parents). * - * @return list<\ReflectionClass> + * @param \ReflectionClass $class + * + * @return list<\ReflectionClass> */ public function getDirectInterfaces(\ReflectionClass $class): array { @@ -228,19 +238,21 @@ public function getDirectInterfaces(\ReflectionClass $class): array * PHP's ReflectionClass::getTraits() flattens the entire trait tree, so we * must exclude traits that come from a parent class or from another trait's use. * - * @return list<\ReflectionClass> + * @param \ReflectionClass $class + * + * @return list<\ReflectionClass> */ public function getDirectTraits(\ReflectionClass $class): array { $scannerDetails = $this->tokenScanner->detailsFor($class); if ($scannerDetails !== null) { - return array_filter( + return array_values(array_filter( array_map( fn (string $name): ?\ReflectionClass => class_exists($name) || trait_exists($name) ? new \ReflectionClass($name) : null, $scannerDetails['traits'], ), - ); + )); } return []; @@ -434,6 +446,8 @@ protected function nestChild(AttributeInterface $parent, AttributeInterface $chi } /** + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector + * * @return list */ protected function readAttributes(\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\ReflectionClassConstant $reflector): array diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/ClassReflector.php b/upload/api/vendor/zircote/swagger-php/src/Utils/ClassReflector.php new file mode 100644 index 00000000..a59a6f45 --- /dev/null +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/ClassReflector.php @@ -0,0 +1,40 @@ +|null, 1: string|null} the reflector, or a + * reason it could not be produced: `null` when nothing by that name exists, the + * throwable's message when the name exists but would not load + */ + public static function tryReflect(string $fqdn): array + { + try { + if (!class_exists($fqdn) && !interface_exists($fqdn) && !trait_exists($fqdn) + && (!function_exists('enum_exists') || !enum_exists($fqdn))) { + return [null, null]; + } + + return [new \ReflectionClass($fqdn), null]; + } catch (\Throwable $throwable) { + return [null, $throwable->getMessage()]; + } + } +} diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/Config.php b/upload/api/vendor/zircote/swagger-php/src/Utils/Config.php index d4599af2..0a45acd3 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/Config.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/Config.php @@ -24,6 +24,8 @@ public function __construct( /** * This attribute's instances on $rc's constructor parameters, keyed by parameter name. * + * @param \ReflectionClass $rc + * * @return array */ public static function forConstructor(\ReflectionClass $rc): array diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/DocBlockParser.php b/upload/api/vendor/zircote/swagger-php/src/Utils/DocBlockParser.php index 25086508..4413384b 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/DocBlockParser.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/DocBlockParser.php @@ -64,6 +64,10 @@ public function formatType(TypeNode $typeNode): string return (string) $typeNode; } + /** + * @param array{param?: array}|null $tags + * collected tag values, by tag name; only `param` is populated today + */ public function parseDocblock(?string $docblock, ?array &$tags = null): string { $docNode = $this->parsePhpDoc($docblock); @@ -115,7 +119,7 @@ public function extractCommentSummary(string $content): string return Undefined::UNDEFINED; } - $lines = preg_split('/(\n|\r\n)/', $content); + $lines = preg_split('/(\n|\r\n)/', $content) ?: []; $summary = ''; foreach ($lines as $line) { $summary .= $line . "\n"; diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/Pipeline.php b/upload/api/vendor/zircote/swagger-php/src/Utils/Pipeline.php index 3eacd685..cecc2809 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/Pipeline.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/Pipeline.php @@ -14,7 +14,7 @@ /** * @template T * - * @extends TypedList + * @extends TypedList */ class Pipeline extends TypedList { @@ -28,9 +28,9 @@ class Pipeline extends TypedList protected LoggerInterface $logger; /** - * @param list $pipes - * @param list|null $groups Ordered group names/enums. When set, process() executes pipes in group order. - * @param string|\BackedEnum|null $defaultGroup Group for pipes without PipeInterface. Must be in $groups if groups are set. + * @param list $pipes + * @param list|null $groups Ordered group names/enums. When set, process() executes pipes in group order. + * @param string|\BackedEnum|null $defaultGroup Group for pipes without PipeInterface. Must be in $groups if groups are set. */ public function __construct(array $pipes = [], ?array $groups = null, string|\BackedEnum|null $defaultGroup = null, ?LoggerInterface $logger = null) { @@ -95,7 +95,7 @@ public function getConfig(): array { $config = []; - $walker = function (callable $pipe) use (&$config): void { + $walker = function (object $pipe) use (&$config): void { $rc = new \ReflectionClass($pipe); $settings = []; @@ -125,13 +125,15 @@ public function getConfig(): array * Keys that match no pipe, and options with no matching setter, are reported * as warnings; they would otherwise be silently ignored. See {@see self::getConfig()} * for the keys a pipeline accepts. + * + * @param array $config */ public function configure(array $config): void { $config = $this->normaliseConfig($config); $applied = []; - $walker = function (callable $pipe) use ($config, &$applied): void { + $walker = function (object $pipe) use ($config, &$applied): void { $rc = new \ReflectionClass($pipe); // apply config @@ -162,6 +164,8 @@ public function configure(array $config): void } /** + * @param \ReflectionClass $rc + * * @return list constructor parameter names carrying a {@see Config} attribute */ protected static function configurableParameters(\ReflectionClass $rc): array @@ -172,7 +176,7 @@ protected static function configurableParameters(\ReflectionClass $rc): array /** * Return pipes in execution order: grouped if groups are configured, otherwise insertion order. * - * @return list + * @return list */ protected function ordered(): array { @@ -204,6 +208,11 @@ protected static function groupKey(string|\BackedEnum $group): string : $group; } + /** + * @param array $config + * + * @return array + */ protected function normaliseConfig(array $config): array { $normalised = []; diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/ServerVariableEnum.php b/upload/api/vendor/zircote/swagger-php/src/Utils/ServerVariableEnum.php new file mode 100644 index 00000000..df147b12 --- /dev/null +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/ServerVariableEnum.php @@ -0,0 +1,60 @@ +` before either runs. + */ +final class ServerVariableEnum +{ + /** + * @param iterable $enum + * + * @return list + */ + public static function asStrings(iterable $enum, ?LoggerInterface $logger = null, ?string $variable = null): array + { + $values = []; + // Built here rather than taken from the caller so the three pipelines word it the same; + // classic's identity() would read differently and split the log expectations. + $identity = 'ServerVariable(' . ($variable ?? '?') . ')'; + + foreach ($enum as $value) { + if (is_bool($value)) { + // Unlike a number, a bool has no textual form to substitute — PHP would give + // '1' and '', and neither is a URL segment anyone meant to write. + $logger?->warning($identity . ': enum values must be strings, dropping ' . var_export($value, true)); + + continue; + } + + if (is_int($value) || is_float($value)) { + $values[] = (string) $value; + + continue; + } + + if (is_string($value)) { + $values[] = $value; + + continue; + } + + $logger?->warning($identity . ': enum values must be strings, dropping ' . get_debug_type($value)); + } + + return $values; + } +} diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/SourceFinder.php b/upload/api/vendor/zircote/swagger-php/src/Utils/SourceFinder.php index 08b5a24c..1c92db88 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/SourceFinder.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/SourceFinder.php @@ -13,6 +13,10 @@ */ class SourceFinder extends Finder { + /** + * @param string|list $directory + * @param string|list|null $exclude + */ public function __construct(string|array $directory, null|array|string $exclude = null, string $pattern = '*.php') { parent::__construct(); @@ -50,6 +54,9 @@ public function __construct(string|array $directory, null|array|string $exclude * * In particular, leading and trailing slashes are removed. */ + /** + * @param list $directories + */ private function getRelativePath(string $fullPath, array $directories): string { foreach ($directories as $directory) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/SourceScanner.php b/upload/api/vendor/zircote/swagger-php/src/Utils/SourceScanner.php index 32c42e5f..cfaf8b39 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/SourceScanner.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/SourceScanner.php @@ -29,7 +29,7 @@ public function __construct(protected LoggerInterface $logger) /** * Scan sources and return resolved file paths. * - * @param list> $sources + * @param iterable> $sources * * @return list resolved absolute file paths */ @@ -62,6 +62,9 @@ public function getReflectors(): array return $this->reflectors; } + /** + * @param iterable> $sources + */ protected function collect(iterable $sources): void { foreach ($sources as $source) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/TokenScanner.php b/upload/api/vendor/zircote/swagger-php/src/Utils/TokenScanner.php index d41a31cc..f855bd0b 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/TokenScanner.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/TokenScanner.php @@ -7,6 +7,8 @@ namespace OpenApi\Utils; use PhpParser\Error; +use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; @@ -48,9 +50,14 @@ public function scanFile(string $filename): array return $this->cache[$filename]; } + $code = file_get_contents($filename); + if (false === $code) { + throw new \RuntimeException("Unable to read {$filename}"); + } + $parser = (new ParserFactory())->createForNewestSupportedVersion(); try { - $stmts = $parser->parse(file_get_contents($filename)); + $stmts = $parser->parse($code) ?? []; } catch (Error $error) { throw new \RuntimeException($error->getMessage(), $error->getCode(), $error); } @@ -75,6 +82,8 @@ public function scanFile(string $filename): array * * Scans the file on demand if not already cached. * + * @param \ReflectionClass $class + * * @return ScannerDetails|null */ public function detailsFor(\ReflectionClass $class): ?array @@ -89,16 +98,27 @@ public function detailsFor(\ReflectionClass $class): ?array return $results[$class->getName()] ?? null; } + /** + * @param array $stmts + * + * @return array + */ protected function collect_stmts(array $stmts, string $namespace): array { - /** @var array $uses */ + /** @var array $uses */ $uses = []; - $resolve = static function (string $name) use ($namespace, &$uses) { + /** + * @return class-string + */ + $resolve = static function (string $name) use ($namespace, &$uses): string { if (array_key_exists($name, $uses)) { return $uses[$name]; } - return $namespace . '\\' . $name; + /** @var class-string $fqdn */ + $fqdn = $namespace . '\\' . $name; + + return $fqdn; }; $details = static function () use (&$uses): array { return [ @@ -133,17 +153,29 @@ protected function collect_stmts(array $stmts, string $namespace): array return $result; } + /** + * @return array + */ protected function collect_uses(Use_ $stmt): array { + /** @var array $uses */ $uses = []; foreach ($stmt->uses as $use) { - $uses[(string) $use->getAlias()] = (string) $use->name; + /** @var class-string $name */ + $name = (string) $use->name; + $uses[(string) $use->getAlias()] = $name; } return $uses; } + /** + * @param ScannerDetails $details + * @param callable(string): class-string $resolve + * + * @return array + */ protected function collect_classlike(ClassLike $stmt, array $details, callable $resolve): array { foreach ($stmt->getProperties() as $properties) { @@ -173,6 +205,12 @@ protected function collect_classlike(ClassLike $stmt, array $details, callable $ ]; } + /** + * @param ScannerDetails $details + * @param callable(string): class-string $resolve + * + * @return array + */ protected function collect_class(Class_ $stmt, array $details, callable $resolve): array { foreach ($stmt->implements as $implement) { @@ -182,7 +220,7 @@ protected function collect_class(Class_ $stmt, array $details, callable $resolve // promoted properties if (($ctor = $stmt->getMethod('__construct')) instanceof ClassMethod) { foreach ($ctor->getParams() as $param) { - if ($param->flags) { + if ($param->flags && $param->var instanceof Variable && is_string($param->var->name)) { $details['properties'][] = $param->var->name; } } @@ -191,6 +229,12 @@ protected function collect_class(Class_ $stmt, array $details, callable $resolve return $this->collect_classlike($stmt, $details, $resolve); } + /** + * @param ScannerDetails $details + * @param callable(string): class-string $resolve + * + * @return array + */ protected function collect_interface(Interface_ $stmt, array $details, callable $resolve): array { foreach ($stmt->extends as $extend) { diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/TypeMapper.php b/upload/api/vendor/zircote/swagger-php/src/Utils/TypeMapper.php index 25416948..dbf23ef7 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/TypeMapper.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/TypeMapper.php @@ -9,7 +9,7 @@ use OpenApi\Type; /** - * @deprecated use {@see Type\TypeMapper} instead + * @deprecated since 6.9.0, removed in 7.0 - use {@see Type\TypeMapper} instead */ class TypeMapper extends Type\TypeMapper { diff --git a/upload/api/vendor/zircote/swagger-php/src/Utils/TypedList.php b/upload/api/vendor/zircote/swagger-php/src/Utils/TypedList.php index 38f2d2e3..8505d5de 100644 --- a/upload/api/vendor/zircote/swagger-php/src/Utils/TypedList.php +++ b/upload/api/vendor/zircote/swagger-php/src/Utils/TypedList.php @@ -10,6 +10,8 @@ /** * @template T + * + * @implements \IteratorAggregate */ class TypedList implements \IteratorAggregate { @@ -26,6 +28,9 @@ public function __construct(array $items = []) $this->items = $items; } + /** + * @return \Traversable + */ public function getIterator(): \Traversable { return new \ArrayIterator($this->items); @@ -92,9 +97,9 @@ public function remove(mixed $item = null, ?callable $matcher = null): static return $this; } - unset($this->items[$key]); - - $this->items = array_values($this->items); + $items = $this->items; + unset($items[$key]); + $this->items = array_values($items); } return $this;