From 41b6a27ba7c3e15c5de5b5f7501c4fafc36e5f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?eW=C9=98yn?= <5607939+Llewellynvdm@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:23:34 +0200 Subject: [PATCH 1/2] Make source ZIPs installable and delegate releases to Octo tools --- .github/workflows/ci.yml | 13 +- .github/workflows/generate-catalogue.yml | 2 +- .github/workflows/golden-image.yml | 4 +- .github/workflows/integration.yml | 15 +- .github/workflows/release.yml | 321 +- .octojpack | 35 +- AGENTS.md | 13 +- CHANGELOG.md | 47 +- README.md | 21 +- SECURITY.md | 2 +- admin/LICENSE | 674 ++ admin/autoload.php | 22 + admin/cli/jcb.php | 2 +- {data => admin/data}/catalogue-seed.json | 0 admin/services/provider.php | 4 +- admin/src/Installer/InstallerScript.php | 6 +- admin/vendor/autoload.php | 25 + .../clue/stream-filter/.github/FUNDING.yml | 2 + admin/vendor/clue/stream-filter/CHANGELOG.md | 97 + admin/vendor/clue/stream-filter/LICENSE | 21 + admin/vendor/clue/stream-filter/README.md | 326 + admin/vendor/clue/stream-filter/composer.json | 32 + .../clue/stream-filter/src/CallbackFilter.php | 120 + .../clue/stream-filter/src/functions.php | 380 ++ .../stream-filter/src/functions_include.php | 9 + admin/vendor/composer/ClassLoader.php | 579 ++ admin/vendor/composer/InstalledVersions.php | 359 + admin/vendor/composer/LICENSE | 19 + admin/vendor/composer/autoload_classmap.php | 10 + admin/vendor/composer/autoload_files.php | 13 + admin/vendor/composer/autoload_namespaces.php | 9 + admin/vendor/composer/autoload_psr4.php | 35 + admin/vendor/composer/autoload_real.php | 50 + admin/vendor/composer/autoload_static.php | 199 + admin/vendor/composer/installed.json | 2020 ++++++ admin/vendor/composer/installed.php | 343 + admin/vendor/composer/platform_check.php | 26 + admin/vendor/doctrine/deprecations/LICENSE | 19 + admin/vendor/doctrine/deprecations/README.md | 216 + .../doctrine/deprecations/composer.json | 39 + .../doctrine/deprecations/src/Deprecation.php | 309 + .../src/PHPUnit/VerifyDeprecations.php | 66 + admin/vendor/mcp/sdk/.gitattributes | 12 + admin/vendor/mcp/sdk/.gitignore | 21 + admin/vendor/mcp/sdk/CHANGELOG.md | 141 + admin/vendor/mcp/sdk/CONTRIBUTING.md | 84 + admin/vendor/mcp/sdk/LICENSE | 216 + admin/vendor/mcp/sdk/README.md | 139 + admin/vendor/mcp/sdk/ROADMAP.md | 21 + admin/vendor/mcp/sdk/SECURITY.md | 21 + ...oauth-authorization-server-out-of-scope.md | 97 + admin/vendor/mcp/sdk/adr/README.md | 10 + admin/vendor/mcp/sdk/composer.json | 97 + admin/vendor/mcp/sdk/phpdoc.dist.xml | 41 + .../Attribute/CompletionProvider.php | 38 + .../src/Capability/Attribute/McpPrompt.php | 40 + .../src/Capability/Attribute/McpResource.php | 49 + .../Attribute/McpResourceTemplate.php | 44 + .../sdk/src/Capability/Attribute/McpTool.php | 42 + .../sdk/src/Capability/Attribute/Schema.php | 258 + .../Completion/EnumCompletionProvider.php | 52 + .../Completion/ListCompletionProvider.php | 38 + .../Completion/ProviderInterface.php | 27 + .../Capability/Discovery/CachedDiscoverer.php | 100 + .../src/Capability/Discovery/Discoverer.php | 451 ++ .../Discovery/DiscovererInterface.php | 34 + .../Capability/Discovery/DiscoveryState.php | 128 + .../Capability/Discovery/DocBlockParser.php | 125 + .../Discovery/ElementMetadataResolver.php | 44 + .../Capability/Discovery/HandlerResolver.php | 96 + .../Capability/Discovery/SchemaGenerator.php | 964 +++ .../Discovery/SchemaGeneratorInterface.php | 41 + .../Capability/Discovery/SchemaValidator.php | 335 + .../Formatter/PromptResultFormatter.php | 211 + .../Formatter/ResourceResultFormatter.php | 200 + .../Formatter/ToolResultFormatter.php | 103 + .../src/Capability/Logger/ClientLogger.php | 82 + .../mcp/sdk/src/Capability/Registry.php | 480 ++ .../sdk/src/Capability/Registry/Container.php | 188 + .../Capability/Registry/ElementReference.php | 28 + .../Registry/InjectableParameters.php | 63 + .../Registry/Loader/ChainLoader.php | 38 + .../Registry/Loader/DiscoveryLoader.php | 185 + .../Registry/Loader/ExplicitElementLoader.php | 105 + .../Registry/Loader/LoaderInterface.php | 22 + .../Loader/ReflectedElementLoader.php | 322 + .../Capability/Registry/PromptReference.php | 51 + .../Capability/Registry/ReferenceHandler.php | 311 + .../Registry/ReferenceHandlerInterface.php | 37 + .../Capability/Registry/ResourceReference.php | 59 + .../Registry/ResourceTemplateReference.php | 112 + .../src/Capability/Registry/ToolReference.php | 142 + .../sdk/src/Capability/RegistryInterface.php | 175 + .../sdk/src/Capability/Tool/NameValidator.php | 20 + admin/vendor/mcp/sdk/src/Client.php | 368 ++ admin/vendor/mcp/sdk/src/Client/Builder.php | 215 + .../mcp/sdk/src/Client/Configuration.php | 46 + .../LoggingNotificationHandler.php | 47 + .../NotificationHandlerInterface.php | 32 + .../ProgressNotificationHandler.php | 52 + .../Request/ElicitationCallbackInterface.php | 26 + .../Request/ElicitationRequestHandler.php | 68 + .../Request/ListRootsRequestHandler.php | 73 + .../Request/RequestHandlerInterface.php | 38 + .../Request/RootsCallbackInterface.php | 32 + .../Request/SamplingCallbackInterface.php | 28 + .../Request/SamplingRequestHandler.php | 80 + admin/vendor/mcp/sdk/src/Client/Protocol.php | 689 ++ .../mcp/sdk/src/Client/State/ClientState.php | 148 + .../src/Client/State/ClientStateInterface.php | 128 + .../src/Client/Stateless/HeaderFactory.php | 102 + .../Client/Stateless/InputRequestResolver.php | 204 + .../src/Client/Stateless/RequestEnvelope.php | 78 + .../sdk/src/Client/Stateless/ToolCatalog.php | 206 + .../src/Client/Transport/BaseTransport.php | 126 + .../HeaderAwareTransportInterface.php | 37 + .../src/Client/Transport/HttpTransport.php | 423 ++ .../src/Client/Transport/StdioTransport.php | 339 + .../Client/Transport/TransportInterface.php | 107 + admin/vendor/mcp/sdk/src/Event/ErrorEvent.php | 59 + .../mcp/sdk/src/Event/NotificationEvent.php | 51 + .../sdk/src/Event/PromptListChangedEvent.php | 19 + .../vendor/mcp/sdk/src/Event/RequestEvent.php | 51 + .../src/Event/ResourceListChangedEvent.php | 19 + .../ResourceTemplateListChangedEvent.php | 19 + .../mcp/sdk/src/Event/ResponseEvent.php | 67 + .../sdk/src/Event/ToolListChangedEvent.php | 19 + .../src/Exception/BadMethodCallException.php | 19 + .../mcp/sdk/src/Exception/ClientException.php | 31 + .../Exception/ClientRegistrationException.php | 23 + .../src/Exception/ConfigurationException.php | 19 + .../sdk/src/Exception/ConnectionException.php | 21 + .../sdk/src/Exception/ContainerException.php | 18 + .../src/Exception/ElicitationException.php | 24 + .../mcp/sdk/src/Exception/Exception.php | 19 + .../sdk/src/Exception/ExceptionInterface.php | 19 + .../Exception/HandlerNotFoundException.php | 19 + .../Exception/InvalidArgumentException.php | 19 + .../src/Exception/InvalidCursorException.php | 24 + .../InvalidInputMessageException.php | 32 + .../mcp/sdk/src/Exception/LogicException.php | 19 + .../Exception/MissingRequestMetaException.php | 22 + ...ssingRequiredClientCapabilityException.php | 33 + .../Exception/NotFoundExceptionInterface.php | 19 + .../sdk/src/Exception/PromptGetException.php | 19 + .../src/Exception/PromptNotFoundException.php | 24 + .../sdk/src/Exception/RegistryException.php | 35 + .../sdk/src/Exception/RequestException.php | 40 + .../src/Exception/RequestStateException.php | 24 + .../Exception/ResourceNotFoundException.php | 24 + .../src/Exception/ResourceReadException.php | 19 + .../mcp/sdk/src/Exception/RootsException.php | 24 + .../sdk/src/Exception/RuntimeException.php | 19 + .../sdk/src/Exception/SamplingException.php | 24 + .../Exception/ServiceNotFoundException.php | 18 + .../sdk/src/Exception/TimeoutException.php | 21 + .../sdk/src/Exception/ToolCallException.php | 19 + .../src/Exception/ToolNotFoundException.php | 24 + .../mcp/sdk/src/JsonRpc/MessageFactory.php | 216 + .../vendor/mcp/sdk/src/Schema/Annotations.php | 118 + .../mcp/sdk/src/Schema/ClientCapabilities.php | 199 + .../sdk/src/Schema/Content/AudioContent.php | 120 + .../Schema/Content/BlobResourceContents.php | 99 + .../mcp/sdk/src/Schema/Content/Content.php | 25 + .../src/Schema/Content/EmbeddedResource.php | 152 + .../sdk/src/Schema/Content/ImageContent.php | 115 + .../sdk/src/Schema/Content/PromptMessage.php | 97 + .../src/Schema/Content/ResourceContents.php | 55 + .../sdk/src/Schema/Content/ResourceLink.php | 155 + .../src/Schema/Content/SamplingMessage.php | 162 + .../sdk/src/Schema/Content/TextContent.php | 96 + .../Schema/Content/TextResourceContents.php | 77 + .../src/Schema/Content/ToolResultContent.php | 121 + .../sdk/src/Schema/Content/ToolUseContent.php | 85 + .../Elicitation/AbstractSchemaDefinition.php | 67 + .../Elicitation/BooleanSchemaDefinition.php | 70 + .../Schema/Elicitation/ElicitationSchema.php | 160 + .../Elicitation/EnumSchemaDefinition.php | 120 + .../MultiSelectEnumSchemaDefinition.php | 127 + .../Elicitation/NumberSchemaDefinition.php | 115 + .../Elicitation/StringSchemaDefinition.php | 119 + .../TitledEnumSchemaDefinition.php | 98 + .../TitledMultiSelectEnumSchemaDefinition.php | 131 + .../mcp/sdk/src/Schema/Enum/CacheScope.php | 30 + .../mcp/sdk/src/Schema/Enum/ElicitAction.php | 24 + .../sdk/src/Schema/Enum/ElicitationMode.php | 31 + .../mcp/sdk/src/Schema/Enum/IconTheme.php | 25 + .../mcp/sdk/src/Schema/Enum/LoggingLevel.php | 62 + .../sdk/src/Schema/Enum/ProtocolVersion.php | 154 + .../mcp/sdk/src/Schema/Enum/ResultType.php | 30 + admin/vendor/mcp/sdk/src/Schema/Enum/Role.php | 23 + .../sdk/src/Schema/Enum/SamplingContext.php | 28 + .../sdk/src/Schema/Enum/ToolChoiceMode.php | 25 + .../Schema/Extension/AbstractExtension.php | 32 + .../sdk/src/Schema/Extension/Apps/McpApps.php | 60 + .../Schema/Extension/Apps/ToolVisibility.php | 26 + .../Extension/Apps/UiResourceContentMeta.php | 96 + .../Schema/Extension/Apps/UiResourceCsp.php | 90 + .../Extension/Apps/UiResourcePermissions.php | 76 + .../src/Schema/Extension/Apps/UiToolMeta.php | 82 + .../Schema/Extension/ExtensionIdentifier.php | 98 + .../Schema/Extension/ExtensionInterface.php | 70 + admin/vendor/mcp/sdk/src/Schema/Icon.php | 135 + .../mcp/sdk/src/Schema/Implementation.php | 121 + .../mcp/sdk/src/Schema/JsonRpc/Error.php | 212 + .../src/Schema/JsonRpc/HasMethodInterface.php | 27 + .../src/Schema/JsonRpc/MessageInterface.php | 25 + .../sdk/src/Schema/JsonRpc/Notification.php | 88 + .../mcp/sdk/src/Schema/JsonRpc/Request.php | 136 + .../mcp/sdk/src/Schema/JsonRpc/Response.php | 90 + .../src/Schema/JsonRpc/ResultInterface.php | 21 + admin/vendor/mcp/sdk/src/Schema/ModelHint.php | 53 + .../mcp/sdk/src/Schema/ModelPreferences.php | 119 + .../Notification/CancelledNotification.php | 68 + .../Notification/InitializedNotification.php | 37 + .../LoggingMessageNotification.php | 79 + .../Notification/ProgressNotification.php | 89 + .../PromptListChangedNotification.php | 37 + .../ResourceListChangedNotification.php | 37 + .../ResourceUpdatedNotification.php | 47 + .../RootsListChangedNotification.php | 43 + .../ToolListChangedNotification.php | 37 + admin/vendor/mcp/sdk/src/Schema/Page.php | 35 + admin/vendor/mcp/sdk/src/Schema/Prompt.php | 133 + .../mcp/sdk/src/Schema/PromptArgument.php | 79 + .../mcp/sdk/src/Schema/PromptReference.php | 44 + .../src/Schema/Request/CallToolRequest.php | 71 + .../Request/CompletionCompleteRequest.php | 85 + .../Request/CreateSamplingMessageRequest.php | 299 + .../src/Schema/Request/DiscoverRequest.php | 43 + .../sdk/src/Schema/Request/ElicitRequest.php | 126 + .../src/Schema/Request/GetPromptRequest.php | 71 + .../src/Schema/Request/InitializeRequest.php | 73 + .../src/Schema/Request/ListPromptsRequest.php | 60 + .../Request/ListResourceTemplatesRequest.php | 60 + .../Schema/Request/ListResourcesRequest.php | 60 + .../src/Schema/Request/ListRootsRequest.php | 51 + .../src/Schema/Request/ListToolsRequest.php | 60 + .../sdk/src/Schema/Request/PingRequest.php | 38 + .../Schema/Request/ReadResourceRequest.php | 55 + .../Request/ResourceSubscribeRequest.php | 54 + .../Request/ResourceUnsubscribeRequest.php | 54 + .../src/Schema/Request/SetLogLevelRequest.php | 65 + .../mcp/sdk/src/Schema/ResourceDefinition.php | 152 + .../mcp/sdk/src/Schema/ResourceReference.php | 44 + .../mcp/sdk/src/Schema/ResourceTemplate.php | 132 + .../sdk/src/Schema/Result/CallToolResult.php | 156 + .../Result/CompletionCompleteResult.php | 89 + .../Result/CreateSamplingMessageResult.php | 177 + .../sdk/src/Schema/Result/DiscoverResult.php | 70 + .../sdk/src/Schema/Result/ElicitResult.php | 100 + .../mcp/sdk/src/Schema/Result/EmptyResult.php | 39 + .../sdk/src/Schema/Result/GetPromptResult.php | 88 + .../src/Schema/Result/InitializeResult.php | 109 + .../src/Schema/Result/InputRequiredResult.php | 86 + .../src/Schema/Result/ListPromptsResult.php | 89 + .../Result/ListResourceTemplatesResult.php | 89 + .../src/Schema/Result/ListResourcesResult.php | 89 + .../sdk/src/Schema/Result/ListRootsResult.php | 85 + .../sdk/src/Schema/Result/ListToolsResult.php | 89 + .../src/Schema/Result/ReadResourceResult.php | 103 + admin/vendor/mcp/sdk/src/Schema/Root.php | 80 + .../mcp/sdk/src/Schema/ServerCapabilities.php | 202 + admin/vendor/mcp/sdk/src/Schema/Tool.php | 291 + .../mcp/sdk/src/Schema/ToolAnnotations.php | 96 + .../vendor/mcp/sdk/src/Schema/ToolChoice.php | 54 + .../mcp/sdk/src/Schema/Wire/McpHeader.php | 217 + admin/vendor/mcp/sdk/src/Server.php | 72 + admin/vendor/mcp/sdk/src/Server/Builder.php | 1173 ++++ .../mcp/sdk/src/Server/ClientGateway.php | 466 ++ .../mcp/sdk/src/Server/Configuration.php | 41 + .../Handler/ElementHandlerInterface.php | 23 + .../Notification/InitializedHandler.php | 34 + .../NotificationHandlerInterface.php | 25 + .../Server/Handler/PromptHandlerInterface.php | 28 + .../Handler/Request/CallToolHandler.php | 182 + .../Request/CompletionCompleteHandler.php | 112 + .../Handler/Request/GetPromptHandler.php | 94 + .../Handler/Request/InitializeHandler.php | 106 + .../Handler/Request/ListPromptsHandler.php | 56 + .../Request/ListResourceTemplatesHandler.php | 56 + .../Handler/Request/ListResourcesHandler.php | 56 + .../Handler/Request/ListToolsHandler.php | 57 + .../Server/Handler/Request/PingHandler.php | 41 + .../Handler/Request/ReadResourceHandler.php | 118 + .../Request/RequestHandlerInterface.php | 36 + .../Request/ResourceSubscribeHandler.php | 75 + .../Request/ResourceUnsubscribeHandler.php | 75 + .../Handler/Request/SetLogLevelHandler.php | 52 + .../Handler/ResourceHandlerInterface.php | 25 + .../ResourceTemplateHandlerInterface.php | 28 + .../Server/Handler/ToolHandlerInterface.php | 28 + .../mcp/sdk/src/Server/InputRequiredShim.php | 228 + .../vendor/mcp/sdk/src/Server/NativeClock.php | 22 + admin/vendor/mcp/sdk/src/Server/Protocol.php | 723 ++ .../mcp/sdk/src/Server/RequestContext.php | 157 + .../Resource/SessionSubscriptionManager.php | 94 + .../Resource/SubscriptionManagerInterface.php | 53 + .../src/Server/Session/FileSessionStore.php | 157 + .../Server/Session/InMemorySessionStore.php | 88 + .../src/Server/Session/Psr16SessionStore.php | 79 + .../mcp/sdk/src/Server/Session/Session.php | 176 + .../src/Server/Session/SessionInterface.php | 80 + .../sdk/src/Server/Session/SessionManager.php | 86 + .../Session/SessionManagerInterface.php | 47 + .../Server/Session/SessionStoreInterface.php | 64 + .../Server/Stateless/ElicitationReplay.php | 169 + .../sdk/src/Server/Stateless/InputContext.php | 125 + .../Stateless/InputRequestCapabilities.php | 96 + .../Server/Stateless/NotificationFilter.php | 115 + .../sdk/src/Server/Stateless/RequestMeta.php | 133 + .../Server/Stateless/RequestStateCodec.php | 125 + .../Stateless/StandardHeaderValidator.php | 299 + .../Server/Stateless/StatelessProtocol.php | 874 +++ .../src/Server/Stateless/StatelessResult.php | 113 + .../Subscription/InMemoryNotificationBus.php | 74 + .../Subscription/NotificationBusInterface.php | 53 + .../Subscription/Psr16NotificationBus.php | 113 + .../PublishingEventDispatcher.php | 53 + .../Subscription/RegistryChangePublisher.php | 81 + .../src/Server/Transport/BaseTransport.php | 143 + .../src/Server/Transport/CallbackStream.php | 157 + .../Transport/Http/JsonRpcErrorResponse.php | 41 + .../Middleware/AuthorizationMiddleware.php | 182 + .../ClientRegistrationMiddleware.php | 177 + .../Http/Middleware/CorsMiddleware.php | 152 + .../DnsRebindingProtectionMiddleware.php | 111 + .../Http/Middleware/OAuthProxyMiddleware.php | 268 + .../Middleware/OAuthRequestMetaMiddleware.php | 146 + .../ProtectedResourceMetadataMiddleware.php | 64 + .../Middleware/ProtocolVersionMiddleware.php | 96 + .../Http/MiddlewareRequestHandler.php | 48 + .../Http/OAuth/AuthorizationResult.php | 135 + .../AuthorizationTokenValidatorInterface.php | 32 + .../Http/OAuth/ClientRegistrarInterface.php | 44 + .../Transport/Http/OAuth/JwksProvider.php | 122 + .../Http/OAuth/JwksProviderInterface.php | 28 + .../Http/OAuth/JwtTokenValidator.php | 205 + .../LenientOidcDiscoveryMetadataPolicy.php | 54 + .../Transport/Http/OAuth/OidcDiscovery.php | 236 + .../Http/OAuth/OidcDiscoveryInterface.php | 31 + .../OidcDiscoveryMetadataPolicyInterface.php | 22 + .../Http/OAuth/ProtectedResourceMetadata.php | 245 + .../ProtectedResourceMetadataHandler.php | 60 + .../StrictOidcDiscoveryMetadataPolicy.php | 48 + .../Transport/Http/StatelessResponder.php | 99 + .../Server/Transport/InMemoryTransport.php | 75 + .../Transport/ManagesTransportCallbacks.php | 82 + .../src/Server/Transport/ReadsBoundedBody.php | 61 + .../StatelessAwareTransportInterface.php | 29 + .../Transport/StatelessHttpTransport.php | 136 + .../Server/Transport/Stdio/RunnerControl.php | 28 + .../Stdio/RunnerControlInterface.php | 22 + .../Server/Transport/Stdio/RunnerState.php | 24 + .../src/Server/Transport/StdioTransport.php | 214 + .../Transport/StreamableHttpTransport.php | 471 ++ .../Server/Transport/TransportInterface.php | 132 + .../mcp/sdk/src/Server/Wire/CachePolicy.php | 87 + .../sdk/src/Server/Wire/EraClassification.php | 61 + .../sdk/src/Server/Wire/InboundClassifier.php | 222 + .../mcp/sdk/src/Server/Wire/Rev2025Codec.php | 28 + .../mcp/sdk/src/Server/Wire/Rev2026Codec.php | 146 + .../src/Server/Wire/WireCodecInterface.php | 41 + .../nyholm/psr7-server/.github/FUNDING.yml | 3 + admin/vendor/nyholm/psr7-server/CHANGELOG.md | 71 + admin/vendor/nyholm/psr7-server/LICENSE | 21 + admin/vendor/nyholm/psr7-server/README.md | 38 + admin/vendor/nyholm/psr7-server/composer.json | 41 + .../psr7-server/src/ServerRequestCreator.php | 303 + .../src/ServerRequestCreatorInterface.php | 56 + admin/vendor/nyholm/psr7/CHANGELOG.md | 172 + admin/vendor/nyholm/psr7/LICENSE | 21 + admin/vendor/nyholm/psr7/README.md | 108 + admin/vendor/nyholm/psr7/composer.json | 49 + .../psr7/src/Factory/HttplugFactory.php | 53 + .../nyholm/psr7/src/Factory/Psr17Factory.php | 78 + admin/vendor/nyholm/psr7/src/MessageTrait.php | 235 + admin/vendor/nyholm/psr7/src/Request.php | 47 + admin/vendor/nyholm/psr7/src/RequestTrait.php | 127 + admin/vendor/nyholm/psr7/src/Response.php | 93 + .../vendor/nyholm/psr7/src/ServerRequest.php | 201 + admin/vendor/nyholm/psr7/src/Stream.php | 399 ++ admin/vendor/nyholm/psr7/src/StreamTrait.php | 57 + admin/vendor/nyholm/psr7/src/UploadedFile.php | 179 + admin/vendor/nyholm/psr7/src/Uri.php | 356 + admin/vendor/opis/json-schema/LICENSE | 176 + admin/vendor/opis/json-schema/NOTICE | 9 + admin/vendor/opis/json-schema/README.md | 66 + admin/vendor/opis/json-schema/SECURITY.md | 12 + admin/vendor/opis/json-schema/autoload.php | 23 + admin/vendor/opis/json-schema/composer.json | 52 + .../json-schema/src/CompliantValidator.php | 55 + .../opis/json-schema/src/ContentEncoding.php | 28 + .../opis/json-schema/src/ContentMediaType.php | 28 + .../json-schema/src/Errors/CustomError.php | 34 + .../json-schema/src/Errors/ErrorContainer.php | 148 + .../json-schema/src/Errors/ErrorFormatter.php | 423 ++ .../src/Errors/ValidationError.php | 96 + .../Exceptions/DuplicateSchemaIdException.php | 57 + .../Exceptions/InvalidKeywordException.php | 46 + .../src/Exceptions/InvalidPragmaException.php | 46 + .../src/Exceptions/ParseException.php | 45 + .../src/Exceptions/SchemaException.php | 25 + .../UnresolvedContentEncodingException.php | 44 + .../UnresolvedContentMediaTypeException.php | 44 + .../src/Exceptions/UnresolvedException.php | 57 + .../Exceptions/UnresolvedFilterException.php | 57 + .../UnresolvedReferenceException.php | 45 + admin/vendor/opis/json-schema/src/Filter.php | 29 + .../json-schema/src/Filters/CommonFilters.php | 43 + .../src/Filters/DataExistsFilter.php | 41 + .../src/Filters/DateTimeFilters.php | 100 + .../src/Filters/FilterExistsFilter.php | 54 + .../src/Filters/FormatExistsFilter.php | 54 + .../src/Filters/GlobalVarExistsFilter.php | 47 + .../src/Filters/SchemaExistsFilter.php | 84 + .../src/Filters/SlotExistsFilter.php | 36 + admin/vendor/opis/json-schema/src/Format.php | 27 + .../src/Formats/DateTimeFormats.php | 70 + .../json-schema/src/Formats/IriFormats.php | 135 + .../json-schema/src/Formats/MiscFormats.php | 59 + .../json-schema/src/Formats/UriFormats.php | 69 + admin/vendor/opis/json-schema/src/Helper.php | 392 ++ .../opis/json-schema/src/Info/DataInfo.php | 114 + .../opis/json-schema/src/Info/SchemaInfo.php | 117 + .../opis/json-schema/src/JsonPointer.php | 410 ++ admin/vendor/opis/json-schema/src/Keyword.php | 30 + .../opis/json-schema/src/KeywordValidator.php | 32 + .../AbstractKeywordValidator.php | 44 + .../CallbackKeywordValidator.php | 59 + .../PragmaKeywordValidator.php | 63 + .../src/Keywords/AbstractRefKeyword.php | 129 + .../src/Keywords/AdditionalItemsKeyword.php | 97 + .../Keywords/AdditionalPropertiesKeyword.php | 83 + .../json-schema/src/Keywords/AllOfKeyword.php | 78 + .../json-schema/src/Keywords/AnyOfKeyword.php | 94 + .../src/Keywords/ConstDataKeyword.php | 55 + .../json-schema/src/Keywords/ConstKeyword.php | 51 + .../src/Keywords/ContainsKeyword.php | 143 + .../src/Keywords/ContentEncodingKeyword.php | 77 + .../src/Keywords/ContentMediaTypeKeyword.php | 83 + .../src/Keywords/ContentSchemaKeyword.php | 64 + .../src/Keywords/DefaultKeyword.php | 53 + .../src/Keywords/DependenciesKeyword.php | 95 + .../src/Keywords/DependentRequiredKeyword.php | 66 + .../src/Keywords/DependentSchemasKeyword.php | 76 + .../src/Keywords/EnumDataKeyword.php | 55 + .../json-schema/src/Keywords/EnumKeyword.php | 79 + .../json-schema/src/Keywords/ErrorTrait.php | 55 + .../Keywords/ExclusiveMaximumDataKeyword.php | 55 + .../src/Keywords/ExclusiveMaximumKeyword.php | 50 + .../Keywords/ExclusiveMinimumDataKeyword.php | 55 + .../src/Keywords/ExclusiveMinimumKeyword.php | 50 + .../src/Keywords/FiltersKeyword.php | 93 + .../src/Keywords/FormatDataKeyword.php | 76 + .../src/Keywords/FormatKeyword.php | 82 + .../src/Keywords/IfThenElseKeyword.php | 104 + .../json-schema/src/Keywords/ItemsKeyword.php | 162 + .../Keywords/IterableDataValidationTrait.php | 110 + .../src/Keywords/MaxItemsDataKeyword.php | 55 + .../src/Keywords/MaxItemsKeyword.php | 58 + .../src/Keywords/MaxLengthDataKeyword.php | 55 + .../src/Keywords/MaxLengthKeyword.php | 58 + .../src/Keywords/MaxPropertiesDataKeyword.php | 55 + .../src/Keywords/MaxPropertiesKeywords.php | 58 + .../src/Keywords/MaximumDataKeyword.php | 55 + .../src/Keywords/MaximumKeyword.php | 54 + .../src/Keywords/MinItemsDataKeyword.php | 55 + .../src/Keywords/MinItemsKeyword.php | 58 + .../src/Keywords/MinLengthDataKeyword.php | 55 + .../src/Keywords/MinLengthKeyword.php | 61 + .../src/Keywords/MinPropertiesDataKeyword.php | 55 + .../src/Keywords/MinPropertiesKeyword.php | 58 + .../src/Keywords/MinimumDataKeyword.php | 55 + .../src/Keywords/MinimumKeyword.php | 54 + .../src/Keywords/MultipleOfDataKeyword.php | 55 + .../src/Keywords/MultipleOfKeyword.php | 55 + .../json-schema/src/Keywords/NotKeyword.php | 66 + .../opis/json-schema/src/Keywords/OfTrait.php | 45 + .../json-schema/src/Keywords/OneOfKeyword.php | 98 + .../src/Keywords/PatternDataKeyword.php | 56 + .../src/Keywords/PatternKeyword.php | 53 + .../src/Keywords/PatternPropertiesKeyword.php | 119 + .../src/Keywords/PointerRefKeyword.php | 54 + .../src/Keywords/PropertiesKeyword.php | 109 + .../src/Keywords/PropertyNamesKeyword.php | 74 + .../src/Keywords/RecursiveRefKeyword.php | 138 + .../src/Keywords/RequiredDataKeyword.php | 85 + .../src/Keywords/RequiredKeyword.php | 68 + .../json-schema/src/Keywords/SlotsKeyword.php | 151 + .../src/Keywords/TemplateRefKeyword.php | 119 + .../json-schema/src/Keywords/TypeKeyword.php | 58 + .../src/Keywords/URIRefKeyword.php | 55 + .../src/Keywords/UnevaluatedItemsKeyword.php | 75 + .../Keywords/UnevaluatedPropertiesKeyword.php | 77 + .../src/Keywords/UniqueItemsDataKeyword.php | 51 + .../src/Keywords/UniqueItemsKeyword.php | 57 + .../src/Parsers/DataKeywordTrait.php | 59 + .../src/Parsers/DefaultVocabulary.php | 56 + .../opis/json-schema/src/Parsers/Draft.php | 82 + .../src/Parsers/DraftOptionTrait.php | 43 + .../src/Parsers/Drafts/Draft06.php | 145 + .../src/Parsers/Drafts/Draft07.php | 43 + .../src/Parsers/Drafts/Draft201909.php | 159 + .../src/Parsers/Drafts/Draft202012.php | 163 + .../json-schema/src/Parsers/KeywordParser.php | 62 + .../src/Parsers/KeywordParserTrait.php | 76 + .../src/Parsers/KeywordValidatorParser.php | 34 + .../PragmaKeywordValidatorParser.php | 57 + .../Keywords/AdditionalItemsKeywordParser.php | 63 + .../AdditionalPropertiesKeywordParser.php | 54 + .../Parsers/Keywords/AllOfKeywordParser.php | 75 + .../Parsers/Keywords/AnyOfKeywordParser.php | 75 + .../Parsers/Keywords/ConstKeywordParser.php | 74 + .../Keywords/ContainsKeywordParser.php | 85 + .../Keywords/ContentEncodingKeywordParser.php | 62 + .../ContentMediaTypeKeywordParser.php | 62 + .../Keywords/ContentSchemaKeywordParser.php | 60 + .../Parsers/Keywords/DefaultKeywordParser.php | 85 + .../Keywords/DependenciesKeywordParser.php | 80 + .../DependentRequiredKeywordParser.php | 70 + .../DependentSchemasKeywordParser.php | 73 + .../Parsers/Keywords/EnumKeywordParser.php | 107 + .../ExclusiveMaximumKeywordParser.php | 70 + .../ExclusiveMinimumKeywordParser.php | 70 + .../Parsers/Keywords/FiltersKeywordParser.php | 161 + .../Parsers/Keywords/FormatKeywordParser.php | 71 + .../Keywords/IfThenElseKeywordParser.php | 109 + .../Parsers/Keywords/ItemsKeywordParser.php | 117 + .../Keywords/MaxItemsKeywordParser.php | 63 + .../Keywords/MaxLengthKeywordParser.php | 63 + .../Keywords/MaxPropertiesKeywordParser.php | 63 + .../Parsers/Keywords/MaximumKeywordParser.php | 91 + .../Keywords/MinItemsKeywordParser.php | 67 + .../Keywords/MinLengthKeywordParser.php | 67 + .../Keywords/MinPropertiesKeywordParser.php | 67 + .../Parsers/Keywords/MinimumKeywordParser.php | 91 + .../Keywords/MultipleOfKeywordParser.php | 67 + .../src/Parsers/Keywords/NotKeywordParser.php | 58 + .../Parsers/Keywords/OneOfKeywordParser.php | 79 + .../Parsers/Keywords/PatternKeywordParser.php | 67 + .../PatternPropertiesKeywordParser.php | 67 + .../Keywords/PropertiesKeywordParser.php | 64 + .../Keywords/PropertyNamesKeywordParser.php | 58 + .../src/Parsers/Keywords/RefKeywordParser.php | 228 + .../Keywords/RequiredKeywordParser.php | 105 + .../Parsers/Keywords/SlotsKeywordParser.php | 64 + .../Parsers/Keywords/TypeKeywordParser.php | 78 + .../UnevaluatedItemsKeywordParser.php | 73 + .../UnevaluatedPropertiesKeywordParser.php | 68 + .../Keywords/UniqueItemsKeywordParser.php | 63 + .../json-schema/src/Parsers/PragmaParser.php | 84 + .../src/Parsers/Pragmas/CastPragmaParser.php | 45 + .../Parsers/Pragmas/GlobalsPragmaParser.php | 48 + .../Parsers/Pragmas/MaxErrorsPragmaParser.php | 44 + .../src/Parsers/Pragmas/SlotsPragmaParser.php | 65 + .../json-schema/src/Parsers/ResolverTrait.php | 38 + .../json-schema/src/Parsers/SchemaParser.php | 644 ++ .../src/Parsers/VariablesTrait.php | 41 + .../json-schema/src/Parsers/Vocabulary.php | 126 + admin/vendor/opis/json-schema/src/Pragma.php | 33 + .../json-schema/src/Pragmas/CastPragma.php | 198 + .../json-schema/src/Pragmas/GlobalsPragma.php | 61 + .../src/Pragmas/MaxErrorsPragma.php | 55 + .../json-schema/src/Pragmas/SlotsPragma.php | 52 + .../src/Resolvers/ContentEncodingResolver.php | 134 + .../Resolvers/ContentMediaTypeResolver.php | 145 + .../src/Resolvers/FilterResolver.php | 265 + .../src/Resolvers/FormatResolver.php | 143 + .../src/Resolvers/SchemaResolver.php | 342 + admin/vendor/opis/json-schema/src/Schema.php | 28 + .../opis/json-schema/src/SchemaLoader.php | 447 ++ .../opis/json-schema/src/SchemaValidator.php | 29 + .../src/Schemas/AbstractSchema.php | 43 + .../json-schema/src/Schemas/BooleanSchema.php | 49 + .../json-schema/src/Schemas/EmptySchema.php | 53 + .../src/Schemas/ExceptionSchema.php | 48 + .../json-schema/src/Schemas/LazySchema.php | 60 + .../json-schema/src/Schemas/ObjectSchema.php | 148 + admin/vendor/opis/json-schema/src/Uri.php | 94 + .../json-schema/src/ValidationContext.php | 715 ++ .../opis/json-schema/src/ValidationResult.php | 53 + .../vendor/opis/json-schema/src/Validator.php | 317 + .../vendor/opis/json-schema/src/Variables.php | 28 + .../src/Variables/RefVariablesContainer.php | 102 + .../src/Variables/VariablesContainer.php | 162 + admin/vendor/opis/string/.editorconfig | 8 + admin/vendor/opis/string/CHANGELOG.md | 10 + admin/vendor/opis/string/LICENSE | 176 + admin/vendor/opis/string/NOTICE | 9 + admin/vendor/opis/string/README.md | 51 + admin/vendor/opis/string/composer.json | 43 + admin/vendor/opis/string/res/ascii.php | 751 +++ admin/vendor/opis/string/res/fold.php | 1417 ++++ admin/vendor/opis/string/res/lower.php | 1396 ++++ admin/vendor/opis/string/res/upper.php | 1413 ++++ .../Exception/InvalidCodePointException.php | 46 + .../src/Exception/InvalidStringException.php | 61 + .../string/src/Exception/UnicodeException.php | 25 + .../vendor/opis/string/src/UnicodeString.php | 1702 +++++ admin/vendor/opis/uri/LICENSE | 176 + admin/vendor/opis/uri/README.md | 40 + admin/vendor/opis/uri/autoload.php | 42 + admin/vendor/opis/uri/composer.json | 44 + admin/vendor/opis/uri/src/Punycode.php | 282 + .../vendor/opis/uri/src/PunycodeException.php | 25 + admin/vendor/opis/uri/src/Uri.php | 965 +++ admin/vendor/opis/uri/src/UriTemplate.php | 520 ++ .../.github/workflows/Build-Test.yml | 50 + admin/vendor/php-http/curl-client/.php_cs | 9 + .../vendor/php-http/curl-client/CHANGELOG.md | 247 + admin/vendor/php-http/curl-client/LICENSE | 19 + admin/vendor/php-http/curl-client/README.md | 42 + .../vendor/php-http/curl-client/composer.json | 56 + admin/vendor/php-http/curl-client/puli.json | 242 + .../php-http/curl-client/src/Client.php | 398 ++ .../php-http/curl-client/src/CurlPromise.php | 111 + .../php-http/curl-client/src/MultiRunner.php | 131 + .../php-http/curl-client/src/PromiseCore.php | 252 + .../curl-client/src/ResponseBuilder.php | 24 + .../php-http/discovery/.php-cs-fixer.php | 17 + admin/vendor/php-http/discovery/CHANGELOG.md | 392 ++ admin/vendor/php-http/discovery/LICENSE | 19 + admin/vendor/php-http/discovery/README.md | 122 + admin/vendor/php-http/discovery/composer.json | 64 + .../php-http/discovery/src/ClassDiscovery.php | 255 + .../discovery/src/Composer/Plugin.php | 474 ++ .../php-http/discovery/src/Exception.php | 12 + .../ClassInstantiationFailedException.php | 14 + .../Exception/DiscoveryFailedException.php | 51 + .../Exception/NoCandidateFoundException.php | 47 + .../src/Exception/NotFoundException.php | 16 + .../Exception/PuliUnavailableException.php | 12 + .../StrategyUnavailableException.php | 15 + .../src/HttpAsyncClientDiscovery.php | 32 + .../discovery/src/HttpClientDiscovery.php | 34 + .../discovery/src/MessageFactoryDiscovery.php | 34 + .../discovery/src/NotFoundException.php | 16 + .../php-http/discovery/src/Psr17Factory.php | 303 + .../discovery/src/Psr17FactoryDiscovery.php | 137 + .../php-http/discovery/src/Psr18Client.php | 52 + .../discovery/src/Psr18ClientDiscovery.php | 33 + .../src/Strategy/CommonClassesStrategy.php | 185 + .../Strategy/CommonPsr17ClassesStrategy.php | 104 + .../src/Strategy/DiscoveryStrategy.php | 23 + .../src/Strategy/MockClientStrategy.php | 24 + .../src/Strategy/PuliBetaStrategy.php | 90 + .../discovery/src/StreamFactoryDiscovery.php | 34 + .../discovery/src/UriFactoryDiscovery.php | 34 + .../php-http/httplug/.php-cs-fixer.dist.php | 16 + admin/vendor/php-http/httplug/CHANGELOG.md | 144 + admin/vendor/php-http/httplug/LICENSE | 20 + admin/vendor/php-http/httplug/README.md | 54 + admin/vendor/php-http/httplug/composer.json | 40 + admin/vendor/php-http/httplug/puli.json | 12 + .../vendor/php-http/httplug/src/Exception.php | 14 + .../httplug/src/Exception/HttpException.php | 65 + .../src/Exception/NetworkException.php | 28 + .../src/Exception/RequestAwareTrait.php | 23 + .../src/Exception/RequestException.php | 29 + .../src/Exception/TransferException.php | 14 + .../php-http/httplug/src/HttpAsyncClient.php | 25 + .../php-http/httplug/src/HttpClient.php | 17 + .../src/Promise/HttpFulfilledPromise.php | 45 + .../src/Promise/HttpRejectedPromise.php | 49 + .../php-http/message/.php-cs-fixer.dist.php | 19 + admin/vendor/php-http/message/CHANGELOG.md | 278 + admin/vendor/php-http/message/LICENSE | 19 + admin/vendor/php-http/message/README.md | 51 + admin/vendor/php-http/message/apigen.neon | 6 + admin/vendor/php-http/message/composer.json | 63 + admin/vendor/php-http/message/puli.json | 111 + .../php-http/message/src/Authentication.php | 25 + .../src/Authentication/AutoBasicAuth.php | 45 + .../message/src/Authentication/BasicAuth.php | 41 + .../message/src/Authentication/Bearer.php | 34 + .../message/src/Authentication/Chain.php | 44 + .../message/src/Authentication/Header.php | 33 + .../message/src/Authentication/Matching.php | 66 + .../message/src/Authentication/QueryParam.php | 44 + .../src/Authentication/RequestConditional.php | 40 + .../message/src/Authentication/Wsse.php | 64 + .../message/src/Builder/ResponseBuilder.php | 146 + admin/vendor/php-http/message/src/Cookie.php | 524 ++ .../vendor/php-http/message/src/CookieJar.php | 206 + .../php-http/message/src/CookieUtil.php | 53 + .../src/Decorator/MessageDecorator.php | 98 + .../src/Decorator/RequestDecorator.php | 66 + .../src/Decorator/ResponseDecorator.php | 44 + .../message/src/Decorator/StreamDecorator.php | 93 + .../message/src/Encoding/ChunkStream.php | 30 + .../message/src/Encoding/CompressStream.php | 39 + .../message/src/Encoding/DechunkStream.php | 23 + .../message/src/Encoding/DecompressStream.php | 39 + .../message/src/Encoding/DeflateStream.php | 35 + .../message/src/Encoding/Filter/Chunk.php | 27 + .../message/src/Encoding/FilteredStream.php | 214 + .../message/src/Encoding/GzipDecodeStream.php | 39 + .../message/src/Encoding/GzipEncodeStream.php | 39 + .../message/src/Encoding/InflateStream.php | 39 + .../vendor/php-http/message/src/Exception.php | 10 + .../Exception/UnexpectedValueException.php | 9 + .../vendor/php-http/message/src/Formatter.php | 35 + .../src/Formatter/CurlCommandFormatter.php | 97 + .../Formatter/FullHttpMessageFormatter.php | 110 + .../message/src/Formatter/SimpleFormatter.php | 46 + .../DiactorosMessageFactory.php | 80 + .../MessageFactory/GuzzleMessageFactory.php | 53 + .../src/MessageFactory/SlimMessageFactory.php | 72 + .../php-http/message/src/RequestMatcher.php | 26 + .../RequestMatcher/CallbackRequestMatcher.php | 29 + .../RequestMatcher/RegexRequestMatcher.php | 38 + .../src/RequestMatcher/RequestMatcher.php | 76 + .../message/src/Stream/BufferedStream.php | 237 + .../StreamFactory/DiactorosStreamFactory.php | 49 + .../src/StreamFactory/GuzzleStreamFactory.php | 30 + .../src/StreamFactory/SlimStreamFactory.php | 40 + .../src/UriFactory/DiactorosUriFactory.php | 37 + .../src/UriFactory/GuzzleUriFactory.php | 31 + .../message/src/UriFactory/SlimUriFactory.php | 34 + admin/vendor/php-http/message/src/filters.php | 6 + admin/vendor/php-http/promise/CHANGELOG.md | 70 + admin/vendor/php-http/promise/LICENSE | 19 + admin/vendor/php-http/promise/README.md | 48 + admin/vendor/php-http/promise/composer.json | 33 + .../vendor/php-http/promise/phpstan.neon.dist | 6 + .../php-http/promise/src/FulfilledPromise.php | 51 + admin/vendor/php-http/promise/src/Promise.php | 69 + .../php-http/promise/src/RejectedPromise.php | 48 + .../reflection-common/.github/dependabot.yml | 7 + .../.github/workflows/push.yml | 223 + .../phpdocumentor/reflection-common/LICENSE | 22 + .../phpdocumentor/reflection-common/README.md | 11 + .../reflection-common/composer.json | 28 + .../reflection-common/src/Element.php | 30 + .../reflection-common/src/File.php | 35 + .../reflection-common/src/Fqsen.php | 89 + .../reflection-common/src/Location.php | 53 + .../reflection-common/src/Project.php | 25 + .../reflection-common/src/ProjectFactory.php | 28 + .../phpdocumentor/reflection-docblock/LICENSE | 21 + .../reflection-docblock/README.md | 74 + .../reflection-docblock/composer.json | 58 + .../reflection-docblock/src/DocBlock.php | 228 + .../src/DocBlock/Description.php | 118 + .../src/DocBlock/DescriptionFactory.php | 179 + .../src/DocBlock/ExampleFinder.php | 158 + .../src/DocBlock/Serializer.php | 156 + .../src/DocBlock/StandardTagFactory.php | 392 ++ .../reflection-docblock/src/DocBlock/Tag.php | 31 + .../src/DocBlock/TagFactory.php | 73 + .../src/DocBlock/Tags/Author.php | 102 + .../src/DocBlock/Tags/BaseTag.php | 53 + .../src/DocBlock/Tags/Covers.php | 99 + .../src/DocBlock/Tags/Deprecated.php | 108 + .../src/DocBlock/Tags/Example.php | 197 + .../src/DocBlock/Tags/Extends_.php | 30 + .../Tags/Factory/AbstractPHPStanFactory.php | 130 + .../DocBlock/Tags/Factory/ExtendsFactory.php | 52 + .../src/DocBlock/Tags/Factory/Factory.php | 41 + .../Tags/Factory/ImplementsFactory.php | 52 + .../DocBlock/Tags/Factory/MethodFactory.php | 82 + .../Tags/Factory/MethodParameterFactory.php | 100 + .../DocBlock/Tags/Factory/MixinFactory.php | 52 + .../DocBlock/Tags/Factory/PHPStanFactory.php | 16 + .../DocBlock/Tags/Factory/ParamFactory.php | 84 + .../DocBlock/Tags/Factory/PropertyFactory.php | 54 + .../Tags/Factory/PropertyReadFactory.php | 54 + .../Tags/Factory/PropertyWriteFactory.php | 54 + .../DocBlock/Tags/Factory/ReturnFactory.php | 52 + .../Tags/Factory/TemplateCovariantFactory.php | 53 + .../DocBlock/Tags/Factory/TemplateFactory.php | 56 + .../DocBlock/Tags/Factory/ThrowsFactory.php | 52 + .../src/DocBlock/Tags/Factory/VarFactory.php | 54 + .../src/DocBlock/Tags/Formatter.php | 24 + .../Tags/Formatter/AlignFormatter.php | 50 + .../Tags/Formatter/PassthroughFormatter.php | 30 + .../src/DocBlock/Tags/Generic.php | 89 + .../src/DocBlock/Tags/Implements_.php | 30 + .../src/DocBlock/Tags/InvalidTag.php | 150 + .../src/DocBlock/Tags/Link.php | 76 + .../src/DocBlock/Tags/Method.php | 135 + .../src/DocBlock/Tags/MethodParameter.php | 91 + .../src/DocBlock/Tags/Mixin.php | 30 + .../src/DocBlock/Tags/Param.php | 94 + .../src/DocBlock/Tags/Property.php | 68 + .../src/DocBlock/Tags/PropertyRead.php | 68 + .../src/DocBlock/Tags/PropertyWrite.php | 68 + .../src/DocBlock/Tags/Reference/Fqsen.php | 37 + .../src/DocBlock/Tags/Reference/Reference.php | 22 + .../src/DocBlock/Tags/Reference/Url.php | 35 + .../src/DocBlock/Tags/Return_.php | 30 + .../src/DocBlock/Tags/See.php | 104 + .../src/DocBlock/Tags/Since.php | 102 + .../src/DocBlock/Tags/Source.php | 115 + .../src/DocBlock/Tags/TagWithType.php | 50 + .../src/DocBlock/Tags/Template.php | 85 + .../src/DocBlock/Tags/TemplateCovariant.php | 30 + .../src/DocBlock/Tags/TemplateExtends.php | 29 + .../src/DocBlock/Tags/TemplateImplements.php | 29 + .../src/DocBlock/Tags/Throws.php | 30 + .../src/DocBlock/Tags/Uses.php | 98 + .../src/DocBlock/Tags/Var_.php | 68 + .../src/DocBlock/Tags/Version.php | 105 + .../src/DocBlockFactory.php | 284 + .../src/DocBlockFactoryInterface.php | 23 + .../src/Exception/CannotCreateTag.php | 11 + .../src/Exception/ParserException.php | 19 + .../src/Exception/PcreException.php | 44 + .../Exception/ReflectionDocblockException.php | 11 + .../reflection-docblock/src/Utils.php | 62 + .../.phpdoc/template/base.html.twig | 15 + .../phpdocumentor/type-resolver/LICENSE | 21 + .../phpdocumentor/type-resolver/README.md | 177 + .../phpdocumentor/type-resolver/composer.json | 51 + .../type-resolver/docs/generics.rst | 29 + .../type-resolver/docs/getting-started.rst | 32 + .../type-resolver/docs/index.rst | 19 + .../type-resolver/docs/upgrade-v1-to-v2.rst | 40 + .../type-resolver/phpdoc.dist.xml | 46 + .../type-resolver/src/FqsenResolver.php | 80 + .../type-resolver/src/PseudoType.php | 19 + .../src/PseudoTypes/ArrayKey.php | 44 + .../src/PseudoTypes/ArrayShape.php | 53 + .../src/PseudoTypes/ArrayShapeItem.php | 18 + .../src/PseudoTypes/CallableArray.php | 43 + .../src/PseudoTypes/CallableString.php | 39 + .../src/PseudoTypes/ClassString.php | 56 + .../src/PseudoTypes/ClosedResource.php | 36 + .../src/PseudoTypes/Conditional.php | 90 + .../PseudoTypes/ConditionalForParameter.php | 90 + .../src/PseudoTypes/ConstExpression.php | 55 + .../src/PseudoTypes/EnumString.php | 56 + .../type-resolver/src/PseudoTypes/False_.php | 36 + .../src/PseudoTypes/FloatValue.php | 45 + .../type-resolver/src/PseudoTypes/Generic.php | 56 + .../src/PseudoTypes/HtmlEscapedString.php | 39 + .../type-resolver/src/PseudoTypes/IntMask.php | 54 + .../src/PseudoTypes/IntMaskOf.php | 49 + .../src/PseudoTypes/IntegerRange.php | 61 + .../src/PseudoTypes/IntegerValue.php | 45 + .../src/PseudoTypes/InterfaceString.php | 56 + .../type-resolver/src/PseudoTypes/KeyOf.php | 48 + .../src/PseudoTypes/ListShape.php | 16 + .../src/PseudoTypes/ListShapeItem.php | 9 + .../type-resolver/src/PseudoTypes/List_.php | 49 + .../src/PseudoTypes/LiteralString.php | 39 + .../src/PseudoTypes/LowercaseString.php | 39 + .../src/PseudoTypes/NegativeInteger.php | 39 + .../src/PseudoTypes/NeverReturn.php | 36 + .../src/PseudoTypes/NeverReturns.php | 36 + .../src/PseudoTypes/NoReturn.php | 36 + .../src/PseudoTypes/NonEmptyArray.php | 47 + .../src/PseudoTypes/NonEmptyList.php | 49 + .../PseudoTypes/NonEmptyLowercaseString.php | 39 + .../src/PseudoTypes/NonEmptyString.php | 39 + .../src/PseudoTypes/NonFalsyString.php | 36 + .../src/PseudoTypes/NonNegativeInteger.php | 39 + .../src/PseudoTypes/NonPositiveInteger.php | 36 + .../src/PseudoTypes/NonZeroInteger.php | 39 + .../src/PseudoTypes/NumericString.php | 39 + .../src/PseudoTypes/Numeric_.php | 47 + .../src/PseudoTypes/ObjectShape.php | 41 + .../src/PseudoTypes/ObjectShapeItem.php | 9 + .../src/PseudoTypes/OffsetAccess.php | 67 + .../src/PseudoTypes/OpenResource.php | 36 + .../src/PseudoTypes/PositiveInteger.php | 39 + .../src/PseudoTypes/PrivatePropertiesOf.php | 27 + .../src/PseudoTypes/PropertiesOf.php | 53 + .../src/PseudoTypes/ProtectedPropertiesOf.php | 27 + .../src/PseudoTypes/PublicPropertiesOf.php | 27 + .../type-resolver/src/PseudoTypes/Scalar.php | 43 + .../src/PseudoTypes/ShapeItem.php | 56 + .../src/PseudoTypes/StringValue.php | 47 + .../src/PseudoTypes/TraitString.php | 56 + .../type-resolver/src/PseudoTypes/True_.php | 36 + .../src/PseudoTypes/TruthyString.php | 36 + .../type-resolver/src/PseudoTypes/ValueOf.php | 49 + .../phpdocumentor/type-resolver/src/Type.php | 25 + .../type-resolver/src/TypeResolver.php | 692 ++ .../type-resolver/src/Types/AbstractList.php | 73 + .../src/Types/AggregatedType.php | 125 + .../type-resolver/src/Types/Array_.php | 50 + .../type-resolver/src/Types/Boolean.php | 32 + .../src/Types/CallableParameter.php | 89 + .../type-resolver/src/Types/Callable_.php | 80 + .../type-resolver/src/Types/Compound.php | 38 + .../type-resolver/src/Types/Context.php | 95 + .../src/Types/ContextFactory.php | 422 ++ .../type-resolver/src/Types/Expression.php | 51 + .../type-resolver/src/Types/Float_.php | 32 + .../type-resolver/src/Types/Integer.php | 32 + .../type-resolver/src/Types/Intersection.php | 37 + .../type-resolver/src/Types/Iterable_.php | 38 + .../type-resolver/src/Types/Mixed_.php | 32 + .../type-resolver/src/Types/Never_.php | 35 + .../type-resolver/src/Types/Null_.php | 32 + .../type-resolver/src/Types/Nullable.php | 51 + .../type-resolver/src/Types/Object_.php | 69 + .../type-resolver/src/Types/Parent_.php | 34 + .../type-resolver/src/Types/Resource_.php | 32 + .../type-resolver/src/Types/Self_.php | 56 + .../type-resolver/src/Types/Static_.php | 61 + .../type-resolver/src/Types/String_.php | 32 + .../type-resolver/src/Types/This.php | 35 + .../type-resolver/src/Types/Void_.php | 35 + admin/vendor/phpstan/phpdoc-parser/LICENSE | 22 + admin/vendor/phpstan/phpdoc-parser/README.md | 209 + .../vendor/phpstan/phpdoc-parser/UPGRADING.md | 129 + .../phpstan/phpdoc-parser/composer.json | 44 + .../src/Ast/AbstractNodeVisitor.php | 34 + .../phpdoc-parser/src/Ast/Attribute.php | 18 + .../phpstan/phpdoc-parser/src/Ast/Comment.php | 36 + .../Ast/ConstExpr/ConstExprArrayItemNode.php | 47 + .../src/Ast/ConstExpr/ConstExprArrayNode.php | 43 + .../src/Ast/ConstExpr/ConstExprFalseNode.php | 31 + .../src/Ast/ConstExpr/ConstExprFloatNode.php | 38 + .../Ast/ConstExpr/ConstExprIntegerNode.php | 38 + .../src/Ast/ConstExpr/ConstExprNode.php | 10 + .../src/Ast/ConstExpr/ConstExprNullNode.php | 31 + .../src/Ast/ConstExpr/ConstExprStringNode.php | 93 + .../src/Ast/ConstExpr/ConstExprTrueNode.php | 31 + .../src/Ast/ConstExpr/ConstFetchNode.php | 47 + .../ConstExpr/DoctrineConstExprStringNode.php | 55 + .../phpstan/phpdoc-parser/src/Ast/Node.php | 22 + .../phpdoc-parser/src/Ast/NodeAttributes.php | 42 + .../phpdoc-parser/src/Ast/NodeTraverser.php | 312 + .../phpdoc-parser/src/Ast/NodeVisitor.php | 87 + .../src/Ast/NodeVisitor/CloningVisitor.php | 20 + .../Ast/PhpDoc/AssertTagMethodValueNode.php | 58 + .../Ast/PhpDoc/AssertTagPropertyValueNode.php | 58 + .../src/Ast/PhpDoc/AssertTagValueNode.php | 55 + .../src/Ast/PhpDoc/DeprecatedTagValueNode.php | 40 + .../PhpDoc/Doctrine/DoctrineAnnotation.php | 48 + .../Ast/PhpDoc/Doctrine/DoctrineArgument.php | 55 + .../src/Ast/PhpDoc/Doctrine/DoctrineArray.php | 46 + .../Ast/PhpDoc/Doctrine/DoctrineArrayItem.php | 60 + .../PhpDoc/Doctrine/DoctrineTagValueNode.php | 47 + .../src/Ast/PhpDoc/ExtendsTagValueNode.php | 44 + .../src/Ast/PhpDoc/GenericTagValueNode.php | 39 + .../src/Ast/PhpDoc/ImplementsTagValueNode.php | 44 + .../src/Ast/PhpDoc/InvalidTagValueNode.php | 68 + .../src/Ast/PhpDoc/MethodTagValueNode.php | 68 + .../PhpDoc/MethodTagValueParameterNode.php | 57 + .../src/Ast/PhpDoc/MixinTagValueNode.php | 44 + .../PhpDoc/ParamClosureThisTagValueNode.php | 47 + ...ImmediatelyInvokedCallableTagValueNode.php | 43 + .../ParamLaterInvokedCallableTagValueNode.php | 43 + .../src/Ast/PhpDoc/ParamOutTagValueNode.php | 47 + .../src/Ast/PhpDoc/ParamTagValueNode.php | 55 + .../src/Ast/PhpDoc/PhpDocChildNode.php | 10 + .../src/Ast/PhpDoc/PhpDocNode.php | 400 ++ .../src/Ast/PhpDoc/PhpDocTagNode.php | 47 + .../src/Ast/PhpDoc/PhpDocTagValueNode.php | 10 + .../src/Ast/PhpDoc/PhpDocTextNode.php | 38 + .../src/Ast/PhpDoc/PropertyTagValueNode.php | 47 + ...PureUnlessCallableIsImpureTagValueNode.php | 43 + ...ureUnlessParameterIsPassedTagValueNode.php | 43 + .../Ast/PhpDoc/RequireExtendsTagValueNode.php | 44 + .../PhpDoc/RequireImplementsTagValueNode.php | 44 + .../src/Ast/PhpDoc/ReturnTagValueNode.php | 44 + .../src/Ast/PhpDoc/SealedTagValueNode.php | 44 + .../src/Ast/PhpDoc/SelfOutTagValueNode.php | 44 + .../src/Ast/PhpDoc/TemplateTagValueNode.php | 60 + .../src/Ast/PhpDoc/ThrowsTagValueNode.php | 44 + .../PhpDoc/TypeAliasImportTagValueNode.php | 49 + .../src/Ast/PhpDoc/TypeAliasTagValueNode.php | 43 + .../Ast/PhpDoc/TypelessParamTagValueNode.php | 51 + .../src/Ast/PhpDoc/UsesTagValueNode.php | 44 + .../src/Ast/PhpDoc/VarTagValueNode.php | 48 + .../src/Ast/Type/ArrayShapeItemNode.php | 62 + .../src/Ast/Type/ArrayShapeNode.php | 88 + .../Ast/Type/ArrayShapeUnsealedTypeNode.php | 46 + .../src/Ast/Type/ArrayTypeNode.php | 46 + .../src/Ast/Type/CallableTypeNode.php | 63 + .../Ast/Type/CallableTypeParameterNode.php | 57 + .../Type/ConditionalTypeForParameterNode.php | 58 + .../src/Ast/Type/ConditionalTypeNode.php | 63 + .../src/Ast/Type/ConstTypeNode.php | 39 + .../src/Ast/Type/GenericTypeNode.php | 70 + .../src/Ast/Type/IdentifierTypeNode.php | 38 + .../src/Ast/Type/IntersectionTypeNode.php | 50 + .../src/Ast/Type/InvalidTypeNode.php | 53 + .../src/Ast/Type/NullableTypeNode.php | 44 + .../src/Ast/Type/ObjectShapeItemNode.php | 60 + .../src/Ast/Type/ObjectShapeNode.php | 45 + .../src/Ast/Type/OffsetAccessTypeNode.php | 48 + .../src/Ast/Type/ThisTypeNode.php | 31 + .../phpdoc-parser/src/Ast/Type/TypeNode.php | 10 + .../src/Ast/Type/UnionTypeNode.php | 50 + .../phpstan/phpdoc-parser/src/Lexer/Lexer.php | 208 + .../src/Parser/ConstExprParser.php | 289 + .../src/Parser/ParserException.php | 93 + .../phpdoc-parser/src/Parser/PhpDocParser.php | 1257 ++++ .../src/Parser/StringUnescaper.php | 100 + .../src/Parser/TokenIterator.php | 400 ++ .../phpdoc-parser/src/Parser/TypeParser.php | 1133 ++++ .../phpdoc-parser/src/ParserConfig.php | 24 + .../phpdoc-parser/src/Printer/DiffElem.php | 44 + .../phpdoc-parser/src/Printer/Differ.php | 196 + .../phpdoc-parser/src/Printer/Printer.php | 959 +++ admin/vendor/psr/clock/CHANGELOG.md | 11 + admin/vendor/psr/clock/LICENSE | 19 + admin/vendor/psr/clock/README.md | 61 + admin/vendor/psr/clock/composer.json | 21 + admin/vendor/psr/clock/src/ClockInterface.php | 13 + admin/vendor/psr/container/.gitignore | 3 + admin/vendor/psr/container/LICENSE | 21 + admin/vendor/psr/container/README.md | 13 + admin/vendor/psr/container/composer.json | 27 + .../src/ContainerExceptionInterface.php | 12 + .../psr/container/src/ContainerInterface.php | 36 + .../src/NotFoundExceptionInterface.php | 10 + .../vendor/psr/event-dispatcher/.editorconfig | 15 + admin/vendor/psr/event-dispatcher/.gitignore | 2 + admin/vendor/psr/event-dispatcher/LICENSE | 21 + admin/vendor/psr/event-dispatcher/README.md | 6 + .../vendor/psr/event-dispatcher/composer.json | 26 + .../src/EventDispatcherInterface.php | 21 + .../src/ListenerProviderInterface.php | 19 + .../src/StoppableEventInterface.php | 26 + admin/vendor/psr/http-client/CHANGELOG.md | 31 + admin/vendor/psr/http-client/LICENSE | 19 + admin/vendor/psr/http-client/README.md | 12 + admin/vendor/psr/http-client/composer.json | 30 + .../src/ClientExceptionInterface.php | 10 + .../psr/http-client/src/ClientInterface.php | 20 + .../src/NetworkExceptionInterface.php | 24 + .../src/RequestExceptionInterface.php | 24 + admin/vendor/psr/http-factory/LICENSE | 21 + admin/vendor/psr/http-factory/README.md | 12 + admin/vendor/psr/http-factory/composer.json | 38 + .../src/RequestFactoryInterface.php | 18 + .../src/ResponseFactoryInterface.php | 18 + .../src/ServerRequestFactoryInterface.php | 24 + .../src/StreamFactoryInterface.php | 45 + .../src/UploadedFileFactoryInterface.php | 34 + .../http-factory/src/UriFactoryInterface.php | 17 + admin/vendor/psr/http-message/CHANGELOG.md | 36 + admin/vendor/psr/http-message/LICENSE | 19 + admin/vendor/psr/http-message/README.md | 16 + admin/vendor/psr/http-message/composer.json | 26 + .../psr/http-message/docs/PSR7-Interfaces.md | 130 + .../psr/http-message/docs/PSR7-Usage.md | 159 + .../psr/http-message/src/MessageInterface.php | 187 + .../psr/http-message/src/RequestInterface.php | 130 + .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 + .../psr/http-message/src/StreamInterface.php | 158 + .../src/UploadedFileInterface.php | 123 + .../psr/http-message/src/UriInterface.php | 324 + admin/vendor/psr/http-server-handler/LICENSE | 21 + .../vendor/psr/http-server-handler/README.md | 12 + .../psr/http-server-handler/composer.json | 36 + .../src/RequestHandlerInterface.php | 22 + .../vendor/psr/http-server-middleware/LICENSE | 21 + .../psr/http-server-middleware/README.md | 12 + .../psr/http-server-middleware/composer.json | 36 + .../src/MiddlewareInterface.php | 25 + admin/vendor/psr/log/LICENSE | 19 + admin/vendor/psr/log/README.md | 58 + admin/vendor/psr/log/composer.json | 26 + admin/vendor/psr/log/src/AbstractLogger.php | 15 + .../psr/log/src/InvalidArgumentException.php | 7 + admin/vendor/psr/log/src/LogLevel.php | 18 + .../psr/log/src/LoggerAwareInterface.php | 14 + admin/vendor/psr/log/src/LoggerAwareTrait.php | 22 + admin/vendor/psr/log/src/LoggerInterface.php | 98 + admin/vendor/psr/log/src/LoggerTrait.php | 98 + admin/vendor/psr/log/src/NullLogger.php | 26 + .../deprecation-contracts/CHANGELOG.md | 5 + .../symfony/deprecation-contracts/LICENSE | 19 + .../symfony/deprecation-contracts/README.md | 26 + .../deprecation-contracts/composer.json | 35 + .../deprecation-contracts/function.php | 27 + .../symfony/options-resolver/CHANGELOG.md | 103 + .../Debug/OptionsResolverIntrospector.php | 114 + .../Exception/AccessException.php | 22 + .../Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../Exception/InvalidOptionsException.php | 23 + .../Exception/MissingOptionsException.php | 23 + .../Exception/NoConfigurationException.php | 26 + .../Exception/NoSuchOptionException.php | 26 + .../Exception/OptionDefinitionException.php | 21 + .../Exception/UndefinedOptionsException.php | 24 + admin/vendor/symfony/options-resolver/LICENSE | 19 + .../options-resolver/OptionConfigurator.php | 160 + .../symfony/options-resolver/Options.php | 22 + .../options-resolver/OptionsResolver.php | 1399 ++++ .../vendor/symfony/options-resolver/README.md | 15 + .../symfony/options-resolver/composer.json | 29 + admin/vendor/symfony/polyfill-uuid/LICENSE | 19 + admin/vendor/symfony/polyfill-uuid/README.md | 12 + admin/vendor/symfony/polyfill-uuid/Uuid.php | 531 ++ .../symfony/polyfill-uuid/bootstrap.php | 97 + .../symfony/polyfill-uuid/bootstrap80.php | 89 + .../symfony/polyfill-uuid/composer.json | 38 + admin/vendor/symfony/uid/AbstractUid.php | 212 + admin/vendor/symfony/uid/BinaryUtil.php | 191 + admin/vendor/symfony/uid/CHANGELOG.md | 61 + .../uid/Command/GenerateUlidCommand.php | 117 + .../uid/Command/GenerateUuidCommand.php | 207 + .../uid/Command/InspectUlidCommand.php | 67 + .../uid/Command/InspectUuidCommand.php | 84 + .../Exception/InvalidArgumentException.php | 16 + .../symfony/uid/Exception/LogicException.php | 16 + .../symfony/uid/Factory/MockUuidFactory.php | 140 + .../uid/Factory/NameBasedUuidFactory.php | 41 + .../uid/Factory/RandomBasedUuidFactory.php | 32 + .../uid/Factory/TimeBasedUuidFactory.php | 38 + .../symfony/uid/Factory/UlidFactory.php | 22 + .../symfony/uid/Factory/UuidFactory.php | 99 + .../vendor/symfony/uid/HashableInterface.php | 42 + admin/vendor/symfony/uid/LICENSE | 19 + admin/vendor/symfony/uid/MaxUlid.php | 20 + admin/vendor/symfony/uid/MaxUuid.php | 22 + admin/vendor/symfony/uid/NilUlid.php | 20 + admin/vendor/symfony/uid/NilUuid.php | 25 + admin/vendor/symfony/uid/README.md | 16 + .../symfony/uid/TimeBasedUidInterface.php | 22 + admin/vendor/symfony/uid/Ulid.php | 210 + admin/vendor/symfony/uid/Uuid.php | 230 + admin/vendor/symfony/uid/UuidV1.php | 85 + admin/vendor/symfony/uid/UuidV3.php | 29 + admin/vendor/symfony/uid/UuidV4.php | 43 + admin/vendor/symfony/uid/UuidV5.php | 29 + admin/vendor/symfony/uid/UuidV6.php | 95 + admin/vendor/symfony/uid/UuidV7.php | 149 + admin/vendor/symfony/uid/UuidV8.php | 27 + admin/vendor/symfony/uid/composer.json | 36 + admin/vendor/webmozart/assert/CHANGELOG.md | 315 + admin/vendor/webmozart/assert/LICENSE | 20 + admin/vendor/webmozart/assert/README.md | 313 + admin/vendor/webmozart/assert/composer.json | 71 + admin/vendor/webmozart/assert/src/Assert.php | 2620 ++++++++ .../vendor/webmozart/assert/src/HasAssert.php | 179 + .../assert/src/InvalidArgumentException.php | 17 + admin/vendor/webmozart/assert/src/Mixin.php | 5827 +++++++++++++++++ .../webmozart/assert/src/PsalmPlugin.php | 50 + changelog.xml | 30 +- composer.json | 10 +- distribution.lock.json | 8 - docs/ARCHITECTURE.md | 2 +- docs/IMPLEMENTATION.md | 10 +- docs/MIGRATION.md | 4 +- docs/RELEASE.md | 131 +- joomengine_mcp.xml | 4 +- pkg_joomengine_mcp_update_server.xml | 3 - tests/actions.php | 4 +- tests/catalogue.php | 4 +- tests/execution.php | 4 +- tests/golden-image/run.sh | 10 +- tests/http.php | 2 +- tests/integration/lifecycle.php | 11 + tests/integration/package.php | 94 - tests/integration/run.sh | 14 +- tests/jcb-bootstrap.php | 4 +- tests/jcb-files.php | 4 +- tests/jcb-output.php | 4 +- tests/jcb-permissions.php | 4 +- tests/jcb-plan-preview.php | 2 +- tests/jcb-results.php | 4 +- tests/jcb-workspace.php | 4 +- tests/jcb.php | 2 +- tests/job-process.php | 2 +- tests/jobs.php | 2 +- tests/ownership.php | 8 +- tests/package.php | 207 +- tests/planned.php | 4 +- tests/process.php | 2 +- tests/protocol.php | 4 +- tests/release.php | 175 +- tests/routes.php | 2 +- tests/schema-shapes.php | 4 +- tools/archive.php | 49 - tools/build-distribution.sh | 27 - tools/build.sh | 13 - tools/distribution.php | 148 - tools/generate-catalogue.php | 2 +- tools/package.php | 108 - tools/release.php | 246 + tools/update-feeds.php | 130 - 1183 files changed, 118162 insertions(+), 996 deletions(-) create mode 100644 admin/LICENSE create mode 100644 admin/autoload.php rename {data => admin/data}/catalogue-seed.json (100%) create mode 100644 admin/vendor/autoload.php create mode 100644 admin/vendor/clue/stream-filter/.github/FUNDING.yml create mode 100644 admin/vendor/clue/stream-filter/CHANGELOG.md create mode 100644 admin/vendor/clue/stream-filter/LICENSE create mode 100644 admin/vendor/clue/stream-filter/README.md create mode 100644 admin/vendor/clue/stream-filter/composer.json create mode 100644 admin/vendor/clue/stream-filter/src/CallbackFilter.php create mode 100644 admin/vendor/clue/stream-filter/src/functions.php create mode 100644 admin/vendor/clue/stream-filter/src/functions_include.php create mode 100644 admin/vendor/composer/ClassLoader.php create mode 100644 admin/vendor/composer/InstalledVersions.php create mode 100644 admin/vendor/composer/LICENSE create mode 100644 admin/vendor/composer/autoload_classmap.php create mode 100644 admin/vendor/composer/autoload_files.php create mode 100644 admin/vendor/composer/autoload_namespaces.php create mode 100644 admin/vendor/composer/autoload_psr4.php create mode 100644 admin/vendor/composer/autoload_real.php create mode 100644 admin/vendor/composer/autoload_static.php create mode 100644 admin/vendor/composer/installed.json create mode 100644 admin/vendor/composer/installed.php create mode 100644 admin/vendor/composer/platform_check.php create mode 100644 admin/vendor/doctrine/deprecations/LICENSE create mode 100644 admin/vendor/doctrine/deprecations/README.md create mode 100644 admin/vendor/doctrine/deprecations/composer.json create mode 100644 admin/vendor/doctrine/deprecations/src/Deprecation.php create mode 100644 admin/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php create mode 100644 admin/vendor/mcp/sdk/.gitattributes create mode 100644 admin/vendor/mcp/sdk/.gitignore create mode 100644 admin/vendor/mcp/sdk/CHANGELOG.md create mode 100644 admin/vendor/mcp/sdk/CONTRIBUTING.md create mode 100644 admin/vendor/mcp/sdk/LICENSE create mode 100644 admin/vendor/mcp/sdk/README.md create mode 100644 admin/vendor/mcp/sdk/ROADMAP.md create mode 100644 admin/vendor/mcp/sdk/SECURITY.md create mode 100644 admin/vendor/mcp/sdk/adr/0001-oauth-authorization-server-out-of-scope.md create mode 100644 admin/vendor/mcp/sdk/adr/README.md create mode 100644 admin/vendor/mcp/sdk/composer.json create mode 100644 admin/vendor/mcp/sdk/phpdoc.dist.xml create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/CompletionProvider.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/McpPrompt.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/McpResource.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/McpResourceTemplate.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/McpTool.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Attribute/Schema.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Completion/EnumCompletionProvider.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Completion/ListCompletionProvider.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Completion/ProviderInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/CachedDiscoverer.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/Discoverer.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/DiscovererInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/DiscoveryState.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/DocBlockParser.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/ElementMetadataResolver.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/HandlerResolver.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGenerator.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGeneratorInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaValidator.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Formatter/PromptResultFormatter.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Formatter/ResourceResultFormatter.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Formatter/ToolResultFormatter.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Logger/ClientLogger.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Container.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ElementReference.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/InjectableParameters.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ChainLoader.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Loader/DiscoveryLoader.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ExplicitElementLoader.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Loader/LoaderInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ReflectedElementLoader.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/PromptReference.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ResourceReference.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ResourceTemplateReference.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Registry/ToolReference.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/RegistryInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Capability/Tool/NameValidator.php create mode 100644 admin/vendor/mcp/sdk/src/Client.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Builder.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Configuration.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Notification/LoggingNotificationHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Notification/NotificationHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Notification/ProgressNotificationHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/ElicitationCallbackInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/ElicitationRequestHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/ListRootsRequestHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/RequestHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/RootsCallbackInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/SamplingCallbackInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Handler/Request/SamplingRequestHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Protocol.php create mode 100644 admin/vendor/mcp/sdk/src/Client/State/ClientState.php create mode 100644 admin/vendor/mcp/sdk/src/Client/State/ClientStateInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Stateless/HeaderFactory.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Stateless/InputRequestResolver.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Stateless/RequestEnvelope.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Stateless/ToolCatalog.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Transport/BaseTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Transport/HeaderAwareTransportInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Transport/HttpTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Transport/StdioTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Client/Transport/TransportInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Event/ErrorEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/NotificationEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/PromptListChangedEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/RequestEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/ResourceListChangedEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/ResourceTemplateListChangedEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/ResponseEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Event/ToolListChangedEvent.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/BadMethodCallException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ClientException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ClientRegistrationException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ConfigurationException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ConnectionException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ContainerException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ElicitationException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/Exception.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ExceptionInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/HandlerNotFoundException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/InvalidArgumentException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/InvalidCursorException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/InvalidInputMessageException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/LogicException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/MissingRequestMetaException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/MissingRequiredClientCapabilityException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/NotFoundExceptionInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/PromptGetException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/PromptNotFoundException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/RegistryException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/RequestException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/RequestStateException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ResourceNotFoundException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ResourceReadException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/RootsException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/RuntimeException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/SamplingException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ServiceNotFoundException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/TimeoutException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ToolCallException.php create mode 100644 admin/vendor/mcp/sdk/src/Exception/ToolNotFoundException.php create mode 100644 admin/vendor/mcp/sdk/src/JsonRpc/MessageFactory.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Annotations.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ClientCapabilities.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/AudioContent.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/BlobResourceContents.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/Content.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/EmbeddedResource.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/ImageContent.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/PromptMessage.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/ResourceContents.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/ResourceLink.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/SamplingMessage.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/TextContent.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/TextResourceContents.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/ToolResultContent.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Content/ToolUseContent.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/AbstractSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/BooleanSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/ElicitationSchema.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/EnumSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/MultiSelectEnumSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/NumberSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/StringSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledEnumSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledMultiSelectEnumSchemaDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/CacheScope.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/ElicitAction.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/ElicitationMode.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/IconTheme.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/LoggingLevel.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/ProtocolVersion.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/ResultType.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/Role.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/SamplingContext.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Enum/ToolChoiceMode.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/AbstractExtension.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/McpApps.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/ToolVisibility.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceContentMeta.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceCsp.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourcePermissions.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiToolMeta.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionIdentifier.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Icon.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Implementation.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/Error.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/HasMethodInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/MessageInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/Notification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/Request.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/Response.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/JsonRpc/ResultInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ModelHint.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ModelPreferences.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/CancelledNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/InitializedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/LoggingMessageNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/ProgressNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/PromptListChangedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/ResourceListChangedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/ResourceUpdatedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/RootsListChangedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Notification/ToolListChangedNotification.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Page.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Prompt.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/PromptArgument.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/PromptReference.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/CallToolRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/CompletionCompleteRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/CreateSamplingMessageRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/DiscoverRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ElicitRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/GetPromptRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/InitializeRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ListPromptsRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ListResourceTemplatesRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ListResourcesRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ListRootsRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ListToolsRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/PingRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ReadResourceRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ResourceSubscribeRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/ResourceUnsubscribeRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Request/SetLogLevelRequest.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ResourceDefinition.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ResourceReference.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ResourceTemplate.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/CallToolResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/CompletionCompleteResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/CreateSamplingMessageResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/DiscoverResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ElicitResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/EmptyResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/GetPromptResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/InitializeResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/InputRequiredResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ListPromptsResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ListResourceTemplatesResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ListResourcesResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ListRootsResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ListToolsResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Result/ReadResourceResult.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Root.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ServerCapabilities.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Tool.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ToolAnnotations.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/ToolChoice.php create mode 100644 admin/vendor/mcp/sdk/src/Schema/Wire/McpHeader.php create mode 100644 admin/vendor/mcp/sdk/src/Server.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Builder.php create mode 100644 admin/vendor/mcp/sdk/src/Server/ClientGateway.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Configuration.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/ElementHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Notification/InitializedHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Notification/NotificationHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/PromptHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/CallToolHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/CompletionCompleteHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/GetPromptHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/InitializeHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ListPromptsHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourceTemplatesHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourcesHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ListToolsHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/PingHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ReadResourceHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/RequestHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceSubscribeHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceUnsubscribeHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/Request/SetLogLevelHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/ResourceHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/ResourceTemplateHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Handler/ToolHandlerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/InputRequiredShim.php create mode 100644 admin/vendor/mcp/sdk/src/Server/NativeClock.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Protocol.php create mode 100644 admin/vendor/mcp/sdk/src/Server/RequestContext.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Resource/SessionSubscriptionManager.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Resource/SubscriptionManagerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/FileSessionStore.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/InMemorySessionStore.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/Psr16SessionStore.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/Session.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/SessionInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/SessionManager.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/SessionManagerInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Session/SessionStoreInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/ElicitationReplay.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/InputContext.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/InputRequestCapabilities.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/NotificationFilter.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/RequestMeta.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/RequestStateCodec.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/StandardHeaderValidator.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/StatelessProtocol.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Stateless/StatelessResult.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Subscription/InMemoryNotificationBus.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Subscription/NotificationBusInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Subscription/Psr16NotificationBus.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Subscription/PublishingEventDispatcher.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Subscription/RegistryChangePublisher.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/BaseTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/CallbackStream.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/JsonRpcErrorResponse.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/AuthorizationMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ClientRegistrationMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/CorsMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/DnsRebindingProtectionMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthProxyMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthRequestMetaMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtocolVersionMiddleware.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/MiddlewareRequestHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationResult.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationTokenValidatorInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ClientRegistrarInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProvider.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProviderInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwtTokenValidator.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicy.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscovery.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryMetadataPolicyInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadata.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadataHandler.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicy.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Http/StatelessResponder.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/InMemoryTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/ManagesTransportCallbacks.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/ReadsBoundedBody.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/StatelessAwareTransportInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/StatelessHttpTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControl.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControlInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerState.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/StdioTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Transport/TransportInterface.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/CachePolicy.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/EraClassification.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/InboundClassifier.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/Rev2025Codec.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/Rev2026Codec.php create mode 100644 admin/vendor/mcp/sdk/src/Server/Wire/WireCodecInterface.php create mode 100644 admin/vendor/nyholm/psr7-server/.github/FUNDING.yml create mode 100644 admin/vendor/nyholm/psr7-server/CHANGELOG.md create mode 100644 admin/vendor/nyholm/psr7-server/LICENSE create mode 100644 admin/vendor/nyholm/psr7-server/README.md create mode 100644 admin/vendor/nyholm/psr7-server/composer.json create mode 100644 admin/vendor/nyholm/psr7-server/src/ServerRequestCreator.php create mode 100644 admin/vendor/nyholm/psr7-server/src/ServerRequestCreatorInterface.php create mode 100644 admin/vendor/nyholm/psr7/CHANGELOG.md create mode 100644 admin/vendor/nyholm/psr7/LICENSE create mode 100644 admin/vendor/nyholm/psr7/README.md create mode 100644 admin/vendor/nyholm/psr7/composer.json create mode 100644 admin/vendor/nyholm/psr7/src/Factory/HttplugFactory.php create mode 100644 admin/vendor/nyholm/psr7/src/Factory/Psr17Factory.php create mode 100644 admin/vendor/nyholm/psr7/src/MessageTrait.php create mode 100644 admin/vendor/nyholm/psr7/src/Request.php create mode 100644 admin/vendor/nyholm/psr7/src/RequestTrait.php create mode 100644 admin/vendor/nyholm/psr7/src/Response.php create mode 100644 admin/vendor/nyholm/psr7/src/ServerRequest.php create mode 100644 admin/vendor/nyholm/psr7/src/Stream.php create mode 100644 admin/vendor/nyholm/psr7/src/StreamTrait.php create mode 100644 admin/vendor/nyholm/psr7/src/UploadedFile.php create mode 100644 admin/vendor/nyholm/psr7/src/Uri.php create mode 100644 admin/vendor/opis/json-schema/LICENSE create mode 100644 admin/vendor/opis/json-schema/NOTICE create mode 100644 admin/vendor/opis/json-schema/README.md create mode 100644 admin/vendor/opis/json-schema/SECURITY.md create mode 100644 admin/vendor/opis/json-schema/autoload.php create mode 100644 admin/vendor/opis/json-schema/composer.json create mode 100644 admin/vendor/opis/json-schema/src/CompliantValidator.php create mode 100644 admin/vendor/opis/json-schema/src/ContentEncoding.php create mode 100644 admin/vendor/opis/json-schema/src/ContentMediaType.php create mode 100644 admin/vendor/opis/json-schema/src/Errors/CustomError.php create mode 100644 admin/vendor/opis/json-schema/src/Errors/ErrorContainer.php create mode 100644 admin/vendor/opis/json-schema/src/Errors/ErrorFormatter.php create mode 100644 admin/vendor/opis/json-schema/src/Errors/ValidationError.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/InvalidKeywordException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/InvalidPragmaException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/ParseException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/SchemaException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/UnresolvedContentEncodingException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/UnresolvedException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/UnresolvedFilterException.php create mode 100644 admin/vendor/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php create mode 100644 admin/vendor/opis/json-schema/src/Filter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/CommonFilters.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/DataExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/DateTimeFilters.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/FilterExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/FormatExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/GlobalVarExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/SchemaExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Filters/SlotExistsFilter.php create mode 100644 admin/vendor/opis/json-schema/src/Format.php create mode 100644 admin/vendor/opis/json-schema/src/Formats/DateTimeFormats.php create mode 100644 admin/vendor/opis/json-schema/src/Formats/IriFormats.php create mode 100644 admin/vendor/opis/json-schema/src/Formats/MiscFormats.php create mode 100644 admin/vendor/opis/json-schema/src/Formats/UriFormats.php create mode 100644 admin/vendor/opis/json-schema/src/Helper.php create mode 100644 admin/vendor/opis/json-schema/src/Info/DataInfo.php create mode 100644 admin/vendor/opis/json-schema/src/Info/SchemaInfo.php create mode 100644 admin/vendor/opis/json-schema/src/JsonPointer.php create mode 100644 admin/vendor/opis/json-schema/src/Keyword.php create mode 100644 admin/vendor/opis/json-schema/src/KeywordValidator.php create mode 100644 admin/vendor/opis/json-schema/src/KeywordValidators/AbstractKeywordValidator.php create mode 100644 admin/vendor/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php create mode 100644 admin/vendor/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/AbstractRefKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/AllOfKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/AnyOfKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ConstDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ConstKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ContainsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ContentEncodingKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ContentSchemaKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/DefaultKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/DependenciesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/DependentRequiredKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/DependentSchemasKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/EnumDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/EnumKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ErrorTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/FiltersKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/FormatDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/FormatKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/IfThenElseKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/ItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/IterableDataValidationTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxLengthKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaximumDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MaximumKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinItemsDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinLengthDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinLengthKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinPropertiesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinimumDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MinimumKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/MultipleOfKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/NotKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/OfTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/OneOfKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PatternDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PatternKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PointerRefKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PropertiesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/PropertyNamesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/RecursiveRefKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/RequiredDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/RequiredKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/SlotsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/TemplateRefKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/TypeKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/URIRefKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Keywords/UniqueItemsKeyword.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/DataKeywordTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/DefaultVocabulary.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Draft.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/DraftOptionTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft06.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft07.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft201909.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft202012.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/KeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/KeywordParserTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/KeywordValidatorParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/KeywordValidators/PragmaKeywordValidatorParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/PragmaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/ResolverTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/SchemaParser.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/VariablesTrait.php create mode 100644 admin/vendor/opis/json-schema/src/Parsers/Vocabulary.php create mode 100644 admin/vendor/opis/json-schema/src/Pragma.php create mode 100644 admin/vendor/opis/json-schema/src/Pragmas/CastPragma.php create mode 100644 admin/vendor/opis/json-schema/src/Pragmas/GlobalsPragma.php create mode 100644 admin/vendor/opis/json-schema/src/Pragmas/MaxErrorsPragma.php create mode 100644 admin/vendor/opis/json-schema/src/Pragmas/SlotsPragma.php create mode 100644 admin/vendor/opis/json-schema/src/Resolvers/ContentEncodingResolver.php create mode 100644 admin/vendor/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php create mode 100644 admin/vendor/opis/json-schema/src/Resolvers/FilterResolver.php create mode 100644 admin/vendor/opis/json-schema/src/Resolvers/FormatResolver.php create mode 100644 admin/vendor/opis/json-schema/src/Resolvers/SchemaResolver.php create mode 100644 admin/vendor/opis/json-schema/src/Schema.php create mode 100644 admin/vendor/opis/json-schema/src/SchemaLoader.php create mode 100644 admin/vendor/opis/json-schema/src/SchemaValidator.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/AbstractSchema.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/BooleanSchema.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/EmptySchema.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/ExceptionSchema.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/LazySchema.php create mode 100644 admin/vendor/opis/json-schema/src/Schemas/ObjectSchema.php create mode 100644 admin/vendor/opis/json-schema/src/Uri.php create mode 100644 admin/vendor/opis/json-schema/src/ValidationContext.php create mode 100644 admin/vendor/opis/json-schema/src/ValidationResult.php create mode 100644 admin/vendor/opis/json-schema/src/Validator.php create mode 100644 admin/vendor/opis/json-schema/src/Variables.php create mode 100644 admin/vendor/opis/json-schema/src/Variables/RefVariablesContainer.php create mode 100644 admin/vendor/opis/json-schema/src/Variables/VariablesContainer.php create mode 100644 admin/vendor/opis/string/.editorconfig create mode 100644 admin/vendor/opis/string/CHANGELOG.md create mode 100644 admin/vendor/opis/string/LICENSE create mode 100644 admin/vendor/opis/string/NOTICE create mode 100644 admin/vendor/opis/string/README.md create mode 100644 admin/vendor/opis/string/composer.json create mode 100644 admin/vendor/opis/string/res/ascii.php create mode 100644 admin/vendor/opis/string/res/fold.php create mode 100644 admin/vendor/opis/string/res/lower.php create mode 100644 admin/vendor/opis/string/res/upper.php create mode 100644 admin/vendor/opis/string/src/Exception/InvalidCodePointException.php create mode 100644 admin/vendor/opis/string/src/Exception/InvalidStringException.php create mode 100644 admin/vendor/opis/string/src/Exception/UnicodeException.php create mode 100644 admin/vendor/opis/string/src/UnicodeString.php create mode 100644 admin/vendor/opis/uri/LICENSE create mode 100644 admin/vendor/opis/uri/README.md create mode 100644 admin/vendor/opis/uri/autoload.php create mode 100644 admin/vendor/opis/uri/composer.json create mode 100644 admin/vendor/opis/uri/src/Punycode.php create mode 100644 admin/vendor/opis/uri/src/PunycodeException.php create mode 100644 admin/vendor/opis/uri/src/Uri.php create mode 100644 admin/vendor/opis/uri/src/UriTemplate.php create mode 100644 admin/vendor/php-http/curl-client/.github/workflows/Build-Test.yml create mode 100644 admin/vendor/php-http/curl-client/.php_cs create mode 100644 admin/vendor/php-http/curl-client/CHANGELOG.md create mode 100644 admin/vendor/php-http/curl-client/LICENSE create mode 100644 admin/vendor/php-http/curl-client/README.md create mode 100644 admin/vendor/php-http/curl-client/composer.json create mode 100644 admin/vendor/php-http/curl-client/puli.json create mode 100644 admin/vendor/php-http/curl-client/src/Client.php create mode 100644 admin/vendor/php-http/curl-client/src/CurlPromise.php create mode 100644 admin/vendor/php-http/curl-client/src/MultiRunner.php create mode 100644 admin/vendor/php-http/curl-client/src/PromiseCore.php create mode 100644 admin/vendor/php-http/curl-client/src/ResponseBuilder.php create mode 100644 admin/vendor/php-http/discovery/.php-cs-fixer.php create mode 100644 admin/vendor/php-http/discovery/CHANGELOG.md create mode 100644 admin/vendor/php-http/discovery/LICENSE create mode 100644 admin/vendor/php-http/discovery/README.md create mode 100644 admin/vendor/php-http/discovery/composer.json create mode 100644 admin/vendor/php-http/discovery/src/ClassDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/Composer/Plugin.php create mode 100644 admin/vendor/php-http/discovery/src/Exception.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/NotFoundException.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php create mode 100644 admin/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php create mode 100644 admin/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/HttpClientDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/MessageFactoryDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/NotFoundException.php create mode 100644 admin/vendor/php-http/discovery/src/Psr17Factory.php create mode 100644 admin/vendor/php-http/discovery/src/Psr17FactoryDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/Psr18Client.php create mode 100644 admin/vendor/php-http/discovery/src/Psr18ClientDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php create mode 100644 admin/vendor/php-http/discovery/src/Strategy/CommonPsr17ClassesStrategy.php create mode 100644 admin/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php create mode 100644 admin/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php create mode 100644 admin/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php create mode 100644 admin/vendor/php-http/discovery/src/StreamFactoryDiscovery.php create mode 100644 admin/vendor/php-http/discovery/src/UriFactoryDiscovery.php create mode 100644 admin/vendor/php-http/httplug/.php-cs-fixer.dist.php create mode 100644 admin/vendor/php-http/httplug/CHANGELOG.md create mode 100644 admin/vendor/php-http/httplug/LICENSE create mode 100644 admin/vendor/php-http/httplug/README.md create mode 100644 admin/vendor/php-http/httplug/composer.json create mode 100644 admin/vendor/php-http/httplug/puli.json create mode 100644 admin/vendor/php-http/httplug/src/Exception.php create mode 100644 admin/vendor/php-http/httplug/src/Exception/HttpException.php create mode 100644 admin/vendor/php-http/httplug/src/Exception/NetworkException.php create mode 100644 admin/vendor/php-http/httplug/src/Exception/RequestAwareTrait.php create mode 100644 admin/vendor/php-http/httplug/src/Exception/RequestException.php create mode 100644 admin/vendor/php-http/httplug/src/Exception/TransferException.php create mode 100644 admin/vendor/php-http/httplug/src/HttpAsyncClient.php create mode 100644 admin/vendor/php-http/httplug/src/HttpClient.php create mode 100644 admin/vendor/php-http/httplug/src/Promise/HttpFulfilledPromise.php create mode 100644 admin/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php create mode 100644 admin/vendor/php-http/message/.php-cs-fixer.dist.php create mode 100644 admin/vendor/php-http/message/CHANGELOG.md create mode 100644 admin/vendor/php-http/message/LICENSE create mode 100644 admin/vendor/php-http/message/README.md create mode 100644 admin/vendor/php-http/message/apigen.neon create mode 100644 admin/vendor/php-http/message/composer.json create mode 100644 admin/vendor/php-http/message/puli.json create mode 100644 admin/vendor/php-http/message/src/Authentication.php create mode 100644 admin/vendor/php-http/message/src/Authentication/AutoBasicAuth.php create mode 100644 admin/vendor/php-http/message/src/Authentication/BasicAuth.php create mode 100644 admin/vendor/php-http/message/src/Authentication/Bearer.php create mode 100644 admin/vendor/php-http/message/src/Authentication/Chain.php create mode 100644 admin/vendor/php-http/message/src/Authentication/Header.php create mode 100644 admin/vendor/php-http/message/src/Authentication/Matching.php create mode 100644 admin/vendor/php-http/message/src/Authentication/QueryParam.php create mode 100644 admin/vendor/php-http/message/src/Authentication/RequestConditional.php create mode 100644 admin/vendor/php-http/message/src/Authentication/Wsse.php create mode 100644 admin/vendor/php-http/message/src/Builder/ResponseBuilder.php create mode 100644 admin/vendor/php-http/message/src/Cookie.php create mode 100644 admin/vendor/php-http/message/src/CookieJar.php create mode 100644 admin/vendor/php-http/message/src/CookieUtil.php create mode 100644 admin/vendor/php-http/message/src/Decorator/MessageDecorator.php create mode 100644 admin/vendor/php-http/message/src/Decorator/RequestDecorator.php create mode 100644 admin/vendor/php-http/message/src/Decorator/ResponseDecorator.php create mode 100644 admin/vendor/php-http/message/src/Decorator/StreamDecorator.php create mode 100644 admin/vendor/php-http/message/src/Encoding/ChunkStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/CompressStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/DechunkStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/DecompressStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/DeflateStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/Filter/Chunk.php create mode 100644 admin/vendor/php-http/message/src/Encoding/FilteredStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/GzipDecodeStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/GzipEncodeStream.php create mode 100644 admin/vendor/php-http/message/src/Encoding/InflateStream.php create mode 100644 admin/vendor/php-http/message/src/Exception.php create mode 100644 admin/vendor/php-http/message/src/Exception/UnexpectedValueException.php create mode 100644 admin/vendor/php-http/message/src/Formatter.php create mode 100644 admin/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php create mode 100644 admin/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php create mode 100644 admin/vendor/php-http/message/src/Formatter/SimpleFormatter.php create mode 100644 admin/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php create mode 100644 admin/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php create mode 100644 admin/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php create mode 100644 admin/vendor/php-http/message/src/RequestMatcher.php create mode 100644 admin/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php create mode 100644 admin/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php create mode 100644 admin/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php create mode 100644 admin/vendor/php-http/message/src/Stream/BufferedStream.php create mode 100644 admin/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php create mode 100644 admin/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php create mode 100644 admin/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php create mode 100644 admin/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php create mode 100644 admin/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php create mode 100644 admin/vendor/php-http/message/src/UriFactory/SlimUriFactory.php create mode 100644 admin/vendor/php-http/message/src/filters.php create mode 100644 admin/vendor/php-http/promise/CHANGELOG.md create mode 100644 admin/vendor/php-http/promise/LICENSE create mode 100644 admin/vendor/php-http/promise/README.md create mode 100644 admin/vendor/php-http/promise/composer.json create mode 100644 admin/vendor/php-http/promise/phpstan.neon.dist create mode 100644 admin/vendor/php-http/promise/src/FulfilledPromise.php create mode 100644 admin/vendor/php-http/promise/src/Promise.php create mode 100644 admin/vendor/php-http/promise/src/RejectedPromise.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/.github/dependabot.yml create mode 100644 admin/vendor/phpdocumentor/reflection-common/.github/workflows/push.yml create mode 100644 admin/vendor/phpdocumentor/reflection-common/LICENSE create mode 100644 admin/vendor/phpdocumentor/reflection-common/README.md create mode 100644 admin/vendor/phpdocumentor/reflection-common/composer.json create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/Element.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/File.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/Fqsen.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/Location.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/Project.php create mode 100644 admin/vendor/phpdocumentor/reflection-common/src/ProjectFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/LICENSE create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/README.md create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/composer.json create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ImplementsFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MixinFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ParamFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateCovariantFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ThrowsFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/Exception/CannotCreateTag.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/Exception/ParserException.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/Exception/ReflectionDocblockException.php create mode 100644 admin/vendor/phpdocumentor/reflection-docblock/src/Utils.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/.phpdoc/template/base.html.twig create mode 100644 admin/vendor/phpdocumentor/type-resolver/LICENSE create mode 100644 admin/vendor/phpdocumentor/type-resolver/README.md create mode 100644 admin/vendor/phpdocumentor/type-resolver/composer.json create mode 100644 admin/vendor/phpdocumentor/type-resolver/docs/generics.rst create mode 100644 admin/vendor/phpdocumentor/type-resolver/docs/getting-started.rst create mode 100644 admin/vendor/phpdocumentor/type-resolver/docs/index.rst create mode 100644 admin/vendor/phpdocumentor/type-resolver/docs/upgrade-v1-to-v2.rst create mode 100644 admin/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/FqsenResolver.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoType.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ArrayKey.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ArrayShape.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ArrayShapeItem.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/CallableArray.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/CallableString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ClassString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ClosedResource.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/Conditional.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ConditionalForParameter.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ConstExpression.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/EnumString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/False_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/FloatValue.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/Generic.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/HtmlEscapedString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/IntMask.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/IntMaskOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/IntegerRange.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/IntegerValue.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/InterfaceString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/KeyOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ListShape.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ListShapeItem.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/List_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/LiteralString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/LowercaseString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NegativeInteger.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NeverReturn.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NeverReturns.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NoReturn.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyArray.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyList.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyLowercaseString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonFalsyString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonNegativeInteger.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonPositiveInteger.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NonZeroInteger.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/NumericString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/Numeric_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ObjectShape.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ObjectShapeItem.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/OffsetAccess.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/OpenResource.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/PositiveInteger.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/PrivatePropertiesOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/PropertiesOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ProtectedPropertiesOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/PublicPropertiesOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/Scalar.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ShapeItem.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/StringValue.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/TraitString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/True_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/TruthyString.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/PseudoTypes/ValueOf.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Type.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/TypeResolver.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/AbstractList.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/AggregatedType.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Array_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Boolean.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/CallableParameter.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Callable_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Compound.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Context.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Expression.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Float_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Integer.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Intersection.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Never_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Null_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Nullable.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Object_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Parent_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Resource_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Self_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Static_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/String_.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/This.php create mode 100644 admin/vendor/phpdocumentor/type-resolver/src/Types/Void_.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/LICENSE create mode 100644 admin/vendor/phpstan/phpdoc-parser/README.md create mode 100644 admin/vendor/phpstan/phpdoc-parser/UPGRADING.md create mode 100644 admin/vendor/phpstan/phpdoc-parser/composer.json create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/AbstractNodeVisitor.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Attribute.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Comment.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprArrayItemNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprArrayNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprFalseNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprFloatNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprIntegerNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprNullNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprStringNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstExprTrueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/ConstFetchNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/ConstExpr/DoctrineConstExprStringNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Node.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/NodeAttributes.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/NodeTraverser.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/NodeVisitor.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/NodeVisitor/CloningVisitor.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/AssertTagMethodValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/AssertTagPropertyValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/AssertTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/DeprecatedTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArray.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArrayItem.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ExtendsTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/GenericTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ImplementsTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/InvalidTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/MethodTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/MethodTagValueParameterNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/MixinTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ParamOutTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ParamTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocChildNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocTagNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocTextNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PropertyTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PureUnlessParameterIsPassedTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/RequireExtendsTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/RequireImplementsTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ReturnTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/SealedTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/SelfOutTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/TemplateTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/ThrowsTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/TypeAliasTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/TypelessParamTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/UsesTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/VarTagValueNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ArrayShapeItemNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ArrayShapeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ArrayShapeUnsealedTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ArrayTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/CallableTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/CallableTypeParameterNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ConditionalTypeForParameterNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ConditionalTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ConstTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/GenericTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/IdentifierTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/IntersectionTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/InvalidTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/NullableTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ObjectShapeItemNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ObjectShapeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/OffsetAccessTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/ThisTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/TypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Ast/Type/UnionTypeNode.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Lexer/Lexer.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/ConstExprParser.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/ParserException.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/PhpDocParser.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/StringUnescaper.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/TokenIterator.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Parser/TypeParser.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/ParserConfig.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Printer/DiffElem.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Printer/Differ.php create mode 100644 admin/vendor/phpstan/phpdoc-parser/src/Printer/Printer.php create mode 100644 admin/vendor/psr/clock/CHANGELOG.md create mode 100644 admin/vendor/psr/clock/LICENSE create mode 100644 admin/vendor/psr/clock/README.md create mode 100644 admin/vendor/psr/clock/composer.json create mode 100644 admin/vendor/psr/clock/src/ClockInterface.php create mode 100644 admin/vendor/psr/container/.gitignore create mode 100644 admin/vendor/psr/container/LICENSE create mode 100644 admin/vendor/psr/container/README.md create mode 100644 admin/vendor/psr/container/composer.json create mode 100644 admin/vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 admin/vendor/psr/container/src/ContainerInterface.php create mode 100644 admin/vendor/psr/container/src/NotFoundExceptionInterface.php create mode 100644 admin/vendor/psr/event-dispatcher/.editorconfig create mode 100644 admin/vendor/psr/event-dispatcher/.gitignore create mode 100644 admin/vendor/psr/event-dispatcher/LICENSE create mode 100644 admin/vendor/psr/event-dispatcher/README.md create mode 100644 admin/vendor/psr/event-dispatcher/composer.json create mode 100644 admin/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php create mode 100644 admin/vendor/psr/event-dispatcher/src/ListenerProviderInterface.php create mode 100644 admin/vendor/psr/event-dispatcher/src/StoppableEventInterface.php create mode 100644 admin/vendor/psr/http-client/CHANGELOG.md create mode 100644 admin/vendor/psr/http-client/LICENSE create mode 100644 admin/vendor/psr/http-client/README.md create mode 100644 admin/vendor/psr/http-client/composer.json create mode 100644 admin/vendor/psr/http-client/src/ClientExceptionInterface.php create mode 100644 admin/vendor/psr/http-client/src/ClientInterface.php create mode 100644 admin/vendor/psr/http-client/src/NetworkExceptionInterface.php create mode 100644 admin/vendor/psr/http-client/src/RequestExceptionInterface.php create mode 100644 admin/vendor/psr/http-factory/LICENSE create mode 100644 admin/vendor/psr/http-factory/README.md create mode 100644 admin/vendor/psr/http-factory/composer.json create mode 100644 admin/vendor/psr/http-factory/src/RequestFactoryInterface.php create mode 100644 admin/vendor/psr/http-factory/src/ResponseFactoryInterface.php create mode 100644 admin/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php create mode 100644 admin/vendor/psr/http-factory/src/StreamFactoryInterface.php create mode 100644 admin/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php create mode 100644 admin/vendor/psr/http-factory/src/UriFactoryInterface.php create mode 100644 admin/vendor/psr/http-message/CHANGELOG.md create mode 100644 admin/vendor/psr/http-message/LICENSE create mode 100644 admin/vendor/psr/http-message/README.md create mode 100644 admin/vendor/psr/http-message/composer.json create mode 100644 admin/vendor/psr/http-message/docs/PSR7-Interfaces.md create mode 100644 admin/vendor/psr/http-message/docs/PSR7-Usage.md create mode 100644 admin/vendor/psr/http-message/src/MessageInterface.php create mode 100644 admin/vendor/psr/http-message/src/RequestInterface.php create mode 100644 admin/vendor/psr/http-message/src/ResponseInterface.php create mode 100644 admin/vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 admin/vendor/psr/http-message/src/StreamInterface.php create mode 100644 admin/vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 admin/vendor/psr/http-message/src/UriInterface.php create mode 100644 admin/vendor/psr/http-server-handler/LICENSE create mode 100644 admin/vendor/psr/http-server-handler/README.md create mode 100644 admin/vendor/psr/http-server-handler/composer.json create mode 100644 admin/vendor/psr/http-server-handler/src/RequestHandlerInterface.php create mode 100644 admin/vendor/psr/http-server-middleware/LICENSE create mode 100644 admin/vendor/psr/http-server-middleware/README.md create mode 100644 admin/vendor/psr/http-server-middleware/composer.json create mode 100644 admin/vendor/psr/http-server-middleware/src/MiddlewareInterface.php create mode 100644 admin/vendor/psr/log/LICENSE create mode 100644 admin/vendor/psr/log/README.md create mode 100644 admin/vendor/psr/log/composer.json create mode 100644 admin/vendor/psr/log/src/AbstractLogger.php create mode 100644 admin/vendor/psr/log/src/InvalidArgumentException.php create mode 100644 admin/vendor/psr/log/src/LogLevel.php create mode 100644 admin/vendor/psr/log/src/LoggerAwareInterface.php create mode 100644 admin/vendor/psr/log/src/LoggerAwareTrait.php create mode 100644 admin/vendor/psr/log/src/LoggerInterface.php create mode 100644 admin/vendor/psr/log/src/LoggerTrait.php create mode 100644 admin/vendor/psr/log/src/NullLogger.php create mode 100644 admin/vendor/symfony/deprecation-contracts/CHANGELOG.md create mode 100644 admin/vendor/symfony/deprecation-contracts/LICENSE create mode 100644 admin/vendor/symfony/deprecation-contracts/README.md create mode 100644 admin/vendor/symfony/deprecation-contracts/composer.json create mode 100644 admin/vendor/symfony/deprecation-contracts/function.php create mode 100644 admin/vendor/symfony/options-resolver/CHANGELOG.md create mode 100644 admin/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/AccessException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/ExceptionInterface.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/MissingOptionsException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/NoConfigurationException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php create mode 100644 admin/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php create mode 100644 admin/vendor/symfony/options-resolver/LICENSE create mode 100644 admin/vendor/symfony/options-resolver/OptionConfigurator.php create mode 100644 admin/vendor/symfony/options-resolver/Options.php create mode 100644 admin/vendor/symfony/options-resolver/OptionsResolver.php create mode 100644 admin/vendor/symfony/options-resolver/README.md create mode 100644 admin/vendor/symfony/options-resolver/composer.json create mode 100644 admin/vendor/symfony/polyfill-uuid/LICENSE create mode 100644 admin/vendor/symfony/polyfill-uuid/README.md create mode 100644 admin/vendor/symfony/polyfill-uuid/Uuid.php create mode 100644 admin/vendor/symfony/polyfill-uuid/bootstrap.php create mode 100644 admin/vendor/symfony/polyfill-uuid/bootstrap80.php create mode 100644 admin/vendor/symfony/polyfill-uuid/composer.json create mode 100644 admin/vendor/symfony/uid/AbstractUid.php create mode 100644 admin/vendor/symfony/uid/BinaryUtil.php create mode 100644 admin/vendor/symfony/uid/CHANGELOG.md create mode 100644 admin/vendor/symfony/uid/Command/GenerateUlidCommand.php create mode 100644 admin/vendor/symfony/uid/Command/GenerateUuidCommand.php create mode 100644 admin/vendor/symfony/uid/Command/InspectUlidCommand.php create mode 100644 admin/vendor/symfony/uid/Command/InspectUuidCommand.php create mode 100644 admin/vendor/symfony/uid/Exception/InvalidArgumentException.php create mode 100644 admin/vendor/symfony/uid/Exception/LogicException.php create mode 100644 admin/vendor/symfony/uid/Factory/MockUuidFactory.php create mode 100644 admin/vendor/symfony/uid/Factory/NameBasedUuidFactory.php create mode 100644 admin/vendor/symfony/uid/Factory/RandomBasedUuidFactory.php create mode 100644 admin/vendor/symfony/uid/Factory/TimeBasedUuidFactory.php create mode 100644 admin/vendor/symfony/uid/Factory/UlidFactory.php create mode 100644 admin/vendor/symfony/uid/Factory/UuidFactory.php create mode 100644 admin/vendor/symfony/uid/HashableInterface.php create mode 100644 admin/vendor/symfony/uid/LICENSE create mode 100644 admin/vendor/symfony/uid/MaxUlid.php create mode 100644 admin/vendor/symfony/uid/MaxUuid.php create mode 100644 admin/vendor/symfony/uid/NilUlid.php create mode 100644 admin/vendor/symfony/uid/NilUuid.php create mode 100644 admin/vendor/symfony/uid/README.md create mode 100644 admin/vendor/symfony/uid/TimeBasedUidInterface.php create mode 100644 admin/vendor/symfony/uid/Ulid.php create mode 100644 admin/vendor/symfony/uid/Uuid.php create mode 100644 admin/vendor/symfony/uid/UuidV1.php create mode 100644 admin/vendor/symfony/uid/UuidV3.php create mode 100644 admin/vendor/symfony/uid/UuidV4.php create mode 100644 admin/vendor/symfony/uid/UuidV5.php create mode 100644 admin/vendor/symfony/uid/UuidV6.php create mode 100644 admin/vendor/symfony/uid/UuidV7.php create mode 100644 admin/vendor/symfony/uid/UuidV8.php create mode 100644 admin/vendor/symfony/uid/composer.json create mode 100644 admin/vendor/webmozart/assert/CHANGELOG.md create mode 100644 admin/vendor/webmozart/assert/LICENSE create mode 100644 admin/vendor/webmozart/assert/README.md create mode 100644 admin/vendor/webmozart/assert/composer.json create mode 100644 admin/vendor/webmozart/assert/src/Assert.php create mode 100644 admin/vendor/webmozart/assert/src/HasAssert.php create mode 100644 admin/vendor/webmozart/assert/src/InvalidArgumentException.php create mode 100644 admin/vendor/webmozart/assert/src/Mixin.php create mode 100644 admin/vendor/webmozart/assert/src/PsalmPlugin.php delete mode 100644 distribution.lock.json delete mode 100644 pkg_joomengine_mcp_update_server.xml delete mode 100644 tests/integration/package.php delete mode 100644 tools/archive.php delete mode 100755 tools/build-distribution.sh delete mode 100755 tools/build.sh delete mode 100644 tools/distribution.php delete mode 100644 tools/package.php create mode 100644 tools/release.php delete mode 100644 tools/update-feeds.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 324f9e1..c4e9fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,20 +32,25 @@ jobs: coverage: none - name: Validate Composer metadata run: composer validate --no-check-publish - - name: Install PHP dependencies - run: composer install --no-interaction --prefer-dist --no-progress + - name: Verify the committed source ZIP and relocated runtime + run: | + mkdir -p build + git archive --format=zip --prefix=component-source/ --output=build/component-source.zip HEAD + php tests/package.php build/component-source.zip + - name: Verify locked dependencies are already installed + run: composer install --no-dev --no-interaction --no-scripts --dry-run - name: PHP syntax run: find . -type d \( -name vendor -o -name .git -o -name .reference -o -name build \) -prune -o -type f -name '*.php' -print0 | xargs -0 -r -n1 php -l - name: Verify source-pinned seed reproducibility run: | set -euo pipefail php tools/generate-catalogue.php - git diff --exit-code -- admin/sql data/catalogue-seed.json docs/migration/parity.json + git diff --exit-code -- admin/sql admin/data/catalogue-seed.json docs/migration/parity.json - name: Behavioural and source-parity contracts run: | set -euo pipefail mkdir -p build/evidence - for suite in run catalogue schema-shapes execution actions planned jobs process job-process jcb jcb-plan-preview protocol http ownership native/run; do + for suite in run catalogue schema-shapes execution actions planned jobs process job-process jcb jcb-plan-preview protocol http ownership native/run release; do php "tests/$suite.php" | tee "build/evidence/${suite//\//-}.log" done - name: Preserve the tested PHP runtime for reproducible investigation diff --git a/.github/workflows/generate-catalogue.yml b/.github/workflows/generate-catalogue.yml index 655e599..6774339 100644 --- a/.github/workflows/generate-catalogue.yml +++ b/.github/workflows/generate-catalogue.yml @@ -41,7 +41,7 @@ jobs: - name: Commit only generated catalogue files to the existing PR branch run: | set -euo pipefail - git add -- admin/sql data/catalogue-seed.json docs/migration/parity.json + git add -- admin/sql admin/data/catalogue-seed.json docs/migration/parity.json if git diff --cached --quiet; then exit 0 fi diff --git a/.github/workflows/golden-image.yml b/.github/workflows/golden-image.yml index 907e993..62fa041 100644 --- a/.github/workflows/golden-image.yml +++ b/.github/workflows/golden-image.yml @@ -27,8 +27,8 @@ jobs: extensions: curl, dom, fileinfo, intl, json, mbstring, sodium, xml, zip tools: composer:v2 coverage: none - - name: Build the actual installable component - run: bash tools/build.sh && php tests/package.php + - name: Verify the committed installable component + run: php tests/package.php - name: Check out the JCB implementation under test uses: actions/checkout@v7 with: diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ec4f286..2142fb3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -70,16 +70,14 @@ jobs: - name: Install the client dependencies for HTTPS and remote stdio acceptance working-directory: build/client-source run: composer install --no-interaction --prefer-dist --no-progress - - name: Build a complete distributable with its PHP dependencies + - name: Record the reviewed companion sources run: | - bash tools/build.sh - bash tools/build-distribution.sh "$GITHUB_WORKSPACE/build/plugin-source" mkdir -p build/evidence git -C build/plugin-source rev-parse HEAD > build/evidence/console-plugin-source.txt git -C build/client-source rev-parse HEAD > build/evidence/client-source.txt - - name: Validate packaged files and native manifests + - name: Validate committed runtime files and native manifests run: | - php tests/package.php --distribution + php tests/package.php php tests/release.php - name: Install, execute HTTP, console and HTTPS client operations, then upgrade env: @@ -93,15 +91,14 @@ jobs: MCP_PLUGIN_SOURCE: ${{ github.workspace }}/build/plugin-source MCP_CLIENT_SOURCE: ${{ github.workspace }}/build/client-source run: bash tests/integration/run.sh - - name: Preserve redacted evidence and the development package + - name: Preserve redacted evidence and the tested source ZIPs if: always() uses: actions/upload-artifact@v7 with: name: installed-${{ matrix.database }}-php-${{ matrix.php }} path: | build/evidence/ - build/*joomengine_mcp-*.zip - build/*joomengine_mcp-*.zip.sha256 - build/distribution.json + build/component-source.zip + build/console-plugin-source.zip if-no-files-found: error retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35353a1..ca99973 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,104 +1,267 @@ -name: Publish component and Joomla package +name: Release component with OctoShoom and OctoJPack on: workflow_dispatch: + inputs: + version: + description: 'Stable version to release (for example 1.2.3 or v1.2.3); repeat to resume a failed release' + required: true + type: string permissions: - contents: read + contents: write concurrency: - group: component-package-release + group: joomla-extension-release cancel-in-progress: false jobs: - publish: - if: github.ref == 'refs/heads/main' + release: runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: write - actions: read + timeout-minutes: 30 + env: + RELEASE_BRANCH: ${{ vars.RELEASE_BRANCH || github.event.repository.default_branch }} + INPUT_VERSION: ${{ inputs.version }} + OCTOSHOOM_REPOSITORY: ${{ vars.OCTOSHOOM_REPOSITORY }} + OCTOSHOOM_REF: ${{ vars.OCTOSHOOM_REF }} + RELEASE_SSH_KNOWN_HOSTS: ${{ vars.RELEASE_SSH_KNOWN_HOSTS }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GIT_AUTHOR_NAME: github-actions[bot] + GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + OCTOJPACK_REPOSITORY: ${{ vars.OCTOJPACK_REPOSITORY }} + OCTOJPACK_REF: ${{ vars.OCTOJPACK_REF }} + PACKAGE_REPOSITORY: ${{ vars.PACKAGE_REPOSITORY }} + PACKAGE_BRANCH: ${{ vars.PACKAGE_BRANCH }} + PACKAGE_UPDATE_SERVER: ${{ vars.PACKAGE_UPDATE_SERVER }} + PACKAGE_CHANGELOG_SERVER: ${{ vars.PACKAGE_CHANGELOG_SERVER }} + CONSOLE_REPOSITORY: ${{ vars.CONSOLE_REPOSITORY }} + CONSOLE_TAG: ${{ vars.CONSOLE_TAG }} steps: - - uses: actions/checkout@v7 + - name: Validate release configuration before changing the repository + id: configuration + env: + RELEASE_SSH_KEY: ${{ secrets.RELEASE_SSH_KEY }} + shell: bash + run: | + set -euo pipefail + fail() { echo "::error::$1"; exit 1; } + for variable in RELEASE_BRANCH OCTOSHOOM_REPOSITORY OCTOSHOOM_REF RELEASE_SSH_KNOWN_HOSTS GH_TOKEN RELEASE_SSH_KEY; do + [[ -n "${!variable}" ]] || fail "Configure the required variable or secret: $variable" + done + [[ "$GITHUB_REF" == "refs/heads/$RELEASE_BRANCH" ]] || fail "Run this workflow from $RELEASE_BRANCH." + git check-ref-format "refs/heads/$RELEASE_BRANCH" + [[ "$OCTOSHOOM_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || fail 'Invalid OCTOSHOOM_REPOSITORY.' + [[ "$OCTOSHOOM_REF" =~ ^[a-fA-F0-9]{40}$ ]] || fail 'Pin OCTOSHOOM_REF to a full reviewed commit SHA.' + version="${INPUT_VERSION#v}" + [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || fail 'Use a stable version such as 1.2.3.' + printf 'RELEASE_VERSION=%s\nRELEASE_TAG=v%s\n' "$version" "$version" >> "$GITHUB_ENV" + printf 'version=%s\ntag=v%s\n' "$version" "$version" >> "$GITHUB_OUTPUT" + mkdir -p "$RUNNER_TEMP/release-ssh" + chmod 700 "$RUNNER_TEMP/release-ssh" + printf '%s\n' "$RELEASE_SSH_KEY" > "$RUNNER_TEMP/release-ssh/key" + printf '%s\n' "$RELEASE_SSH_KNOWN_HOSTS" > "$RUNNER_TEMP/release-ssh/known_hosts" + chmod 600 "$RUNNER_TEMP/release-ssh/key" "$RUNNER_TEMP/release-ssh/known_hosts" + printf 'GIT_SSH_COMMAND=ssh -i %s/release-ssh/key -o IdentitiesOnly=yes -o UserKnownHostsFile=%s/release-ssh/known_hosts -o StrictHostKeyChecking=yes\n' "$RUNNER_TEMP" "$RUNNER_TEMP" >> "$GITHUB_ENV" + git config --global user.name "$GIT_AUTHOR_NAME" + git config --global user.email "$GIT_AUTHOR_EMAIL" + config="$(jq -cn --arg owner "${GITHUB_REPOSITORY%/*}" --arg repo "${GITHUB_REPOSITORY#*/}" --arg branch "$RELEASE_BRANCH" \ + '{update_servers:[{owner:$owner,repo:$repo,branch:$branch,path:"joomengine_mcp_update_server.xml"}]}')" + printf 'shoom=%s\n' "$config" >> "$GITHUB_OUTPUT" + for variable in OCTOJPACK_REPOSITORY OCTOJPACK_REF PACKAGE_REPOSITORY PACKAGE_BRANCH PACKAGE_UPDATE_SERVER PACKAGE_CHANGELOG_SERVER CONSOLE_REPOSITORY CONSOLE_TAG; do + [[ -n "${!variable}" ]] || fail "Configure the required variable: $variable" + done + for repository in "$OCTOJPACK_REPOSITORY" "$PACKAGE_REPOSITORY" "$CONSOLE_REPOSITORY"; do + [[ "$repository" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || fail 'Repository variables must use owner/name.' + done + [[ "$OCTOJPACK_REF" =~ ^[a-fA-F0-9]{40}$ ]] || fail 'Pin OCTOJPACK_REF to a reviewed commit with GitHub tag archive support.' + [[ "$CONSOLE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || fail 'CONSOLE_TAG must be an immutable released vX.Y.Z tag.' + for source in "$GITHUB_REPOSITORY" "$CONSOLE_REPOSITORY" "$OCTOSHOOM_REPOSITORY" "$OCTOJPACK_REPOSITORY"; do + [[ "${PACKAGE_REPOSITORY,,}" != "${source,,}" ]] || fail 'PACKAGE_REPOSITORY must be a separate distribution repository.' + done + git check-ref-format "refs/heads/$PACKAGE_BRANCH" + [[ "$PACKAGE_UPDATE_SERVER" =~ ^https://[^[:space:]]+$ && "$PACKAGE_CHANGELOG_SERVER" =~ ^https://[^[:space:]]+$ ]] || fail 'Package feed and changelog URLs must be HTTPS URLs.' + - name: Check out the current release branch + uses: actions/checkout@v7 with: + ref: ${{ env.RELEASE_BRANCH }} fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN }} + - name: Check out the reviewed OctoShoom action + uses: actions/checkout@v7 + with: + repository: ${{ vars.OCTOSHOOM_REPOSITORY }} + ref: ${{ vars.OCTOSHOOM_REF }} + path: .release-actions/octoshoom + token: ${{ secrets.RELEASE_TOKEN }} + persist-credentials: false - uses: shivammathur/setup-php@v2 with: - php-version: '8.4' - extensions: curl, dom, fileinfo, json, mbstring, simplexml, sodium, xml, zip - tools: composer:v2 + php-version: '8.3' + extensions: dom, simplexml, zip coverage: none - - name: Require successful installed verification on this exact main commit - env: - GH_TOKEN: ${{ github.token }} + - name: Check out the reviewed OctoJPack action + uses: actions/checkout@v7 + with: + repository: ${{ vars.OCTOJPACK_REPOSITORY }} + ref: ${{ vars.OCTOJPACK_REF }} + path: .release-actions/octojpack + token: ${{ secrets.RELEASE_TOKEN }} + persist-credentials: false + - name: Verify the released console plugin and separate package destination + shell: bash run: | set -euo pipefail - git fetch origin main --tags - [[ "$(git rev-parse origin/main)" == "$GITHUB_SHA" ]] - mkdir -p build - gh api --paginate "repos/$GITHUB_REPOSITORY/actions/runs?head_sha=$GITHUB_SHA&event=push&per_page=100" \ - --jq '.workflow_runs[] | {name, head_sha, status, conclusion, run_number}' | jq -s . > build/release-ci.json - for workflow in 'PHP contracts and migration evidence' 'Installed Joomla component' 'Installed JCB golden image'; do - jq -e --arg name "$workflow" --arg sha "$GITHUB_SHA" \ - '[.[] | select(.name == $name and .head_sha == $sha)] | sort_by(.run_number) | last | .status == "completed" and .conclusion == "success"' \ - build/release-ci.json >/dev/null || { echo "Required workflow has not passed: $workflow" >&2; exit 1; } - done - version="$(php -r 'echo (string) simplexml_load_file("joomengine_mcp.xml")->version;')" - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] - tag="v$version" - if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then - echo 'This immutable version exists; bump it in a reviewed change.' >&2 - exit 1 + # All prerequisites are checked before publishing the component tag. + gh api "repos/$CONSOLE_REPOSITORY/git/ref/tags/$CONSOLE_TAG" --silent + curl --fail --silent --show-error --location --retry 3 \ + "https://github.com/$CONSOLE_REPOSITORY/archive/refs/tags/$CONSOLE_TAG.zip" --output "$RUNNER_TEMP/console.zip" + console_update_server="$(php -r ' + $zip = new ZipArchive(); + if ($zip->open($argv[1]) !== true) { throw new RuntimeException("Invalid console ZIP."); } + $matches = []; + for ($i = 0; $i < $zip->numFiles; $i++) + { if (preg_match("~^[^/]+/joomengine_mcp.xml$~D", $zip->getNameIndex($i))) { $matches[] = $i; } } + if (count($matches) !== 1) { throw new RuntimeException("Missing console root manifest."); } + $manifest = simplexml_load_string($zip->getFromIndex($matches[0]), SimpleXMLElement::class, LIBXML_NONET); + if ((string) $manifest["type"] !== "plugin" || (string) $manifest["group"] !== "console" + || (string) $manifest->version !== substr($argv[3], 1) + || explode(".", (string) $manifest->version)[0] !== explode(".", $argv[4])[0]) + { throw new RuntimeException("Console tag must have a matching manifest and the same major version as the component."); } + $url = (string) $manifest->updateservers->server; + if (!str_starts_with($url, "https://raw.githubusercontent.com/" . $argv[2] . "/") + || !str_ends_with($url, "/joomengine_mcp_update_server.xml") || preg_match("/[\\x00-\\x20]/", $url)) + { throw new RuntimeException("Console manifest must declare its repository update server."); } + echo $url; + ' "$RUNNER_TEMP/console.zip" "$CONSOLE_REPOSITORY" "$CONSOLE_TAG" "$RELEASE_VERSION")" + curl --fail --silent --show-error --location --retry 3 "$console_update_server" --output "$RUNNER_TEMP/console-feed.xml" + php -r ' + $feed = simplexml_load_file($argv[1], SimpleXMLElement::class, LIBXML_NONET); + $entries = $feed->xpath("update[version=\"" . substr($argv[4], 1) . "\"]"); + $url = "https://github.com/" . $argv[3] . "/archive/refs/tags/" . $argv[4] . ".zip"; + if (count($entries) !== 1 || (string) $entries[0]->type !== "plugin" + || (string) $entries[0]->element !== "joomengine_mcp" || (string) $entries[0]->folder !== "console" + || (string) $entries[0]->downloads->downloadurl !== $url + || !hash_equals(hash_file("sha512", $argv[2]), strtolower((string) $entries[0]->sha512))) + { throw new RuntimeException("Release the console tag and finish its OctoShoom workflow first."); } + ' "$RUNNER_TEMP/console-feed.xml" "$RUNNER_TEMP/console.zip" "$CONSOLE_REPOSITORY" "$CONSOLE_TAG" + git clone --no-checkout --depth=1 --branch "$PACKAGE_BRANCH" \ + "git@github.com:$PACKAGE_REPOSITORY.git" "$RUNNER_TEMP/package-target" + - name: Verify metadata transitions + run: php tests/release.php + - name: Freeze the changelogs and create the immutable tag + shell: bash + run: | + set -euo pipefail + git fetch origin "$RELEASE_BRANCH" --tags + git checkout -B release-source "origin/$RELEASE_BRANCH" + if git show-ref --verify --quiet "refs/tags/$RELEASE_TAG"; then + git merge-base --is-ancestor "$RELEASE_TAG^{commit}" "origin/$RELEASE_BRANCH" + git worktree add --detach "$RUNNER_TEMP/released-source" "$RELEASE_TAG" + php -r 'require $argv[1]; mcpRelease(["verify-tag", $argv[3], $argv[4], $argv[5]], $argv[2]);' \ + tools/release.php "$RUNNER_TEMP/released-source" "$RELEASE_VERSION" "$GITHUB_REPOSITORY" "$RELEASE_BRANCH" + git worktree remove "$RUNNER_TEMP/released-source" + echo "Resuming $RELEASE_TAG; its tag and release metadata remain unchanged." + else + php tools/release.php prepare "$RELEASE_VERSION" "$GITHUB_REPOSITORY" "$RELEASE_BRANCH" + git add joomengine_mcp.xml CHANGELOG.md changelog.xml .octojpack plugins/webservices/joomengine_mcp/joomengine_mcp.xml + git commit -m "Release $RELEASE_TAG" + git tag -a "$RELEASE_TAG" -m "Release $RELEASE_TAG" + git push --atomic origin "HEAD:refs/heads/$RELEASE_BRANCH" "refs/tags/$RELEASE_TAG" fi - printf 'RELEASE_VERSION=%s\nRELEASE_TAG=%s\n' "$version" "$tag" >> "$GITHUB_ENV" - - name: Rebuild complete distribution from immutable sources + - name: Add the Joomla update entry after its tag exists + shell: bash run: | set -euo pipefail - bash tools/build.sh - bash tools/build-distribution.sh - php tests/package.php --distribution - php tests/release.php - jq -e '.sources.component.workingTreeModified == false' build/distribution.json >/dev/null - sha256sum build/*joomengine_mcp-*.zip > build/distribution-first.sha256 - bash tools/build.sh - bash tools/build-distribution.sh - sha256sum --check build/distribution-first.sha256 - - name: Publish immutable versioned assets - env: - GH_TOKEN: ${{ github.token }} + # Fetch current main again: a retry or another contributor may have advanced it. + git fetch origin "$RELEASE_BRANCH" + git checkout -B release-source "origin/$RELEASE_BRANCH" + php tools/release.php feed "$RELEASE_VERSION" "$GITHUB_REPOSITORY" "$RELEASE_BRANCH" + git add joomengine_mcp_update_server.xml + if ! git diff --cached --quiet; then + git commit -m "Add $RELEASE_TAG to the Joomla update server" + git push origin "HEAD:refs/heads/$RELEASE_BRANCH" + fi + - name: Generate and publish update hashes with OctoShoom + uses: ./.release-actions/octoshoom + with: + config-json: ${{ steps.configuration.outputs.shoom }} + git-url: github.com + git-user: github-actions[bot] + git-email: 41898282+github-actions[bot]@users.noreply.github.com + commit-message: 'Add Joomla release archive SHA-512 values' + - name: Verify the committed SHA-512 against the actual tag download + shell: bash run: | set -euo pipefail - git fetch origin main - [[ "$(git rev-parse origin/main)" == "$GITHUB_SHA" ]] - component="build/com_joomengine_mcp-$RELEASE_VERSION.zip" - package="build/pkg_joomengine_mcp-$RELEASE_VERSION.zip" - plugin_version="$(php -r 'echo json_decode(file_get_contents("distribution.lock.json"),true,64,JSON_THROW_ON_ERROR)["console"]["version"];')" - console="build/plg_console_joomengine_mcp-$plugin_version.zip" - gh release create "$RELEASE_TAG" --target "$GITHUB_SHA" \ - --title "JoomEngine MCP $RELEASE_VERSION" --notes-file CHANGELOG.md --draft - # OctoJpack release mode consumes the first ZIP asset: upload the component first. - gh release upload "$RELEASE_TAG" "$component" "$component.sha256" - gh release upload "$RELEASE_TAG" "$package" "$package.sha256" "$console" "$console.sha256" build/distribution.json - gh release edit "$RELEASE_TAG" --draft=false --latest - mkdir -p build/published - gh release download "$RELEASE_TAG" --dir build/published --pattern '*.zip' --pattern '*.sha256' - for archive in "$component" "$package" "$console"; do - cmp "$archive" "build/published/$(basename "$archive")" - cmp "$archive.sha256" "build/published/$(basename "$archive").sha256" - done - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" > build/published/release.json - php tools/update-feeds.php build/published/release.json build/published - gh release upload "$RELEASE_TAG" build/release-feeds/*.xml - - name: Preserve build and publication evidence - if: always() - uses: actions/upload-artifact@v7 + git fetch origin "$RELEASE_BRANCH" + git show "origin/$RELEASE_BRANCH:joomengine_mcp_update_server.xml" > joomengine_mcp_update_server.xml + curl --fail --silent --show-error --location --retry 3 \ + "https://github.com/$GITHUB_REPOSITORY/archive/refs/tags/$RELEASE_TAG.zip" \ + --output "$RUNNER_TEMP/released-extension.zip" + php tools/release.php verify-hash "$RELEASE_VERSION" "$GITHUB_REPOSITORY" "$RELEASE_BRANCH" "$RUNNER_TEMP/released-extension.zip" + - name: Render the external package configuration and preserve existing package tags + id: package + shell: bash + run: | + set -euo pipefail + git show "$RELEASE_TAG:.octojpack" > "$RUNNER_TEMP/released.octojpack" + package_name="$(jq -r .package.package_name "$RUNNER_TEMP/released.octojpack")" + target="$RUNNER_TEMP/package-target" + git -C "$target" fetch origin "$PACKAGE_BRANCH" + if [[ -n "$(git -C "$target" ls-remote origin "refs/tags/$RELEASE_TAG")" ]]; then + git -C "$target" fetch --depth=1 origin "refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG" + git -C "$target" show "$RELEASE_TAG:$package_name.xml" > "$RUNNER_TEMP/existing-package.xml" + php -r '$m = simplexml_load_file($argv[1]); if ((string) $m["type"] !== "package" || (string) $m->version !== $argv[2]) { throw new RuntimeException("Existing package tag has conflicting metadata."); }' \ + "$RUNNER_TEMP/existing-package.xml" "$RELEASE_VERSION" + echo 'publish=false' >> "$GITHUB_OUTPUT" + echo "Package $RELEASE_TAG already exists; preserving its tag and destination branch." + exit 0 + fi + if git -C "$target" cat-file -e "FETCH_HEAD:$package_name.xml" 2>/dev/null; then + git -C "$target" show "FETCH_HEAD:$package_name.xml" > "$RUNNER_TEMP/current-package.xml" + php -r '$m = simplexml_load_file($argv[1]); if (version_compare((string) $m->version, $argv[2], ">")) { throw new RuntimeException("Refusing to replace a newer package branch with an older release."); }' \ + "$RUNNER_TEMP/current-package.xml" "$RELEASE_VERSION" + fi + configuration="$(jq -c \ + --arg source_owner "${GITHUB_REPOSITORY%/*}" --arg source_repo "${GITHUB_REPOSITORY#*/}" \ + --arg component_tag "$RELEASE_TAG" --arg console_owner "${CONSOLE_REPOSITORY%/*}" \ + --arg console_repo "${CONSOLE_REPOSITORY#*/}" --arg console_tag "$CONSOLE_TAG" \ + --arg package_owner "${PACKAGE_REPOSITORY%/*}" --arg package_repo "${PACKAGE_REPOSITORY#*/}" \ + --arg package_branch "$PACKAGE_BRANCH" --arg update "$PACKAGE_UPDATE_SERVER" --arg changelog "$PACKAGE_CHANGELOG_SERVER" \ + '.repository = {owner:$package_owner,repo:$package_repo,branch:$package_branch} + | .package.update_servers = $update | .package.changelog_servers = $changelog + | .files[0].owner = $source_owner | .files[0].repo = $source_repo | .files[0].mode = $component_tag + | .files[1].owner = $console_owner | .files[1].repo = $console_repo | .files[1].mode = $console_tag' \ + "$RUNNER_TEMP/released.octojpack")" + [[ "$configuration" != *'[[['* ]] || { echo 'Unresolved OctoJPack configuration value.' >&2; exit 1; } + printf 'config=%s\npublish=true\n' "$configuration" >> "$GITHUB_OUTPUT" + - name: Build and publish the separate Joomla package with OctoJPack + if: steps.package.outputs.publish == 'true' + uses: ./.release-actions/octojpack with: - name: component-release-evidence - path: | - build/release-ci.json - build/distribution.json - build/distribution-first.sha256 - build/published/release.json - build/release-feeds/ - if-no-files-found: error - retention-days: 30 + config-json: ${{ steps.package.outputs.config }} + token: ${{ secrets.RELEASE_TOKEN }} + url: github.com + api: https://api.github.com + version: ${{ steps.configuration.outputs.tag }} + licence-dir: ${{ github.workspace }} + push: 'true' + upload-artifact: 'true' + - name: Confirm the package tag was published + shell: bash + run: | + set -euo pipefail + git ls-remote --exit-code "git@github.com:$PACKAGE_REPOSITORY.git" "refs/tags/$RELEASE_TAG" + printf 'Package: [%s](https://github.com/%s/tree/%s).\n' \ + "$RELEASE_TAG" "$PACKAGE_REPOSITORY" "$RELEASE_TAG" >> "$GITHUB_STEP_SUMMARY" + - name: Record the completed extension release + shell: bash + run: | + printf 'Released [%s](https://github.com/%s/tree/%s). The Joomla update server contains the verified OctoShoom SHA-512.\n' \ + "$RELEASE_TAG" "$GITHUB_REPOSITORY" "$RELEASE_TAG" >> "$GITHUB_STEP_SUMMARY" + - name: Remove temporary authentication material + if: always() + shell: bash + run: rm -rf -- "$RUNNER_TEMP/release-ssh" diff --git a/.octojpack b/.octojpack index e0cd251..2134f39 100644 --- a/.octojpack +++ b/.octojpack @@ -21,32 +21,43 @@ "description": "JoomEngine MCP component, webservices routing, and local console server.", "version_id": "com_joomengine_mcp", "version": "0.1.1", - "update_servers": "https://github.com/joomengine/mcp_component/releases/latest/download/pkg_joomengine_mcp_update_server.xml" + "update_servers": "[[[PACKAGE_UPDATE_SERVER]]]", + "changelog_servers": "[[[PACKAGE_CHANGELOG_SERVER]]]" }, "repository": { - "owner": "joomengine", - "repo": "mcp_component", - "branch": "distribution" + "owner": "[[[PACKAGE_OWNER]]]", + "repo": "[[[PACKAGE_REPOSITORY]]]", + "branch": "[[[PACKAGE_BRANCH]]]" }, "files": [ { - "owner": "joomengine", - "repo": "mcp_component", + "owner": "[[[SOURCE_OWNER]]]", + "repo": "[[[SOURCE_REPOSITORY]]]", "id": "com_joomengine_mcp", "type": "component", - "mode": "releases" + "mode": "[[[COMPONENT_TAG]]]" }, { - "owner": "joomengine", - "repo": "mcp_plugin", + "owner": "[[[CONSOLE_OWNER]]]", + "repo": "[[[CONSOLE_REPOSITORY]]]", "id": "joomengine_mcp", "type": "plugin", "group": "console", - "mode": "releases" + "mode": "[[[CONSOLE_TAG]]]" } ], "languages": [ - {"tag": "en-GB", "ini": "sys.ini", "key": "PKG_JOOMENGINE_MCP", "value": "JoomEngine MCP Package"}, - {"tag": "en-GB", "ini": "ini", "key": "PKG_JOOMENGINE_MCP", "value": "JoomEngine MCP Package"} + { + "tag": "en-GB", + "ini": "sys.ini", + "key": "PKG_JOOMENGINE_MCP", + "value": "JoomEngine MCP Package" + }, + { + "tag": "en-GB", + "ini": "ini", + "key": "PKG_JOOMENGINE_MCP", + "value": "JoomEngine MCP Package" + } ] } diff --git a/AGENTS.md b/AGENTS.md index da4c7ed..36f6e29 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Complete the PHP migration of `joomengine/joomla-mcp` into this component and `joomengine/mcp_plugin`, preserving all documented tools, resources, prompts, core API operations, CLI actions, approval/plan semantics, compatibility, verification, recovery and distribution behaviours unless explicitly superseded. **Also implement complete first-class Joomla Component Builder API and CLI coverage as specified in docs/integrations/JCB.md.** Joomla core alone does not satisfy this project. Catalogue presence is not executable parity. -Use existing `feature/jcb-mcp-runtime` / PR #1 in both server repositories. Commit and push cohesive increments. Do not create replacement branches, rewrite others' commits, change the TypeScript or JCB source repositories, merge PRs or publish releases without a separate instruction. Documentation precedes new runtime objectives. Maintain docs/IMPLEMENTATION.md with actual outcomes and exact remaining work. +The migration PR #1 has been merged. Branch new work from current `main`, or continue the relevant open PR branch. Commit and push cohesive increments. Do not rewrite others' commits, change the TypeScript or JCB source repositories, merge PRs or run a production release without instruction. Documentation precedes new runtime objectives. Maintain docs/IMPLEMENTATION.md with actual outcomes and exact remaining work. ## Repository ownership @@ -29,6 +29,17 @@ PHP style authority: https://github.com/extension-builder/joomla/blob/main/docs/ 9. JCB's 45-entity package map is neither a complete API route list nor proof of 225 registered commands. Pin and inspect actual API routing/plugin/controllers and installed CLI InputDefinitions. The API-generation source is not an installed endpoint inventory. Unresolved upstream surfaces remain required work, not invented executable rows. 10. Joomla ZIPs contain all server dependencies. Keep PHP-only production installation/runtime, complete manifests/SQL/update metadata and .octojpack. No false update URLs, unimplemented advertised features, silent test skips or production claims from mocks alone. +## Source installation and release contract + +- This is exclusively the component repository. A GitHub source ZIP of any reviewed branch or tag must install directly in Joomla, with every manifest file, production Composer dependency, licence, routing plugin and seed already tracked. No downstream Composer run, staging, compilation or packaging is allowed. +- Keep production dependencies in `admin/vendor` and the installation seed in `admin/data`. When changing dependencies, run Composer as a maintainer, commit the lock and complete resolved runtime, and verify its autoload paths after relocation to Joomla's administrator component directory. Never hand-edit third-party vendor source. +- Preserve SQL installation/schema updates and the JSON-driven customization-preserving seed updater. They are approved installation mechanisms; do not replace them merely to change the release process. +- OctoJPack alone combines extensions using `.octojpack` and writes to a separate configured package repository. Package manifests, package update feeds, assembly scripts and bundled console copies do not belong here. Improve shared Octo tools upstream instead of copying their implementation into this repository. +- The manual Release workflow takes the next version. It freezes metadata, creates the immutable tag, adds that tag archive to the native component feed, runs OctoShoom synchronously and verifies its committed checksum, then invokes OctoJPack. A failed hash stage must prevent packaging. Never move an existing tag. Keep destination repositories, tool refs and credentials in documented GitHub variables/secrets. +- Update **both** `CHANGELOG.md` and `changelog.xml` with every meaningful change. Put pending entries under the exact literal `[[[NEXT_VERSION]]]`; create a new pending section after the previous one is released. Do not invent a version or modify historical released entries. The workflow replaces this marker with its input version in both files. +- Joomla changelog identity is `com_joomengine_mcp` / `component`. Use native categories `security`, `fix`, `language`, `addition`, `change`, `remove`, and `note`, each containing `item` children. Use matching human headings in Markdown. Record compatibility warnings under Note, errors fixed under Fix, and security fixes under Security. Keep both changelogs consistent and the manifest's `changelogurl` valid. +- Workflow changes require positive/negative metadata and ordering checks; source installation checks must inspect the tracked source archive. Do not restore package builders to make a test pass. See `docs/RELEASE.md` for the configuration and retry contract. + ## Verification Run syntax/style/unit/schema/manifest/language/package tests, exact source-to-PHP parity and protocol conformance. Add disposable Joomla 6 core and JCB installation/update/uninstall, API token/view-level/asset tests, true CRUD read-back/cleanup, CLI package/compiler scenarios, job concurrency/cancellation/reconciliation and client interoperability. Verify both JCB-absent core functionality and JCB-present full coverage. Generic behavioural contracts are preferred to issue-specific fixtures. Record real failures and missing evidence; never turn missing infrastructure or upstream no-op behaviour into a pass. diff --git a/CHANGELOG.md b/CHANGELOG.md index c03f31c..3ebc355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,54 @@ # Changelog -## 0.1.1 — unreleased +## [[[NEXT_VERSION]]] -### Added +### Fix + +- Make the repository source ZIP directly installable, with committed production dependencies, relocation-safe autoloading, catalogue seed and administrator licence. +- Point component update discovery to the maintained native XML feed. + +### Addition + +- Add a manual version release workflow that freezes both changelogs, creates an immutable tag, updates the Joomla feed and waits for OctoShoom before invoking OctoJPack. +- Document GitHub variables/secrets, release retries and the source-installation contract for future agents. + +### Change + +- Delegate combined Joomla package assembly exclusively to OctoJPack and a separate package repository. +- Verify tracked source archives in component and console installation tests. + +### Remove + +- Remove repository-local ZIP/package builders, distribution locks and package update metadata. + +### Note + +- Release the console plugin first and select its immutable tag in the component release configuration. +- Configure the variables and secrets in docs/RELEASE.md before the first release. OctoJPack must include GitHub tag-archive support. + +## 0.1.1 — development baseline + +### Addition - Installed JCB API-route and command inventories, explicit administrator/CLI synchronization, and customization-preserving database definitions. - Confirmed package/compiler planning, frozen input and source/state fingerprints, and isolated native execution with retained diagnostics. - Durable principal-owned jobs, worker tickets and leases, progress, cancellation, queued redispatch, uncertain-outcome recovery and retained hash-verified compiler artifacts. - Versioned MySQL/PostgreSQL job and artifact schema updates. -- Combined Joomla package with immutable console-plugin source selection, deterministic ZIP metadata, checksums and build provenance. -- `.octojpack`, manual main-only release automation and component/package update feeds generated only after archive publication and download validation. -- Package lifecycle, release validation, job/process and JCB behavioural tests, alongside installed Joomla and JCB workflows described in `docs/IMPLEMENTATION.md`. +- Job/process and JCB behavioural tests, alongside installed Joomla and JCB workflows described in docs/IMPLEMENTATION.md. -### Changed +### Change - Background execution retains the original API user's identity and authority; a PHP worker does not grant local server-owner permissions. - Stale command definitions, implementation sources or approved JCB state require a new plan. -- Component update discovery uses verified GitHub release feed assets. -- Imported native production PHP follows the JCB code style, preserving source attribution, protocol literals and the original behavioural contracts. +- Imported native production PHP follows the JCB code style, preserving source attribution, protocol literals and original behavioural contracts. ## 0.1.0 — development baseline +### Addition + - Database catalogue, schema validation, reviewed API/native handlers, grants/plans/executions/audit, SDK protocol adapters and shared console runtime. -- Native administrator MVC/forms, assets, access rules, API routing plugin, portable SQL, dependency-complete component ZIP and installed core tests. +- Native administrator MVC/forms, assets, access rules, API routing plugin, portable SQL and installed core tests. - Source-pinned migration descriptors and preserved original PHP native handlers. -- External client and remote stdio ownership separated into `mcp_client`. +- External client and remote stdio ownership separated into mcp_client. -Release availability is established by GitHub releases, not this changelog. +Development baseline entries describe existing source, not previously published releases. Published immutable tags establish release availability. diff --git a/README.md b/README.md index 35aacd7..1cbc146 100644 --- a/README.md +++ b/README.md @@ -9,28 +9,21 @@ An installable PHP MCP server for Joomla 6.1–6.x, with database-defined tools, ## Repository boundary -This repository owns the installed server: catalogue, administrator MVC/forms, HTTP endpoint/routing glue, Joomla token/ACL integration, shared API/native execution, durable state, jobs and server distribution. Its composer.json installs **server dependencies**, under package identity `joomengine/mcp-component`. External MCP connection code, remote stdio bridging, client documentation and client releases belong exclusively to `mcp_client`; this server must not depend on that client package. +This repository owns the installable component: catalogue, administrator MVC/forms, HTTP endpoint/routing glue, Joomla token/ACL integration, shared API/native execution, durable state and jobs. Its locked production dependencies are committed under `admin/vendor`; Composer is a maintainer tool, never an installation step. External MCP connection code, remote stdio bridging, client documentation and client releases belong exclusively to `mcp_client`. The component's outbound Joomla API transport is implemented in `admin/src/Http`. See [client separation and handoff](docs/CLIENT-HANDOFF.md). -## Build and install +## Download and install -Use PHP 8.3 or later and Composer 2 with the extensions in `composer.json`, plus DOM, SimpleXML and ZIP for packaging: +Download this repository using **Code → Download ZIP**, or download a release tag's source ZIP, and upload it in Joomla's extension installer. The source tree already contains its production PHP dependencies, installation data and owned webservices routing plugin. No build, Composer run or repacking is required. The supported host is Joomla 6.1–6.x with PHP 8.3 or later and the extensions listed in `composer.json`. -```bash -bash tools/build.sh -bash tools/build-distribution.sh -php tests/package.php --distribution -php tests/release.php -``` - -Install `build/pkg_joomengine_mcp-0.1.1.zip` through Joomla's extension installer. It includes the component, its owned webservices routing plugin and the thin console plugin. The component ZIP also supports an HTTP-only installation. Both include the PHP server dependencies; Composer is unnecessary on the Joomla server. Configure the canonical site API URL and Joomla permissions under **Components → JoomEngine MCP → Options**. The authenticated MCP endpoint is `/api/index.php/v1/joomengine-mcp`, relative to the Joomla installation. +Configure the canonical site API URL and Joomla permissions under **Components → JoomEngine MCP → Options**. The authenticated MCP endpoint is `/api/index.php/v1/joomengine-mcp`, relative to the Joomla installation. For direct local console commands, install the separate [console plugin](https://github.com/joomengine/mcp_plugin), or use the combined Joomla package published by OctoJPack in the configured package repository. For native JCB background jobs, provide a PHP CLI executable compatible with the installed Joomla version, with `pcntl_fork`, `posix_setsid` and `proc_open` available. Select its absolute path in the component's **PHP CLI binary** setting when it differs from the automatically detected PHP executable. The web-server account must be able to launch it and write the private artifact directory outside the served Joomla tree. Worker prerequisites are checked before a write is claimed. The golden-image Dockerfile demonstrates the required CLI extension setup; ordinary Joomla API operations remain available without that job runtime. Remote AI applications can use the independent client's [Docker Compose launcher](https://github.com/joomengine/mcp_client/blob/feature/standalone-php-client/docs/DOCKER.md). Supply the HTTPS Joomla installation URL and native API token; the client discovers capabilities from this component. The console plugin is required for direct local Joomla MCP commands, while the remote client connects to the component's authenticated HTTP endpoint. -The combined package pins the console plugin by immutable Git commit in `distribution.lock.json`. [Release instructions](docs/RELEASE.md) describe reproducibility, update feeds, `.octojpack` and manual main-only publication. Development builds do not publish releases. +The combined package is built exclusively by [OctoJPack](https://github.com/octoleo/octojpack), using `.octojpack`, and published to a separate package repository. Run the manual **Release** workflow with the next version to freeze both changelogs, create the tag, update the Joomla feed, wait for OctoShoom to commit its checksums, and then invoke OctoJPack. [Release instructions](docs/RELEASE.md) list the repository variables and secrets to configure. ## Required capabilities @@ -54,10 +47,12 @@ Joomla core remains usable without JCB. Remote HTTP and stdio clients retain the ## Current status -Development remains on `feature/jcb-mcp-runtime` and existing PR #1. Native administration, complete component packaging, combined distribution, installed core tests, JCB synchronization and job/artifact runtime are implemented. [Implementation status](docs/IMPLEMENTATION.md) separates implemented source, isolated checks and current installed Joomla/JCB acceptance. Release availability is established by GitHub releases, not the development branch. +Native administration, installed core tests, JCB synchronization and job/artifact runtime are implemented. [Implementation status](docs/IMPLEMENTATION.md) separates historical installed Joomla/JCB evidence from verification of the current source and release changes. Published tags identify component releases; package publication belongs to the configured package repository. The original migration source is pinned at `joomengine/joomla-mcp@2cff50f4f6b440da3c684f9995a77efad32e1a36`. It remains unchanged. Joomla 6 native contracts are authoritative; repository/MVC/XML placement follows JCB's extension-root layout. This is hand-authored JCB-aligned source, not an already imported JCB blueprint. Imported native handlers retain the original behavioural contracts and source attribution while following the JCB PHP style. The original implementation's deliberately unavailable operations remain explicit diagnostics; they are documented in the [migration provenance](docs/migration/README.md). Read [architecture](docs/ARCHITECTURE.md), [database design](docs/DATABASE.md), [migration plan](docs/MIGRATION.md), [security](SECURITY.md), and [agent instructions](AGENTS.md) before changing the runtime. + +See [CHANGELOG.md](CHANGELOG.md) for human-readable changes and [changelog.xml](changelog.xml) for Joomla's categorized changelog. Changes awaiting release use `[[[NEXT_VERSION]]]` in both files; the release workflow assigns their version. diff --git a/SECURITY.md b/SECURITY.md index 1f19c9f..d9fcfb5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,4 +14,4 @@ Preserve grants, expiry/revocation, principal/action/input/revision binding, sta Validate present Origin headers, media/protocol types and bounded I/O. Same-site API forwarding uses the configured canonical origin, verified TLS and no redirects. Never log tokens, passwords, credential-bearing definition fields or raw sensitive exceptions/payloads. Serialize only necessary redacted audit metadata. -Static Joomla tokens are not OAuth discovery. Do not advertise an authorization flow that is not implemented. Release archives contain dependencies/checksums, and update feeds refer only to published immutable assets. Keep source licences/notices. Report issues privately to maintainers without posting secrets or working exploit payloads in public issues. +Static Joomla tokens are not OAuth discovery. Do not advertise an authorization flow that is not implemented. Source archives contain committed production dependencies, and update feeds refer only to existing immutable source tags with checksums committed by OctoShoom. Keep source licences/notices. Report issues privately to maintainers without posting secrets or working exploit payloads in public issues. diff --git a/admin/LICENSE b/admin/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/admin/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/admin/autoload.php b/admin/autoload.php new file mode 100644 index 0000000..0a60ad4 --- /dev/null +++ b/admin/autoload.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (C) 2026 Vast Development Method. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +/** + * Load the committed runtime before and after Joomla relocates the admin folder. + * + * Composer owns third-party dependencies; Joomla owns extension namespaces. The + * administrator mapping is also needed during installation and standalone tests, + * before Joomla has refreshed its extension namespace cache. + * + * @var \Composer\Autoload\ClassLoader $loader + */ +$loader = require __DIR__ . '/vendor/autoload.php'; +$loader->addPsr4('VDM\\Component\\JoomEngineMcp\\Administrator\\', __DIR__ . '/src'); + +return $loader; diff --git a/admin/cli/jcb.php b/admin/cli/jcb.php index 80d2dcb..85f6189 100644 --- a/admin/cli/jcb.php +++ b/admin/cli/jcb.php @@ -70,7 +70,7 @@ define('JPATH_BASE', $root); require JPATH_BASE . '/includes/defines.php'; require JPATH_BASE . '/includes/framework.php'; - require dirname(__DIR__) . '/vendor/autoload.php'; + require dirname(__DIR__) . '/autoload.php'; $container = Factory::getContainer(); $container->alias('session', 'session.cli')->alias(Session::class, 'session.cli') ->alias(\Joomla\Session\Session::class, 'session.cli')->alias(SessionInterface::class, 'session.cli'); diff --git a/data/catalogue-seed.json b/admin/data/catalogue-seed.json similarity index 100% rename from data/catalogue-seed.json rename to admin/data/catalogue-seed.json diff --git a/admin/services/provider.php b/admin/services/provider.php index 3940d57..036e008 100644 --- a/admin/services/provider.php +++ b/admin/services/provider.php @@ -28,11 +28,11 @@ \defined('_JEXEC') or die; -$autoload = dirname(__DIR__) . '/vendor/autoload.php'; +$autoload = dirname(__DIR__) . '/autoload.php'; if (!is_file($autoload)) { - throw new RuntimeException('The installed JoomEngine MCP runtime dependencies are missing. Install the built component package.'); + throw new RuntimeException('The installed JoomEngine MCP runtime dependencies are missing. Reinstall the complete component source ZIP.'); } require_once $autoload; diff --git a/admin/src/Installer/InstallerScript.php b/admin/src/Installer/InstallerScript.php index 03489af..8093415 100644 --- a/admin/src/Installer/InstallerScript.php +++ b/admin/src/Installer/InstallerScript.php @@ -72,11 +72,11 @@ public function preflight(string $type, InstallerAdapter $adapter): bool $source = $adapter->getParent()->getPath('source'); - foreach (['admin/vendor/autoload.php', 'admin/data/catalogue-seed.json', 'plugins/webservices/joomengine_mcp/joomengine_mcp.xml'] as $file) + foreach (['admin/autoload.php', 'admin/vendor/autoload.php', 'admin/data/catalogue-seed.json', 'plugins/webservices/joomengine_mcp/joomengine_mcp.xml'] as $file) { if (!is_file($source . '/' . $file)) { - throw new RuntimeException('Install the built component ZIP, not a GitHub source archive.'); + throw new RuntimeException('The component source ZIP is incomplete; required file is missing: ' . $file); } } @@ -124,7 +124,7 @@ public function postflight(string $type, InstallerAdapter $adapter): bool try { - require_once JPATH_ADMINISTRATOR . '/components/com_joomengine_mcp/vendor/autoload.php'; + require_once JPATH_ADMINISTRATOR . '/components/com_joomengine_mcp/autoload.php'; $store = new JoomlaStore($this->database); if ($type !== 'install') diff --git a/admin/vendor/autoload.php b/admin/vendor/autoload.php new file mode 100644 index 0000000..ab05df5 --- /dev/null +++ b/admin/vendor/autoload.php @@ -0,0 +1,25 @@ + $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource` function can be used to +append a filter callback to the given stream. + +Each stream can have a list of filters attached. +This function appends a filter to the end of this list. + +If the given filter can not be added, it throws an `Exception`. + +The `$stream` can be any valid stream resource, such as: + +```php +$stream = fopen('demo.txt', 'w+'); +``` + +The `$callback` should be a valid callable function which accepts +an individual chunk of data and should return the updated chunk: + +```php +$filter = Clue\StreamFilter\append($stream, function ($chunk) { + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); +``` + +As such, you can also use native PHP functions or any other `callable`: + +```php +Clue\StreamFilter\append($stream, 'strtoupper'); + +// will write "HELLO" to the underlying stream +fwrite($stream, 'hello'); +``` + +If the `$callback` accepts invocation without parameters, +then this signature will be invoked once ending (flushing) the filter: + +```php +Clue\StreamFilter\append($stream, function ($chunk = null) { + if ($chunk === null) { + // will be called once ending the filter + return 'end'; + } + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); + +fclose($stream); +``` + +> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data +from the end signal handler if the stream is being closed. + +If your callback throws an `Exception`, then the filter process will be aborted. +In order to play nice with PHP's stream handling, +the `Exception` will be transformed to a PHP warning instead: + +```php +Clue\StreamFilter\append($stream, function ($chunk) { + throw new \RuntimeException('Unexpected chunk'); +}); + +// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk" +fwrite($stream, 'hello'); +``` + +The optional `$read_write` parameter can be used to only invoke the `$callback` +when either writing to the stream or only when reading from the stream: + +```php +Clue\StreamFilter\append($stream, function ($chunk) { + // will be called each time you write to the stream + return $chunk; +}, STREAM_FILTER_WRITE); + +Clue\StreamFilter\append($stream, function ($chunk) { + // will be called each time you read from the stream + return $chunk; +}, STREAM_FILTER_READ); +``` + +This function returns a filter resource which can be passed to [`remove()`](#remove). + +> Note that once a filter has been added to stream, the stream can no longer be passed to +> [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) +> (and family). +> +> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line} +> +> This is due to limitations of PHP's stream filter support, as it can no longer reliably +> tell when the underlying stream resource is actually ready. +> As an alternative, consider calling `stream_select()` on the unfiltered stream and +> then pass the unfiltered data through the [`fun()`](#fun) function. + +### prepend() + +The `prepend(resource $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource` function can be used to +prepend a filter callback to the given stream. + +Each stream can have a list of filters attached. +This function prepends a filter to the start of this list. + +If the given filter can not be added, it throws an `Exception`. + +```php +$filter = Clue\StreamFilter\prepend($stream, function ($chunk) { + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); +``` + +This function returns a filter resource which can be passed to [`remove()`](#remove). + +Except for the position in the list of filters, this function behaves exactly +like the [`append()`](#append) function. +For more details about its behavior, see also the [`append()`](#append) function. + +### fun() + +The `fun(string $filter, mixed $parameters = null): callable` function can be used to +create a filter function which uses the given built-in `$filter`. + +PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php). +Using `fun()` makes accessing these as easy as passing an input string to filter +and getting the filtered output string. + +```php +$fun = Clue\StreamFilter\fun('string.rot13'); + +assert('grfg' === $fun('test')); +assert('test' === $fun($fun('test')); +``` + +Please note that not all filter functions may be available depending +on installed PHP extensions and the PHP version in use. +In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions +or parameters as Zend PHP. +Accessing an unknown filter function will result in a `RuntimeException`: + +```php +Clue\StreamFilter\fun('unknown'); // throws RuntimeException +``` + +Some filters may accept or require additional filter parameters – most +filters do not require filter parameters. +If given, the optional `$parameters` argument will be passed to the +underlying filter handler as-is. +In particular, note how *not passing* this parameter at all differs from +explicitly passing a `null` value (which many filters do not accept). +Please refer to the individual filter definition for more details. +For example, the `string.strip_tags` filter can be invoked like this: + +```php +$fun = Clue\StreamFilter\fun('string.strip_tags', ''); + +$ret = $fun('h
i
'); +assert('hi' === $ret); +``` + +Under the hood, this function allocates a temporary memory stream, so it's +recommended to clean up the filter function after use. +Also, some filter functions (in particular the +[zlib compression filters](https://www.php.net/manual/en/filters.compression.php)) +may use internal buffers and may emit a final data chunk on close. +The filter function can be closed by invoking without any arguments: + +```php +$fun = Clue\StreamFilter\fun('zlib.deflate'); + +$ret = $fun('hello') . $fun('world') . $fun(); +assert('helloworld' === gzinflate($ret)); +``` + +The filter function must not be used anymore after it has been closed. +Doing so will result in a `RuntimeException`: + +```php +$fun = Clue\StreamFilter\fun('string.rot13'); +$fun(); + +$fun('test'); // throws RuntimeException +``` + +> Note: If you're using the zlib compression filters, then you should be wary +about engine inconsistencies between different PHP versions and HHVM. +These inconsistencies exist in the underlying PHP engines and there's little we +can do about this in this library. +[Our test suite](tests/) contains several test cases that exhibit these issues. +If you feel some test case is missing or outdated, we're happy to accept PRs! :) + +### remove() + +The `remove(resource $filter): bool` function can be used to +remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend). + +```php +$filter = Clue\StreamFilter\append($stream, function () { + // … +}); +Clue\StreamFilter\remove($filter); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +$ composer require clue/stream-filter:^1.7 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and +HHVM. +It's *highly recommended to use the latest supported PHP version* for this project. +Older PHP versions may suffer from a number of inconsistencies documented above. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ vendor/bin/phpunit +``` + +## License + +This project is released under the permissive [MIT license](LICENSE). + +> Did you know that I offer custom development services and issuing invoices for + sponsorships of releases and for contributions? Contact me (@clue) for details. diff --git a/admin/vendor/clue/stream-filter/composer.json b/admin/vendor/clue/stream-filter/composer.json new file mode 100644 index 0000000..2e74456 --- /dev/null +++ b/admin/vendor/clue/stream-filter/composer.json @@ -0,0 +1,32 @@ +{ + "name": "clue/stream-filter", + "description": "A simple and modern approach to stream filtering in PHP", + "keywords": ["stream", "callback", "filter", "php_user_filter", "stream_filter_append", "stream_filter_register", "bucket brigade"], + "homepage": "https://github.com/clue/stream-filter", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Clue\\Tests\\StreamFilter\\": "tests/" + } + } +} diff --git a/admin/vendor/clue/stream-filter/src/CallbackFilter.php b/admin/vendor/clue/stream-filter/src/CallbackFilter.php new file mode 100644 index 0000000..a667fe6 --- /dev/null +++ b/admin/vendor/clue/stream-filter/src/CallbackFilter.php @@ -0,0 +1,120 @@ +closed = false; + + if (!\is_callable($this->params)) { + throw new \InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)'); + } + $this->callback = $this->params; + + // callback supports end event if it accepts invocation without arguments + $ref = new \ReflectionFunction($this->callback); + $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0); + + return true; + } + + /** @return void */ + #[\ReturnTypeWillChange] + public function onClose() + { + $this->closed = true; + + // callback supports closing and is not already closed + if ($this->supportsClose) { + $this->supportsClose = false; + // invoke without argument to signal end and discard resulting buffer + try { + \call_user_func($this->callback); + } catch (\Exception $ignored) { + // this might be called during engine shutdown, so it's not safe + // to raise any errors or exceptions here + // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING); + } + } + + $this->callback = null; + } + + /** @return int */ + #[\ReturnTypeWillChange] + public function filter($in, $out, &$consumed, $closing) + { + // concatenate whole buffer from input brigade + $data = ''; + while ($bucket = \stream_bucket_make_writeable($in)) { + $consumed += $bucket->datalen; + $data .= $bucket->data; + } + + // skip processing callback that already ended + if ($this->closed) { + return \PSFS_FEED_ME; + } + + // only invoke filter function if buffer is not empty + // this may skip flushing a closing filter + if ($data !== '') { + try { + $data = \call_user_func($this->callback, $data); + } catch (\Exception $e) { + // exception should mark filter as closed + $this->onClose(); + \trigger_error('Error invoking filter: ' . $e->getMessage(), \E_USER_WARNING); + + return \PSFS_ERR_FATAL; + } + } + + // mark filter as closed after processing closing chunk + if ($closing) { + $this->closed = true; + + // callback supports closing and is not already closed + if ($this->supportsClose) { + $this->supportsClose = false; + + // invoke without argument to signal end and append resulting buffer + try { + $data .= \call_user_func($this->callback); + } catch (\Exception $e) { + \trigger_error('Error ending filter: ' . $e->getMessage(), \E_USER_WARNING); + + return \PSFS_ERR_FATAL; + } + } + } + + if ($data !== '') { + // create a new bucket for writing the resulting buffer to the output brigade + // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM) + $bucket = @\stream_bucket_new($this->stream, $data); + + // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler + // because closing the stream invalidates the stream and its stream bucket brigade before + // invoking the filter close handler. + if ($bucket !== false) { + \stream_bucket_append($out, $bucket); + } + } + + return \PSFS_PASS_ON; + } +} diff --git a/admin/vendor/clue/stream-filter/src/functions.php b/admin/vendor/clue/stream-filter/src/functions.php new file mode 100644 index 0000000..865f6b9 --- /dev/null +++ b/admin/vendor/clue/stream-filter/src/functions.php @@ -0,0 +1,380 @@ + Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data + * from the end signal handler if the stream is being closed. + * + * If your callback throws an `Exception`, then the filter process will be aborted. + * In order to play nice with PHP's stream handling, + * the `Exception` will be transformed to a PHP warning instead: + * + * ```php + * Clue\StreamFilter\append($stream, function ($chunk) { + * throw new \RuntimeException('Unexpected chunk'); + * }); + * + * // raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk" + * fwrite($stream, 'hello'); + * ``` + * + * The optional `$read_write` parameter can be used to only invoke the `$callback` + * when either writing to the stream or only when reading from the stream: + * + * ```php + * Clue\StreamFilter\append($stream, function ($chunk) { + * // will be called each time you write to the stream + * return $chunk; + * }, STREAM_FILTER_WRITE); + * + * Clue\StreamFilter\append($stream, function ($chunk) { + * // will be called each time you read from the stream + * return $chunk; + * }, STREAM_FILTER_READ); + * ``` + * + * This function returns a filter resource which can be passed to [`remove()`](#remove). + * + * > Note that once a filter has been added to stream, the stream can no longer be passed to + * > [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) + * > (and family). + * > + * > > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line} + * > + * > This is due to limitations of PHP's stream filter support, as it can no longer reliably + * > tell when the underlying stream resource is actually ready. + * > As an alternative, consider calling `stream_select()` on the unfiltered stream and + * > then pass the unfiltered data through the [`fun()`](#fun) function. + * + * @param resource $stream + * @param callable $callback + * @param int $read_write + * @return resource filter resource which can be used for `remove()` + * @throws \Exception on error + * @uses stream_filter_append() + */ +function append($stream, $callback, $read_write = STREAM_FILTER_ALL) +{ + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_append() expects parameter 1 to be resource,... + $errstr = $error; // @codeCoverageIgnore + }); + + try { + $ret = \stream_filter_append($stream, register(), $read_write, $callback); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); + + // PHP 8 throws above on type errors, older PHP and memory issues can throw here + // @codeCoverageIgnoreStart + if ($ret === false) { + throw new \RuntimeException('Unable to append filter: ' . $errstr); + } + // @codeCoverageIgnoreEnd + + return $ret; +} + +/** + * Prepend a filter callback to the given stream. + * + * Each stream can have a list of filters attached. + * This function prepends a filter to the start of this list. + * + * If the given filter can not be added, it throws an `Exception`. + * + * ```php + * $filter = Clue\StreamFilter\prepend($stream, function ($chunk) { + * // will be called each time you read or write a $chunk to/from the stream + * return $chunk; + * }); + * ``` + * + * This function returns a filter resource which can be passed to [`remove()`](#remove). + * + * Except for the position in the list of filters, this function behaves exactly + * like the [`append()`](#append) function. + * For more details about its behavior, see also the [`append()`](#append) function. + * + * @param resource $stream + * @param callable $callback + * @param int $read_write + * @return resource filter resource which can be used for `remove()` + * @throws \Exception on error + * @uses stream_filter_prepend() + */ +function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL) +{ + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_prepend() expects parameter 1 to be resource,... + $errstr = $error; // @codeCoverageIgnore + }); + + try { + $ret = \stream_filter_prepend($stream, register(), $read_write, $callback); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); + + // PHP 8 throws above on type errors, older PHP and memory issues can throw here + // @codeCoverageIgnoreStart + if ($ret === false) { + throw new \RuntimeException('Unable to prepend filter: ' . $errstr); + } + // @codeCoverageIgnoreEnd + + return $ret; +} + +/** + * Create a filter function which uses the given built-in `$filter`. + * + * PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php). + * Using `fun()` makes accessing these as easy as passing an input string to filter + * and getting the filtered output string. + * + * ```php + * $fun = Clue\StreamFilter\fun('string.rot13'); + * + * assert('grfg' === $fun('test')); + * assert('test' === $fun($fun('test')); + * ``` + * + * Please note that not all filter functions may be available depending + * on installed PHP extensions and the PHP version in use. + * In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions + * or parameters as Zend PHP. + * Accessing an unknown filter function will result in a `RuntimeException`: + * + * ```php + * Clue\StreamFilter\fun('unknown'); // throws RuntimeException + * ``` + * + * Some filters may accept or require additional filter parameters – most + * filters do not require filter parameters. + * If given, the optional `$parameters` argument will be passed to the + * underlying filter handler as-is. + * In particular, note how *not passing* this parameter at all differs from + * explicitly passing a `null` value (which many filters do not accept). + * Please refer to the individual filter definition for more details. + * For example, the `string.strip_tags` filter can be invoked like this: + * + * ```php + * $fun = Clue\StreamFilter\fun('string.strip_tags', '
'); + * + * $ret = $fun('h
i
'); + * assert('hi' === $ret); + * ``` + * + * Under the hood, this function allocates a temporary memory stream, so it's + * recommended to clean up the filter function after use. + * Also, some filter functions (in particular the + * [zlib compression filters](https://www.php.net/manual/en/filters.compression.php)) + * may use internal buffers and may emit a final data chunk on close. + * The filter function can be closed by invoking without any arguments: + * + * ```php + * $fun = Clue\StreamFilter\fun('zlib.deflate'); + * + * $ret = $fun('hello') . $fun('world') . $fun(); + * assert('helloworld' === gzinflate($ret)); + * ``` + * + * The filter function must not be used anymore after it has been closed. + * Doing so will result in a `RuntimeException`: + * + * ```php + * $fun = Clue\StreamFilter\fun('string.rot13'); + * $fun(); + * + * $fun('test'); // throws RuntimeException + * ``` + * + * > Note: If you're using the zlib compression filters, then you should be wary + * about engine inconsistencies between different PHP versions and HHVM. + * These inconsistencies exist in the underlying PHP engines and there's little we + * can do about this in this library. + * [Our test suite](tests/) contains several test cases that exhibit these issues. + * If you feel some test case is missing or outdated, we're happy to accept PRs! :) + * + * @param string $filter built-in filter name. See stream_get_filters() or http://php.net/manual/en/filters.php + * @param mixed $parameters (optional) parameters to pass to the built-in filter as-is + * @return callable a filter callback which can be append()'ed or prepend()'ed + * @throws \RuntimeException on error + * @link http://php.net/manual/en/filters.php + * @see stream_get_filters() + * @see append() + */ +function fun($filter, $parameters = null) +{ + $fp = \fopen('php://memory', 'w'); + + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_append() expects parameter 1 to be resource,... + $errstr = $error; + }); + + if (\func_num_args() === 1) { + $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE); + } else { + $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters); + } + + \restore_error_handler(); + + if ($filter === false) { + \fclose($fp); + throw new \RuntimeException('Unable to access built-in filter: ' . $errstr); + } + + // append filter function which buffers internally + $buffer = ''; + append($fp, function ($chunk) use (&$buffer) { + $buffer .= $chunk; + + // always return empty string in order to skip actually writing to stream resource + return ''; + }, \STREAM_FILTER_WRITE); + + $closed = false; + + return function ($chunk = null) use ($fp, $filter, &$buffer, &$closed) { + if ($closed) { + throw new \RuntimeException('Unable to perform operation on closed stream'); + } + if ($chunk === null) { + $closed = true; + $buffer = ''; + \fclose($fp); + return $buffer; + } + // initialize buffer and invoke filters by attempting to write to stream + $buffer = ''; + \fwrite($fp, $chunk); + + // buffer now contains everything the filter function returned + return $buffer; + }; +} + +/** + * Remove a filter previously added via `append()` or `prepend()`. + * + * ```php + * $filter = Clue\StreamFilter\append($stream, function () { + * // … + * }); + * Clue\StreamFilter\remove($filter); + * ``` + * + * @param resource $filter + * @return bool true on success or false on error + * @throws \RuntimeException on error + * @uses stream_filter_remove() + */ +function remove($filter) +{ + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_remove() expects parameter 1 to be resource,... + $errstr = $error; + }); + + try { + $ret = \stream_filter_remove($filter); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); + + if ($ret === false) { + // PHP 8 throws above on type errors, older PHP and memory issues can throw here + throw new \RuntimeException('Unable to remove filter: ' . $errstr); + } +} + +/** + * Registers the callback filter and returns the resulting filter name + * + * There should be little reason to call this function manually. + * + * @return string filter name + * @uses CallbackFilter + */ +function register() +{ + static $registered = null; + if ($registered === null) { + $registered = 'stream-callback'; + \stream_filter_register($registered, __NAMESPACE__ . '\CallbackFilter'); + } + return $registered; +} diff --git a/admin/vendor/clue/stream-filter/src/functions_include.php b/admin/vendor/clue/stream-filter/src/functions_include.php new file mode 100644 index 0000000..198b45f --- /dev/null +++ b/admin/vendor/clue/stream-filter/src/functions_include.php @@ -0,0 +1,9 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/admin/vendor/composer/InstalledVersions.php b/admin/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/admin/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/admin/vendor/composer/LICENSE b/admin/vendor/composer/LICENSE new file mode 100644 index 0000000..62ecfd8 --- /dev/null +++ b/admin/vendor/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/composer/autoload_classmap.php b/admin/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..37e449b --- /dev/null +++ b/admin/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/admin/vendor/composer/autoload_files.php b/admin/vendor/composer/autoload_files.php new file mode 100644 index 0000000..08309c4 --- /dev/null +++ b/admin/vendor/composer/autoload_files.php @@ -0,0 +1,13 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + '9c67151ae59aff4788964ce8eb2a0f43' => $vendorDir . '/clue/stream-filter/src/functions_include.php', + '09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php', + '8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php', +); diff --git a/admin/vendor/composer/autoload_namespaces.php b/admin/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..f1ae7a0 --- /dev/null +++ b/admin/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'), + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'), + 'Symfony\\Component\\Uid\\' => array($vendorDir . '/symfony/uid'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), + 'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'), + 'Opis\\Uri\\' => array($vendorDir . '/opis/uri/src'), + 'Opis\\String\\' => array($vendorDir . '/opis/string/src'), + 'Opis\\JsonSchema\\' => array($vendorDir . '/opis/json-schema/src'), + 'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'), + 'Nyholm\\Psr7Server\\' => array($vendorDir . '/nyholm/psr7-server/src'), + 'Mcp\\' => array($vendorDir . '/mcp/sdk/src'), + 'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'), + 'Http\\Message\\' => array($vendorDir . '/php-http/message/src'), + 'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'), + 'Http\\Client\\Curl\\' => array($vendorDir . '/php-http/curl-client/src'), + 'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/src'), + 'Clue\\StreamFilter\\' => array($vendorDir . '/clue/stream-filter/src'), +); diff --git a/admin/vendor/composer/autoload_real.php b/admin/vendor/composer/autoload_real.php new file mode 100644 index 0000000..9571e87 --- /dev/null +++ b/admin/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInit028b5c045697560e085794f7ca6d8693::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/admin/vendor/composer/autoload_static.php b/admin/vendor/composer/autoload_static.php new file mode 100644 index 0000000..adeda10 --- /dev/null +++ b/admin/vendor/composer/autoload_static.php @@ -0,0 +1,199 @@ + __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '9c67151ae59aff4788964ce8eb2a0f43' => __DIR__ . '/..' . '/clue/stream-filter/src/functions_include.php', + '09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php', + '8cff32064859f4559445b89279f3199c' => __DIR__ . '/..' . '/php-http/message/src/filters.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'p' => + array ( + 'phpDocumentor\\Reflection\\' => 25, + ), + 'W' => + array ( + 'Webmozart\\Assert\\' => 17, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Uuid\\' => 22, + 'Symfony\\Component\\Uid\\' => 22, + 'Symfony\\Component\\OptionsResolver\\' => 34, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Server\\' => 16, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\EventDispatcher\\' => 20, + 'Psr\\Container\\' => 14, + 'Psr\\Clock\\' => 10, + 'PHPStan\\PhpDocParser\\' => 21, + ), + 'O' => + array ( + 'Opis\\Uri\\' => 9, + 'Opis\\String\\' => 12, + 'Opis\\JsonSchema\\' => 16, + ), + 'N' => + array ( + 'Nyholm\\Psr7\\' => 12, + 'Nyholm\\Psr7Server\\' => 18, + ), + 'M' => + array ( + 'Mcp\\' => 4, + ), + 'H' => + array ( + 'Http\\Promise\\' => 13, + 'Http\\Message\\' => 13, + 'Http\\Discovery\\' => 15, + 'Http\\Client\\Curl\\' => 17, + 'Http\\Client\\' => 12, + ), + 'D' => + array ( + 'Doctrine\\Deprecations\\' => 22, + ), + 'C' => + array ( + 'Clue\\StreamFilter\\' => 18, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'phpDocumentor\\Reflection\\' => + array ( + 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', + 1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', + 2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', + ), + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), + 'Symfony\\Polyfill\\Uuid\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-uuid', + ), + 'Symfony\\Component\\Uid\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/uid', + ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Http\\Server\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-server-handler/src', + 1 => __DIR__ . '/..' . '/psr/http-server-middleware/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/clock/src', + ), + 'PHPStan\\PhpDocParser\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/phpdoc-parser/src', + ), + 'Opis\\Uri\\' => + array ( + 0 => __DIR__ . '/..' . '/opis/uri/src', + ), + 'Opis\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/opis/string/src', + ), + 'Opis\\JsonSchema\\' => + array ( + 0 => __DIR__ . '/..' . '/opis/json-schema/src', + ), + 'Nyholm\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/nyholm/psr7/src', + ), + 'Nyholm\\Psr7Server\\' => + array ( + 0 => __DIR__ . '/..' . '/nyholm/psr7-server/src', + ), + 'Mcp\\' => + array ( + 0 => __DIR__ . '/..' . '/mcp/sdk/src', + ), + 'Http\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/promise/src', + ), + 'Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/message/src', + ), + 'Http\\Discovery\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/discovery/src', + ), + 'Http\\Client\\Curl\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/curl-client/src', + ), + 'Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/httplug/src', + ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/src', + ), + 'Clue\\StreamFilter\\' => + array ( + 0 => __DIR__ . '/..' . '/clue/stream-filter/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit028b5c045697560e085794f7ca6d8693::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit028b5c045697560e085794f7ca6d8693::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit028b5c045697560e085794f7ca6d8693::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/admin/vendor/composer/installed.json b/admin/vendor/composer/installed.json new file mode 100644 index 0000000..6afd997 --- /dev/null +++ b/admin/vendor/composer/installed.json @@ -0,0 +1,2020 @@ +{ + "packages": [ + { + "name": "clue/stream-filter", + "version": "v1.7.0", + "version_normalized": "1.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/clue/stream-filter.git", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "time": "2023-12-20T15:40:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "Clue\\StreamFilter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "support": { + "issues": "https://github.com/clue/stream-filter/issues", + "source": "https://github.com/clue/stream-filter/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "install-path": "../clue/stream-filter" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "version_normalized": "1.1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2026-02-07T07:09:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "mcp/sdk", + "version": "v0.8.1", + "version_normalized": "0.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/modelcontextprotocol/php-sdk.git", + "reference": "c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/modelcontextprotocol/php-sdk/zipball/c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e", + "reference": "c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "opis/json-schema": "^2.4", + "php": "^8.1", + "php-http/discovery": "^1.20", + "phpdocumentor/reflection-docblock": "^5.6 || ^6.0", + "psr/clock": "^1.0", + "psr/container": "^1.0 || ^2.0", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "ext-openssl": "*", + "firebase/php-jwt": "^6.10 || ^7.0", + "laminas/laminas-httphandlerrunner": "^2.12", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phar-io/composer-distributor": "^1.0.2", + "php-cs-fixer/shim": "^3.91", + "phpdocumentor/shim": "^3", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5", + "psr/simple-cache": "^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/console": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/http-client": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/process": "^5.4 || ^6.4 || ^7.3 || ^8.0" + }, + "suggest": { + "symfony/finder": "Required for file-based discovery." + }, + "time": "2026-08-29T23:10:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Mcp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Kyrian Obikwelu", + "email": "koshnawaza@gmail.com" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "Model Context Protocol SDK for Client and Server applications in PHP", + "support": { + "issues": "https://github.com/modelcontextprotocol/php-sdk/issues", + "source": "https://github.com/modelcontextprotocol/php-sdk/tree/v0.8.1" + }, + "install-path": "../mcp/sdk" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "version_normalized": "1.8.2.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "time": "2024-09-09T07:06:30+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "install-path": "../nyholm/psr7" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "time": "2023-11-08T09:30:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "install-path": "../nyholm/psr7-server" + }, + { + "name": "opis/json-schema", + "version": "2.6.0", + "version_normalized": "2.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/8458763e0dd0b6baa310e04f1829fc73da4e8c8a", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.1", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "time": "2025-10-17T12:46:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.6.0" + }, + "install-path": "../opis/json-schema" + }, + { + "name": "opis/string", + "version": "2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/3e4d2aaff518ac518530b89bb26ed40f4503635e", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "time": "2025-10-17T12:38:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.1.0" + }, + "install-path": "../opis/string" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "time": "2021-05-22T15:57:08+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "install-path": "../opis/uri" + }, + { + "name": "php-http/curl-client", + "version": "2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/curl-client.git", + "reference": "f59d6992065f44be8b8fb484dd678a919c27dbf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/curl-client/zipball/f59d6992065f44be8b8fb484dd678a919c27dbf2", + "reference": "f59d6992065f44be8b8fb484dd678a919c27dbf2", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^8.1", + "php-http/discovery": "^1.6", + "php-http/httplug": "^2.0", + "php-http/message": "^1.2", + "psr/http-client": "^1.0", + "psr/http-factory-implementation": "^1.0", + "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0", + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/client-integration-tests": "^4.0", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^9.6.17 || ^10.0 || ^11.0 || ^12.0" + }, + "time": "2025-12-09T12:02:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\Curl\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + } + ], + "description": "PSR-18 and HTTPlug Async client with cURL", + "homepage": "http://php-http.org", + "keywords": [ + "curl", + "http", + "psr-18" + ], + "support": { + "issues": "https://github.com/php-http/curl-client/issues", + "source": "https://github.com/php-http/curl-client/tree/2.4.0" + }, + "install-path": "../php-http/curl-client" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "version_normalized": "1.20.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "time": "2024-10-02T11:20:13+00:00", + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "install-path": "../php-http/discovery" + }, + { + "name": "php-http/httplug", + "version": "2.4.1", + "version_normalized": "2.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/promise": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" + }, + "time": "2024-09-23T11:39:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "support": { + "issues": "https://github.com/php-http/httplug/issues", + "source": "https://github.com/php-http/httplug/tree/2.4.1" + }, + "install-path": "../php-http/httplug" + }, + { + "name": "php-http/message", + "version": "1.16.2", + "version_normalized": "1.16.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/06dd5e8562f84e641bf929bfe699ee0f5ce8080a", + "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.5", + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.6", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.0.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "slim/slim": "^3.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "laminas/laminas-diactoros": "Used with Diactoros Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation" + }, + "time": "2024-10-02T11:34:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/filters.php" + ], + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "support": { + "issues": "https://github.com/php-http/message/issues", + "source": "https://github.com/php-http/message/tree/1.16.2" + }, + "install-path": "../php-http/message" + }, + { + "name": "php-http/promise", + "version": "1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" + }, + "time": "2024-03-15T13:55:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/php-http/promise/issues", + "source": "https://github.com/php-http/promise/tree/1.3.1" + }, + "install-path": "../php-http/promise" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2020-06-27T09:03:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "install-path": "../phpdocumentor/reflection-common" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", + "version_normalized": "6.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "time": "2026-03-18T20:49:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "install-path": "../phpdocumentor/reflection-docblock" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "time": "2026-01-06T21:53:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "install-path": "../phpdocumentor/type-resolver" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.5", + "version_normalized": "2.3.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "time": "2026-08-31T16:05:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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" + }, + "install-path": "../phpstan/phpdoc-parser" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "time": "2022-11-25T14:36:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "install-path": "../psr/clock" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "version_normalized": "1.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-09-23T14:17:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2024-04-15T12:06:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "2.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2023-04-04T09:54:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-10T20:06:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "install-path": "../psr/http-server-handler" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "time": "2023-04-11T06:14:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "install-path": "../psr/http-server-middleware" + }, + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2024-09-11T13:17:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "install-path": "../psr/log" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "version_normalized": "3.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2026-06-05T06:23:12+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/options-resolver", + "version": "v7.4.8", + "version_normalized": "7.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "time": "2026-03-24T13:12:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/options-resolver" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "version_normalized": "1.37.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "time": "2026-04-10T16:19:22+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-uuid" + }, + { + "name": "symfony/uid", + "version": "v7.4.17", + "version_normalized": "7.4.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "69d732355a139c6f8881337d28515aa01f12b8be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/69d732355a139c6f8881337d28515aa01f12b8be", + "reference": "69d732355a139c6f8881337d28515aa01f12b8be", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "time": "2026-08-11T07:38:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/uid" + }, + { + "name": "webmozart/assert", + "version": "2.4.1", + "version_normalized": "2.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "time": "2026-06-15T15:31:57+00:00", + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.1" + }, + "install-path": "../webmozart/assert" + } + ], + "dev": false, + "dev-package-names": [] +} diff --git a/admin/vendor/composer/installed.php b/admin/vendor/composer/installed.php new file mode 100644 index 0000000..c7f51e3 --- /dev/null +++ b/admin/vendor/composer/installed.php @@ -0,0 +1,343 @@ + array( + 'name' => 'joomengine/mcp-component', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '14c715c50c2cc29fd3c8cc3c4780442efb507398', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'clue/stream-filter' => array( + 'pretty_version' => 'v1.7.0', + 'version' => '1.7.0.0', + 'reference' => '049509fef80032cb3f051595029ab75b49a3c2f7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../clue/stream-filter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => '1.1.6', + 'version' => '1.1.6.0', + 'reference' => 'd4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'joomengine/mcp-component' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '14c715c50c2cc29fd3c8cc3c4780442efb507398', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'mcp/sdk' => array( + 'pretty_version' => 'v0.8.1', + 'version' => '0.8.1.0', + 'reference' => 'c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../mcp/sdk', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nyholm/psr7' => array( + 'pretty_version' => '1.8.2', + 'version' => '1.8.2.0', + 'reference' => 'a71f2b11690f4b24d099d6b16690a90ae14fc6f3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nyholm/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nyholm/psr7-server' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '4335801d851f554ca43fa6e7d2602141538854dc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nyholm/psr7-server', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'opis/json-schema' => array( + 'pretty_version' => '2.6.0', + 'version' => '2.6.0.0', + 'reference' => '8458763e0dd0b6baa310e04f1829fc73da4e8c8a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../opis/json-schema', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'opis/string' => array( + 'pretty_version' => '2.1.0', + 'version' => '2.1.0.0', + 'reference' => '3e4d2aaff518ac518530b89bb26ed40f4503635e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../opis/string', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'opis/uri' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../opis/uri', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/async-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + 1 => '1.0', + ), + ), + 'php-http/client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + 1 => '1.0', + ), + ), + 'php-http/curl-client' => array( + 'pretty_version' => '2.4.0', + 'version' => '2.4.0.0', + 'reference' => 'f59d6992065f44be8b8fb484dd678a919c27dbf2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/curl-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/discovery' => array( + 'pretty_version' => '1.20.0', + 'version' => '1.20.0.0', + 'reference' => '82fe4c73ef3363caed49ff8dd1539ba06044910d', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../php-http/discovery', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/httplug' => array( + 'pretty_version' => '2.4.1', + 'version' => '2.4.1.0', + 'reference' => '5cad731844891a4c282f3f3e1b582c46839d22f4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/httplug', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/message' => array( + 'pretty_version' => '1.16.2', + 'version' => '1.16.2.0', + 'reference' => '06dd5e8562f84e641bf929bfe699ee0f5ce8080a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/message-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'php-http/promise' => array( + 'pretty_version' => '1.3.1', + 'version' => '1.3.1.0', + 'reference' => 'fc85b1fba37c169a69a07ef0d5a8075770cc1f83', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/promise', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpdocumentor/reflection-common' => array( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/reflection-common', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpdocumentor/reflection-docblock' => array( + 'pretty_version' => '6.0.3', + 'version' => '6.0.3.0', + 'reference' => '7bae67520aa9f5ecc506d646810bd40d9da54582', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/reflection-docblock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpdocumentor/type-resolver' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => '327a05bbee54120d4786a0dc67aad30226ad4cf9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/type-resolver', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpstan/phpdoc-parser' => array( + 'pretty_version' => '2.3.5', + 'version' => '2.3.5.0', + 'reference' => '148cefffaf0233e4c08cc13db8a195a56dd6dfe9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpdoc-parser', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/clock' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + 1 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + 1 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '2.0', + 'version' => '2.0.0.0', + 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + 1 => '1.0', + ), + ), + 'psr/http-server-handler' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => '84c4fb66179be4caaf8e97bd239203245302e7d4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-server-handler', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-server-middleware' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => 'c1481f747daaa6a0782775cd6a8c26a1bf4a3829', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-server-middleware', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.7.1', + 'version' => '3.7.1.0', + 'reference' => 'f3202fa1b5097b0af062dc978b32ecf63404e31d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/options-resolver' => array( + 'pretty_version' => 'v7.4.8', + 'version' => '7.4.8.0', + 'reference' => '2888fcdc4dc2fd5f7c7397be78631e8af12e02b4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/options-resolver', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-uuid' => array( + 'pretty_version' => 'v1.37.0', + 'version' => '1.37.0.0', + 'reference' => '26dfec253c4cf3e51b541b52ddf7e42cb0908e94', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-uuid', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/uid' => array( + 'pretty_version' => 'v7.4.17', + 'version' => '7.4.17.0', + 'reference' => '69d732355a139c6f8881337d28515aa01f12b8be', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/uid', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'webmozart/assert' => array( + 'pretty_version' => '2.4.1', + 'version' => '2.4.1.0', + 'reference' => '2ccb7c2e821038c03a3e6e1700c570c158c55f70', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webmozart/assert', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/admin/vendor/composer/platform_check.php b/admin/vendor/composer/platform_check.php new file mode 100644 index 0000000..2a8f1c8 --- /dev/null +++ b/admin/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80300)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.3.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/admin/vendor/doctrine/deprecations/LICENSE b/admin/vendor/doctrine/deprecations/LICENSE new file mode 100644 index 0000000..156905c --- /dev/null +++ b/admin/vendor/doctrine/deprecations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2021 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/vendor/doctrine/deprecations/README.md b/admin/vendor/doctrine/deprecations/README.md new file mode 100644 index 0000000..7cbc606 --- /dev/null +++ b/admin/vendor/doctrine/deprecations/README.md @@ -0,0 +1,216 @@ +# Doctrine Deprecations + +A small (side-effect free by default) layer on top of +`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. + +- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under +- options to avoid having to rely on error handlers global state by using PSR-3 logging +- deduplicate deprecation messages to avoid excessive triggering and reduce overhead + +We recommend to collect Deprecations using a PSR logger instead of relying on +the global error handler. + +## Usage from consumer perspective: + +Enable Doctrine deprecations to be sent to a PSR3 logger: + +```php +\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); +``` + +Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` +messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`. +Alternatively, call: + +```php +\Doctrine\Deprecations\Deprecation::enableWithTriggerError(); +``` + +If you only want to enable deprecation tracking, without logging or calling `trigger_error` +then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`. +Alternatively, call: + +```php +\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); +``` + +Tracking is enabled with all three modes and provides access to all triggered +deprecations and their individual count: + +```php +$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); + +foreach ($deprecations as $identifier => $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## Displaying deprecations after running a PHPUnit test suite + +It is possible to integrate this library with PHPUnit to display all +deprecations triggered during the test suite execution. + +```xml + + + + + + + + + + + + src + + + +``` + +Note that you can still trigger Deprecations in your code, provided you use the +`#[IgnoreDeprecations]` to ignore them for tests that call it. + +At the moment, it is not possible to disable deduplication with an environment +variable, but you can use a bootstrap file to achieve that: + +```php +// tests/bootstrap.php + + … + +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/admin/vendor/doctrine/deprecations/composer.json b/admin/vendor/doctrine/deprecations/composer.json new file mode 100644 index 0000000..14a81cf --- /dev/null +++ b/admin/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,39 @@ +{ + "name": "doctrine/deprecations", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "license": "MIT", + "type": "library", + "homepage": "https://www.doctrine-project.org/", + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/admin/vendor/doctrine/deprecations/src/Deprecation.php b/admin/vendor/doctrine/deprecations/src/Deprecation.php new file mode 100644 index 0000000..1801e6c --- /dev/null +++ b/admin/vendor/doctrine/deprecations/src/Deprecation.php @@ -0,0 +1,309 @@ +|null */ + private static $type; + + /** @var LoggerInterface|null */ + private static $logger; + + /** @var array */ + private static $ignoredPackages = []; + + /** @var array */ + private static $triggeredDeprecations = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param float|int|string $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param float|int|string $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** @param list $backtrace */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if (($type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'] ?? null, + 'line' => $backtrace[0]['line'] ?? null, + 'package' => $package, + 'link' => $link, + ]; + + assert(self::$logger !== null); + + self::$logger->notice($message, $context); + } + + if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file'] ?? 'native code'), + $backtrace[0]['line'] ?? 0, + self::basename($backtrace[1]['file'] ?? 'native code'), + $backtrace[1]['line'] ?? 0, + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + self::$ignoredLinks = []; + + foreach (self::$triggeredDeprecations as $link => $count) { + self::$triggeredDeprecations[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = true; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$triggeredDeprecations; + } + + /** @return int-mask-of */ + private static function getTypeFromEnv(): int + { + switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { + case 'trigger': + self::$type = self::TYPE_TRIGGER_ERROR; + break; + + case 'track': + self::$type = self::TYPE_TRACK_DEPRECATIONS; + break; + + default: + self::$type = self::TYPE_NONE; + break; + } + + return self::$type; + } +} diff --git a/admin/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php b/admin/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php new file mode 100644 index 0000000..7e168bd --- /dev/null +++ b/admin/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** @before */ + #[Before] + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** @after */ + #[After] + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/admin/vendor/mcp/sdk/.gitattributes b/admin/vendor/mcp/sdk/.gitattributes new file mode 100644 index 0000000..b7b2a18 --- /dev/null +++ b/admin/vendor/mcp/sdk/.gitattributes @@ -0,0 +1,12 @@ +/.github export-ignore +/.phpdoc export-ignore +/docs export-ignore +/examples export-ignore +/tests export-ignore +/.php-cs-fixer.dist.php export-ignore +/Makefile export-ignore +/mkdocs.yml export-ignore +/requirements-docs.txt export-ignore +/phpdoc.dist.xml +/phpstan* export-ignore +/phpunit.xml.dist export-ignore diff --git a/admin/vendor/mcp/sdk/.gitignore b/admin/vendor/mcp/sdk/.gitignore new file mode 100644 index 0000000..ca3a13f --- /dev/null +++ b/admin/vendor/mcp/sdk/.gitignore @@ -0,0 +1,21 @@ +.phpunit.cache +.php-cs-fixer.cache +composer.lock +coverage +vendor +examples/**/dev.log +examples/**/cache +examples/**/sessions +tests/Conformance/client-conformance.json +tests/Conformance/server-conformance.json +tests/Conformance/results* +tests/Conformance/sessions +tests/Conformance/logs/*.log + +# phpDocumentor +.phpdoc/build/ +.phpdoc/cache/ + +# Documentation site (make docs) +/site/ +/.cache/ diff --git a/admin/vendor/mcp/sdk/CHANGELOG.md b/admin/vendor/mcp/sdk/CHANGELOG.md new file mode 100644 index 0000000..f085872 --- /dev/null +++ b/admin/vendor/mcp/sdk/CHANGELOG.md @@ -0,0 +1,141 @@ +# Changelog + +All notable changes to `mcp/sdk` will be documented in this file. + +0.8.0 +----- + +* Support for MCP spec version 2026-07-28 incl. stateless HTTP transport + * Negotiate the protocol revision during `initialize`: the server counter-offers a revision it supports and the client fails the handshake instead of continuing on an unagreed one. Adds `Client::getProtocolVersion()`. + * Serve both protocol eras from one endpoint: `StreamableHttpTransport` classifies each request (`InboundClassifier`) and routes it to the matching dispatcher, so one URL answers modern and handshake-era clients alike. `Builder::withoutModernEra()` opts out, `Builder::setModernVersions()` narrows the modern leg. + * Speak the lifecycle from the client: `Client` opens with `server/discover`, stamps `_meta` with version, capabilities and client info, and sends the `Mcp-Method`/`Mcp-Name`/`Mcp-Param-*` headers (via `Client\Stateless\ToolCatalog`). `Schema\Wire\McpHeader` holds the shared header names. + * Add multi round-trip requests (SEP-2322): a handler returning `InputRequiredResult` yields `resultType: "input_required"` with an opaque, signed `requestState` (`RequestStateCodec`, key via `Builder::setRequestState()`); the client retries with `inputResponses`, read through `RequestContext::getInputContext()` and its typed `elicitResult()`/`samplingResult()`/`rootsResult()`. On the client, `InputRequestResolver` answers such results automatically from the host's elicitation, sampling and roots handlers. + * Serve `ClientGateway::elicit()`/`elicitUrl()` on every revision: where the client cannot be asked mid-request, `Server\Stateless\ElicitationReplay` turns the ask into `input_required` and resumes once re-sent — the handler is entered once per ask. `InputRequiredShim` does the reverse for handshake-era clients. `sample()`/`listRoots()` raise a `LogicException` on `2026-07-28`, which removed them. + * Run stateless handlers in a fiber so `$gateway->progress()`/`log()` stream over SSE when the handler emits something and the client accepts `text/event-stream`; honour `io.modelcontextprotocol/logLevel` (SEP-2575). Adds `LoggingLevel::severity()`/`isAtLeast()`. + * Deliver notifications on `subscriptions/listen` (SEP-2575) via `NotificationBusInterface` — `InMemoryNotificationBus` for persistent runtimes, `Psr16NotificationBus` for PHP-FPM — set with `Builder::setNotificationBus()`; `Builder::setSubscriptionLifetime()` replaces the hard-coded 30s ceiling. + * Validate the standard request headers (SEP-2243) with `StandardHeaderValidator` (`Builder::setHeaderValidator()`), answering `-32020` when they contradict the body. + * Add `Wire\CachePolicy` (`Builder::setCachePolicy()`) for SEP-2549 caching hints; defaults to `ttlMs: 0, cacheScope: private`. A `ReadResourceResult` may override with its own values. + * Carry W3C trace context (SEP-414): `traceparent`/`tracestate`/`baggage` from `_meta` are exposed via `RequestContext::getTraceContext()` and echoed onto the request's notifications. + * Close the schema gaps for `2025-06-18`/`2025-11-25` and add the `2026-07-28` surface (SEP-2106): url-mode elicitation (`ClientGateway::elicitUrl()`/`supportsElicitationUrl()`), `Implementation::title`, and `outputSchema`/`structuredContent` accepting any JSON value. + * Deprecate Roots, Sampling and Logging (SEP-2577, earliest removal `2027-07-28`); they keep working but trigger a deprecation notice. + * [BC Break] Answer a not-found subject with `-32602` instead of `-32002` (SEP-2164): `resources/read` picks the code by revision (`-32602` from `2026-07-28` on), `prompts/get`, `completion/complete` and `tools/call` switch on every revision. Adds `ProtocolVersion::usesInvalidParamsForResourceNotFound()`. + * [BC Break] A list-shaped tool result is only sent as `structuredContent` on `2026-07-28`+; older revisions keep the JSON-encoded value in `content`. +* [BC Break] Add the extensions framework (SEP-2133) MCP Apps sits on: `ExtensionInterface::getId()` returns an `ExtensionIdentifier` value object, and the interface gains `getMessages()`/`getRequestHandlers()` (extend `AbstractExtension` to skip both). `MessageFactory::make()` takes an `$additional` message list; `RequestHandlerInterface`'s result template is covariant. `ServerExtensionInterface` is replaced by the side-agnostic `Schema\Extension\ExtensionInterface`. +* Add client-side extension negotiation: `ClientGateway::supportsExtension()`, `Client\Builder::enableExtension()`, `ClientCapabilities::withExtensions()`. +* Add sampling-with-tools: sampling requests can carry tools and tool-choice preferences, messages support tool-use/tool-result blocks, and clients advertise `sampling.context`/`sampling.tools` (`ClientGateway::supportsSamplingTools()`/`supportsSamplingContext()`). Requests violating the tool-flow rules are rejected with a JSON-RPC error. +* [BC Break] `SamplingMessage::$content` and `CreateSamplingMessageResult::$content` may be a list of content blocks — use `getContentBlocks()`. `CreateSamplingMessageResult` rejects roles other than `assistant` and empty content. +* Add client-side Roots support (`RootsCallbackInterface`, `Client::sendRootsListChanged()`) and server-side `ClientGateway::listRoots()`/`supportsRoots()`/`supportsSampling()`. +* Add `Schema\Content\ResourceLink` to reference a resource by URI in tool results and prompt messages. +* [BC Break] `Schema\JsonRpc\Error` accepts `null` as `$id`; an unreadable id now omits the member instead of sending `"id": ""`. `MessageFactory` decodes a missing or null id as an id-less error. +* Preserve the request `id` on an invalid-but-parseable message (`-32600`) via `InvalidInputMessageException::getRequestId()`. +* [BC Break] Drop the SDK-only name pattern on `ResourceDefinition`/`ResourceTemplate` `$name`; the spec allows any string. +* Log expected tool failures (`ToolCallException`) at debug level instead of error. +* Add `annotations` to `ImageContent`. +* Fix empty tool/resource schemas serializing as `[]` instead of `{}`. +* Fix `PromptResultFormatter` dropping `annotations`, `_meta` and `mimeType` for plain-array content. + +0.7.0 +----- + +* Add client-side elicitation support: `ElicitationCallbackInterface`, `ElicitationRequestHandler`, and `ElicitationException` let clients respond to server elicitation requests. +* Defer element loading to the first registry read: loaders now run at request time (first `has*`/`get*` call) instead of eagerly at `Builder::build()`, fixing empty registries under persistent runtimes (e.g. FrankenPHP worker mode) where a loader's data source is not ready at build time. Adds `Builder::setLazyLoading()` (default on), a public `Registry::load()`, and an optional `LoaderInterface` constructor argument on `Registry`. +* [BC Break] Element loading is lazy by default: loader failures now surface on the first request rather than at `Builder::build()`, and `initialize` advertises capabilities from the configured sources rather than the loaded registry. Call `Builder::setLazyLoading(false)` to restore eager build-time loading. +* Allow `[$instance, 'methodName']` as an element handler in `Builder::addTool()`, `addResource()`, `addResourceTemplate()`, and `addPrompt()`. Unblocks handlers with constructor dependencies that the container-less `new $className()` fallback cannot build. +* Always emit an `items` schema for array tool parameters: untyped arrays get `items: {}` and nullable typed arrays (e.g. `string[]|null`) keep their element type. Fixes strict clients rejecting tools with "array type must have items" (#151). +* Harden JSON-RPC input parsing: single-message vs batch is now decided from the decoded JSON type (object → single, list array → batch) instead of the raw first byte. Scalars, empty payloads, and non-object batch elements are surfaced as `InvalidInputMessageException` entries instead of triggering warnings or a `TypeError`. +* Add `maxBatchSize` (default `100`) to `MessageFactory` — oversized JSON-RPC batches are rejected before any message is constructed, guarding against amplification. +* Add `maxBodyBytes` (default 4 MiB) to `StreamableHttpTransport` — POST bodies exceeding the cap are rejected with `413`. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory. +* Reject malformed `Mcp-Session-Id` headers with a `400` response: a repeated header or a value that is not a valid UUID is now rejected up front instead of surfacing as an uncaught `Uuid::fromString()` error. +* Extract RFC 9728 metadata serving into `ProtectedResourceMetadataHandler`, a transport-neutral PSR-15 `RequestHandlerInterface` that can be mounted directly as a Symfony/Laravel controller; `ProtectedResourceMetadataMiddleware` now delegates to it (no BC break). + +0.6.0 +----- + +* Add `Builder::add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler)` for explicit registration of elements whose schema is only known at runtime. +* Add handler interfaces `ToolHandlerInterface`, `ResourceHandlerInterface`, `ResourceTemplateHandlerInterface`, `PromptHandlerInterface`, and the `ElementHandlerInterface` marker. +* [BC Break] Renamed `Mcp\Schema\Resource` to `Mcp\Schema\ResourceDefinition`. No alias. +* [BC Break] Renamed `Mcp\Capability\Registry\Loader\ArrayLoader` to `Mcp\Capability\Registry\Loader\ReflectedElementLoader`. +* [BC Break] Bump default protocol version to `2025-11-25` +* Add support for MCP Apps extension in schema and server +* Add `extensions` to `ServerCapabilities` and `ClientCapabilities` and `Builder::enableExtension()` +* Allow overriding the default name pattern for Discovery +* Add configurable session garbage collection (`gcProbability`/`gcDivisor`) +* Add optional `title` field to `ResourceDefinition` and `ResourceTemplate` for MCP spec compliance +* Add `ChainLoader` to compose multiple `LoaderInterface` implementations via explicit ordering. +* Add `RegistryInterface::unregisterTool()`, `unregisterResource()`, `unregisterResourceTemplate()`, `unregisterPrompt()` — idempotent removals. +* Add `RegistryInterface::hasTool()`, `hasResource()`, `hasResourceTemplate()`, `hasPrompt()` — by-name existence checks. +* `DiscoveryLoader` now refreshes only its own previously written entries; manual registrations (via `Builder::addTool()` etc. or runtime `$registry->registerTool()` calls) survive rediscovery, and a same-name manual registration takes precedence over discovery on collision. +* [BC Break] Removed `ElementReference::$isManual` public property and the `bool $isManual` parameter from all `*Reference` constructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order. +* [BC Break] `RegistryInterface::registerTool()`, `registerResource()`, `registerResourceTemplate()`, `registerPrompt()` lost their trailing `bool $isManual = false` parameter. Callers using positional arguments must drop the flag. +* [BC Break] Removed `RegistryInterface::clear()`, `getDiscoveryState()`, `setDiscoveryState()`. Rediscovery now goes through `DiscoveryLoader::load()` directly. +* `Registry::register*()` semantics changed to plain last-write-wins (overwrites silently) and the methods now return the stored `*Reference`. The previous "discovered registration is ignored when a manual one already exists" precedence rule still applies, but is now enforced by `DiscoveryLoader` via reference-identity tracking — and still emits a debug log when a discovery is skipped due to a conflicting registration. +* Add optional `title` parameter to `Builder::addResource()` and `Builder::addResourceTemplate()` for MCP spec compliance +* [BC Break] `Builder::addResource()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments. +* [BC Break] `Builder::addResourceTemplate()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments. +* Add `CorsMiddleware`, `DnsRebindingProtectionMiddleware`, and `ProtocolVersionMiddleware` for `StreamableHttpTransport`, composed automatically as the default stack via `StreamableHttpTransport::defaultMiddleware()` +* [BC BREAK] `StreamableHttpTransport` constructor: `$corsHeaders` parameter removed; CORS is now configured via `CorsMiddleware`. The `$middleware` parameter is nullable — `null` (or omitted) installs the default stack; `[]` disables all defaults. Default `Access-Control-Allow-Origin` is no longer set (was `*`). +* [BC Break] `ResourceDefinition::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments. +* [BC Break] `ResourceTemplate::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments. +* [BC Break] `McpResource` and `McpResourceTemplate` attribute signatures changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments. + +0.5.0 +----- + +* Add built-in authentication middleware for HTTP transport using OAuth +* Add client component for building MCP clients +* Add `Builder::setReferenceHandler()` to allow custom `ReferenceHandlerInterface` implementations (e.g. authorization decorators) +* Add elicitation enum schema types per SEP-1330: `TitledEnumSchemaDefinition`, `MultiSelectEnumSchemaDefinition`, `TitledMultiSelectEnumSchemaDefinition` +* [BC break] Make Symfony Finder component optional. Users would need to install `symfony/finder` now themselves +* Add `LenientOidcDiscoveryMetadataPolicy` for identity providers that omit `code_challenge_methods_supported` (e.g. FusionAuth, Microsoft Entra ID) +* Add OAuth 2.0 Dynamic Client Registration middleware (RFC 7591) +* Add optional `title` field to `Prompt` and `McpPrompt` for MCP spec compliance +* [BC Break] `Builder::addPrompt()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments. +* Add optional `title` field to `Tool` and `McpTool` for MCP spec compliance +* [BC Break] `Tool::__construct()` signature changed — `$title` parameter added between `$name` and `$inputSchema`. Callers using positional arguments must switch to named arguments or pass `null` for `$title`. +* [BC Break] `McpTool` attribute signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments. +* [BC Break] `Builder::addTool()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments. + +0.4.0 +----- + +* Rename `Mcp\Server\Session\Psr16StoreSession` to `Mcp\Server\Session\Psr16SessionStore` +* Add missing handlers for resource subscribe/unsubscribe and persist subscriptions via session +* Introduce `SessionManager` to encapsulate session handling (replaces `SessionFactory`) and move garbage collection logic from `Protocol`. + +0.3.0 +----- + +* Add output schema support to MCP tools +* Add validation of the input parameters given to a Tool. +* Rename `Mcp\Capability\Registry\ResourceReference::$schema` to `Mcp\Capability\Registry\ResourceReference::$resource`. +* Introduce `SchemaGeneratorInterface` and `DiscovererInterface` to allow custom schema generation and discovery implementations. +* Remove `DocBlockParser::getSummary()` method, use `DocBlockParser::getDescription()` instead. + +0.2.2 +----- + +* Throw exception when trying to inject parameter with the unsupported names `$_session` or `$_request`. +* `Throwable` objects are passed to log context instead of the exception message. + +0.2.1 +----- + +* Add `RunnerControl` for `StdioTransport` to allow break out from continuously listening for new input. +* Open range of supported Symfony versions to include v5.4 + +0.2.0 +----- + +* Make `Protocol` stateless by decouple if from `TransportInterface`. Removed `Protocol::getTransport()`. +* Change signature of `Builder::addLoaders(...$loaders)` to `Builder::addLoaders(iterable $loaders)`. +* Removed `ClientAwareInterface` in favor of injecting a `RequestContext` with argument injection. +* The `ClientGateway` cannot be injected with argument injection anymore. Use `RequestContext` instead. +* Removed `ClientAwareTrait` +* Removed `Protocol::getTransport()` +* Added parameter for `TransportInterface` to `Protocol::processInput()` + +0.1.0 +----- + +* First tagged release of package +* Support for implementing MCP server diff --git a/admin/vendor/mcp/sdk/CONTRIBUTING.md b/admin/vendor/mcp/sdk/CONTRIBUTING.md new file mode 100644 index 0000000..c02ee47 --- /dev/null +++ b/admin/vendor/mcp/sdk/CONTRIBUTING.md @@ -0,0 +1,84 @@ +# Contributing + +Thanks for considering a contribution to the PHP SDK for the Model Context Protocol. This is a +collaboration between [the PHP Foundation](https://thephp.foundation/) and the +[Symfony project](https://symfony.com/), and it follows Symfony's conventions throughout. + +## Ways to contribute + +- **Report a bug** or **propose a feature** by [opening an issue](https://github.com/modelcontextprotocol/php-sdk/issues). + Check existing issues first — many spec-driven changes are already tracked under the relevant + `2026-07-28`-style release label. +- **Send a pull request** for a fix, a new capability, or a docs improvement. +- **Improve the guides** under `docs/` — see [Documentation](#documentation) below for how they're built. +- **Help close a conformance gap** — `make conformance-tests` runs the official MCP conformance + suite against this SDK; a failing scenario there is a concrete, well-scoped contribution. + +## Development setup + +Requires PHP 8.1+. + +```bash +composer install +``` + +## Before opening a pull request + +Run the full CI suite locally: + +```bash +make ci +``` + +This runs, in order: `make cs` (PHP CS Fixer, auto-fixes style), `make phpstan` (static analysis), +and `make tests` (unit + inspector tests). All three must pass. If your change touches +protocol-observable behavior, also run: + +```bash +make conformance-tests # requires Docker +``` + +## Coding standards + +This project follows [Symfony's coding standards](https://symfony.com/doc/current/contributing/code/standards.html) +and [backward compatibility promise](https://symfony.com/doc/current/contributing/code/bc.html). In short: + +See [CLAUDE.md](CLAUDE.md) for a fuller tour of the codebase's architecture and layout. + +## Tests + +New capabilities need unit tests (`tests/Unit/`) covering the core logic, and — for anything +reachable over the wire — inspector tests (`tests/Inspector/`) for end-to-end coverage. If you're +adding a documented pattern, consider adding or updating an example under `examples/`. + +## Documentation + +The guides under `docs/` are built with [Zensical](https://zensical.org/) in `--strict` mode, +which fails the build on a broken internal link: + +```bash +make docs-guides +``` + +Links between guide pages must be relative paths that resolve within `docs/` (e.g. +`protocol-versions.md`, `../CLAUDE.md` will *not* resolve — Zensical only follows the `docs/` tree). +For anything at the repo root (`CLAUDE.md`, `ROADMAP.md`, `CHANGELOG.md`), link to it by its GitHub +URL instead, matching the pattern already used across `docs/`. The class-level API reference is +generated separately by phpDocumentor (`make docs-api`) and isn't hand-written. + +## Versioning + +The SDK follows [Semantic Versioning](https://semver.org/) — see +[SDK tier target](docs/sdk-tier.md#versioning) for what that means pre- and post-1.0, and how it +lines up with Symfony's backward compatibility promise. + +## Licensing + +New contributions are licensed under Apache License, Version 2.0. Existing code predating this +policy remains under the MIT License — see [LICENSE](LICENSE) for the details. By opening a pull +request, you agree your contribution is provided under those terms. + +## Getting help + +If something is unclear or you want early feedback on an approach before writing code, open an +issue or a draft pull request — that's the right place to ask, rather than guessing at scope. diff --git a/admin/vendor/mcp/sdk/LICENSE b/admin/vendor/mcp/sdk/LICENSE new file mode 100644 index 0000000..4a93985 --- /dev/null +++ b/admin/vendor/mcp/sdk/LICENSE @@ -0,0 +1,216 @@ +The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. + +Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License. + +No rights beyond those granted by the applicable original license are conveyed for such contributions. + +--- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on behalf + of the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +--- + +MIT License + +Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Creative Commons Attribution 4.0 International (CC-BY-4.0) + +Documentation in this project (excluding specifications) is licensed under +CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for +the full license text. diff --git a/admin/vendor/mcp/sdk/README.md b/admin/vendor/mcp/sdk/README.md new file mode 100644 index 0000000..04965f8 --- /dev/null +++ b/admin/vendor/mcp/sdk/README.md @@ -0,0 +1,139 @@ +# MCP PHP SDK + +
+ +[![Latest Version](https://img.shields.io/packagist/v/mcp/sdk.svg)](https://packagist.org/packages/mcp/sdk) +[![CI](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/pipeline.yaml/badge.svg)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/pipeline.yaml) +[![PHP Version](https://img.shields.io/packagist/php-v/mcp/sdk.svg)](https://packagist.org/packages/mcp/sdk) +[![License](https://img.shields.io/packagist/l/mcp/sdk.svg)](LICENSE) + +[![Server Conformance 2025-11-25](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/server-conformance-2025-11-25.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) +[![Client Conformance 2025-11-25](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/client-conformance-2025-11-25.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) +[![Server Conformance 2026-07-28](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/server-conformance-2026-07-28.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) +[![Client Conformance 2026-07-28](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/client-conformance-2026-07-28.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) + +
+ +The official PHP SDK for the Model Context Protocol (MCP). It provides a framework-agnostic API for implementing MCP +servers and clients in PHP — tools, resources, prompts, STDIO and HTTP transports, sessions, authorization, and both +protocol eras (the `initialize` handshake and the stateless `2026-07-28` revision). + +This project represents a collaboration between [the PHP Foundation](https://thephp.foundation/) and the [Symfony project](https://symfony.com/). It adopts +development practices and standards from the Symfony project, including [Coding Standards](https://symfony.com/doc/current/contributing/code/standards.html) and the +[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + +Until the first major release, this SDK is considered [experimental](https://symfony.com/doc/current/contributing/code/experimental.html), please see the [roadmap](./ROADMAP.md) for +planned next steps and features. + +## Installation + +```bash +composer require mcp/sdk +``` + +## Build a server + +A server is a plain PHP class plus three lines of wiring: + +```php +use Mcp\Capability\Attribute\McpResource; +use Mcp\Capability\Attribute\McpTool; +use Mcp\Server; +use Mcp\Server\Transport\StdioTransport; + +class Calculator +{ + /** + * Adds two numbers. + */ + #[McpTool] + public function add(int $a, int $b): int + { + return $a + $b; + } + + #[McpResource(uri: 'config://calculator/settings')] + public function settings(): array + { + return ['precision' => 2]; + } +} + +exit(Server::builder() + ->setServerInfo('Calculator', '1.0.0') + ->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor']) + ->build() + ->run(new StdioTransport())); +``` + +The walkthrough in [First server](docs/get-started/first-server.md) explains each piece, and +[Try it with the Inspector](docs/get-started/inspector.md) shows it running. + +## Build a client + +```php +use Mcp\Client; +use Mcp\Client\Transport\StdioTransport; + +$client = Client::builder() + ->setClientInfo('My Application', '1.0.0') + ->build(); + +$client->connect(new StdioTransport(command: 'php', args: ['/path/to/server.php'])); + +$tools = $client->listTools(); +$result = $client->callTool('add', ['a' => 5, 'b' => 3]); + +$client->disconnect(); +``` + +See [Connecting to a server](docs/client/connecting.md) for transports, timeouts, and the +handlers that answer server-initiated requests. + +## Documentation + +The full documentation is published at **[php.sdk.modelcontextprotocol.io](https://php.sdk.modelcontextprotocol.io/)**. + +- **[Get started](docs/get-started/index.md)** — Install the SDK and build your first server +- **[Servers](docs/servers/index.md)** — Tools, resources, resource templates, prompts, and how to register them +- **[Inside your handler](docs/handlers/index.md)** — Talking back to the client, logging, and asking for input +- **[Running your server](docs/run/index.md)** — Server builder, STDIO and HTTP transports, framework integration, sessions, authorization +- **[Clients](docs/client/index.md)** — Client SDK for connecting to and communicating with MCP servers +- **[Protocol versions](docs/protocol-versions.md)** — The two protocol eras, and what revision `2026-07-28` changed +- **[Advanced](docs/advanced/index.md)** — Events, protocol extensions (including MCP Apps), and custom message handlers +- **[Examples](docs/examples.md)** — Runnable server and client examples +- **[API Reference](https://php.sdk.modelcontextprotocol.io/api/)** — Generated class reference + +## External Resources + +- **[Model Context Protocol Documentation](https://modelcontextprotocol.io)** — Official MCP documentation +- **[Model Context Protocol Specification](https://spec.modelcontextprotocol.io)** — Protocol specification +- **[Officially Supported Servers](https://github.com/modelcontextprotocol/servers)** — Reference server implementations + +## PHP Libraries Using the MCP SDK + +- [api-platform/mcp](https://github.com/api-platform/mcp) — MCP integration for API Platform +- [bnomei/kirby-mcp](https://github.com/bnomei/kirby-mcp) — MCP server for the Kirby CMS +- [drupal/mcp_server](https://www.drupal.org/project/mcp_server) — MCP server for Drupal exposing configuration and entities as MCP elements +- [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) — CakePHP plugin exposing application functionality over MCP +- [nette/mcp-inspector](https://github.com/nette/mcp-inspector) — MCP server for introspecting Nette applications +- [symfony/ai-mate](https://github.com/symfony/ai-mate) — AI development assistant MCP server for Symfony projects +- [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) — Symfony integration bundle + +Building something on top of the SDK? Open a pull request to add it to this list. + +## Contributing + +We are passionate about supporting contributors of all levels of experience and would love to see you get involved in +the project. Start by [reporting issues](https://github.com/modelcontextprotocol/php-sdk/issues) or +[sending pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls). See +[CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and what to run before +opening a PR. + +## Credits + +The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by [Kyrian Obikwelu](https://github.com/CodeWithKyrian), and the [Symfony AI initiative](https://github.com/symfony/ai). We are grateful for the work done by both projects and their contributors, which created a solid foundation for this SDK. + +## License + +This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under the MIT License — see the [LICENSE](LICENSE) file for details. diff --git a/admin/vendor/mcp/sdk/ROADMAP.md b/admin/vendor/mcp/sdk/ROADMAP.md new file mode 100644 index 0000000..5dfb9ce --- /dev/null +++ b/admin/vendor/mcp/sdk/ROADMAP.md @@ -0,0 +1,21 @@ +# Roadmap + +This roadmap is a living document that outlines the planned features and improvements for our project. + +## Goals for the First Major Release + +- **Server** +- [x] Implement full support for elicitations +- [ ] Implement OAuth2 authentication for server +- **Client** +- [x] Implement client-side support +- [x] Implement client examples and documentation +- [ ] Implement OAuth2 authentication for client +- **Schema** +- [ ] Implement schema generation based on TS or JSON Schema +- [ ] Achieve full spec conformance +- **Extensions** +- [ ] Implement the Tasks extension +- **Architecture** +- [ ] Split the SDK into multiple packages + diff --git a/admin/vendor/mcp/sdk/SECURITY.md b/admin/vendor/mcp/sdk/SECURITY.md new file mode 100644 index 0000000..5029242 --- /dev/null +++ b/admin/vendor/mcp/sdk/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +Thank you for helping keep the Model Context Protocol and its ecosystem secure. + +## Reporting Security Issues + +If you discover a security vulnerability in this repository, please report it through +the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) +for this repository. + +Please **do not** report security vulnerabilities through public GitHub issues, discussions, +or pull requests. + +## What to Include + +To help us triage and respond quickly, please include: + +- A description of the vulnerability +- Steps to reproduce the issue +- The potential impact +- Any suggested fixes (optional) diff --git a/admin/vendor/mcp/sdk/adr/0001-oauth-authorization-server-out-of-scope.md b/admin/vendor/mcp/sdk/adr/0001-oauth-authorization-server-out-of-scope.md new file mode 100644 index 0000000..ebb5947 --- /dev/null +++ b/admin/vendor/mcp/sdk/adr/0001-oauth-authorization-server-out-of-scope.md @@ -0,0 +1,97 @@ +# 0001 — The MCP server is an OAuth Resource Server, not an Authorization Server + +- Status: Accepted +- Date: 2026-06-15 + +## Context + +OAuth 2.1 defines three distinct roles: + +| Role | What it does | Status in this SDK | Scope | +|------|--------------|--------------------|-------| +| Resource Server | Validates incoming bearer tokens, serves Protected Resource Metadata (RFC 9728), emits `WWW-Authenticate` | Shipped (`AuthorizationMiddleware`, `JwtTokenValidator`, `ProtectedResourceMetadata`) | **IN scope** | +| Delegation / proxy to an upstream AS | Forwards `/authorize` and `/token` to your existing IdP | Shipped (`OAuthProxyMiddleware`) | **IN scope — delegation ONLY** | +| Authorization Server / Identity Provider (IdP) | Mints its own tokens, registers clients, runs login and consent | Absent | **OUT of scope** | + +The SDK repeatedly receives pull requests that move it toward becoming a full OAuth 2.1 +**authorization server** — asking the MCP server to mint its own access and refresh tokens, +register clients, and run login and consent flows, that is, to become an Identity Provider. +The most explicit example is [#373](https://github.com/modelcontextprotocol/php-sdk/pull/373) +("[Server] Add native OAuth 2.1 authorization server", ~3,400 lines across 50+ files), but the +direction has crept in incrementally rather than in one PR. + +A contributing factor is that the SDK already exposes the *authorization-server endpoints in +proxy form*, and that surface has grown: +[#221](https://github.com/modelcontextprotocol/php-sdk/pull/221) added the OAuth resource-server +middleware, and [#269](https://github.com/modelcontextprotocol/php-sdk/pull/269) added Dynamic +Client Registration (RFC 7591). Today `OAuthProxyMiddleware` answers `/authorize`, `/token`, and +`/.well-known/oauth-authorization-server`, and DCR endpoints exist. The shape of that surface +invites contributors to "finish the job" by backing those endpoints with a real token issuer. +It is not scaffolding to be completed — it is a delegating proxy, and that is the whole of its +intent. + +## Decision + +**The MCP server is an OAuth 2.1 Resource Server that MAY delegate to an upstream +authorization server. It will NOT issue tokens or act as an Identity Provider.** + +Concretely: + +- The SDK validates bearer tokens, serves RFC 9728 Protected Resource Metadata, and emits + `WWW-Authenticate` challenges. This is the Resource Server role and is fully supported. +- The SDK MAY delegate `/authorize` and `/token` to an upstream authorization server via + `OAuthProxyMiddleware`. This is delegation only: the middleware redirects the browser to + the upstream `/authorize` endpoint and proxies `/token` requests to the upstream token + endpoint. It never mints, signs, stores, or rotates tokens of its own. +- The SDK will NOT implement an authorization server: no token issuance, no token signing or + key management, no login UI, no consent UI, no authorization-code or refresh-token storage, + no first-party Dynamic Client Registration acting as an issuer. + +Pull requests that add authorization-server / IdP behavior are declined by reference to this +ADR. + +## Rationale + +- **Security liability.** Issuing tokens means owning signing-key generation, storage, and + rotation; authorization-code and refresh-token persistence; refresh-token rotation and + replay detection; and consent. A defect in any of these is a credential-issuance + vulnerability affecting every consumer of the SDK. This is precisely the surface an MCP SDK + should not own. +- **RFC footprint.** A correct authorization server must implement and keep current with + RFC 6749 (OAuth 2.0), RFC 7591 (Dynamic Client Registration), RFC 8414 (Authorization + Server Metadata), PKCE (RFC 7636), and refresh-token rotation guidance, among others. That + is an open-ended maintenance and conformance burden far outside the SDK's purpose. +- **Mature implementations already exist.** Token issuance is a solved problem. + `league/oauth2-server` provides it as a PHP library, and every production IdP — Keycloak, + Auth0, Microsoft Entra ID, Okta — provides it as a service. Re-implementing it inside an + MCP SDK adds risk without adding value. + +## Boundary statement + +`OAuthProxyMiddleware` **delegates** to an upstream IdP. It is **not** authorization-server +scaffolding to be completed. Backing its `/authorize` and `/token` endpoints with a +first-party token issuer is out of scope and will be declined. + +## Consequences + +- The supported authorization architecture is: an external authorization server (your IdP or + `league/oauth2-server` running in your own application) issues tokens; the MCP server + validates them as a Resource Server and optionally proxies the OAuth flow to that upstream. +- Contributors get a single, citable ruling for why authorization-server PRs are declined, + reducing repeated large-PR churn. +- Resource Server and proxy/delegation features remain welcome and supported. + +## Alternatives / what to do instead + +If you need an authorization server (token issuance, client registration, login, consent), do +**not** add it to this SDK. Instead: + +- **Front the MCP server with an existing IdP** — Keycloak, Auth0, Microsoft Entra ID, or + Okta. Point `JwtTokenValidator` and `ProtectedResourceMetadata` at that issuer, and + optionally use `OAuthProxyMiddleware` to delegate `/authorize` and `/token` to it. +- **Run `league/oauth2-server` in your own application**, behind the SDK's existing proxy and + validator seams. The MCP server validates the tokens it issues; it does not issue them + itself. + +See [`../docs/run/authorization.md`](../docs/run/authorization.md) for the supported Resource Server +and delegation setup. diff --git a/admin/vendor/mcp/sdk/adr/README.md b/admin/vendor/mcp/sdk/adr/README.md new file mode 100644 index 0000000..d8ab0de --- /dev/null +++ b/admin/vendor/mcp/sdk/adr/README.md @@ -0,0 +1,10 @@ +# Architecture Decision Records + +This directory holds Architecture Decision Records (ADRs): short documents that capture a +significant architectural decision, the context that led to it, and its consequences. They +exist so a decision can be cited once and referenced thereafter — for example, when declining +a pull request that conflicts with an accepted decision. + +## Records + +- [0001 — The MCP server is an OAuth Resource Server, not an Authorization Server](0001-oauth-authorization-server-out-of-scope.md) diff --git a/admin/vendor/mcp/sdk/composer.json b/admin/vendor/mcp/sdk/composer.json new file mode 100644 index 0000000..5c572b0 --- /dev/null +++ b/admin/vendor/mcp/sdk/composer.json @@ -0,0 +1,97 @@ +{ + "name": "mcp/sdk", + "description": "Model Context Protocol SDK for Client and Server applications in PHP", + "license": "Apache-2.0", + "type": "library", + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Kyrian Obikwelu", + "email": "koshnawaza@gmail.com" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "require": { + "php": "^8.1", + "ext-fileinfo": "*", + "opis/json-schema": "^2.4", + "php-http/discovery": "^1.20", + "phpdocumentor/reflection-docblock": "^5.6 || ^6.0", + "psr/clock": "^1.0", + "psr/container": "^1.0 || ^2.0", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0" + }, + "suggest": { + "symfony/finder": "Required for file-based discovery." + }, + "require-dev": { + "ext-openssl": "*", + "composer/semver": "^3.0", + "firebase/php-jwt": "^6.10 || ^7.0", + "laminas/laminas-httphandlerrunner": "^2.12", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phar-io/composer-distributor": "^1.0.2", + "php-cs-fixer/shim": "^3.91", + "phpdocumentor/shim": "^3", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5", + "psr/simple-cache": "^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/console": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/http-client": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/process": "^5.4 || ^6.4 || ^7.3 || ^8.0" + }, + "autoload": { + "psr-4": { + "Mcp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Mcp\\Example\\Server\\CachedDiscovery\\": "examples/server/cached-discovery/", + "Mcp\\Example\\Server\\ClientCommunication\\": "examples/server/client-communication/", + "Mcp\\Example\\Server\\ClientLogging\\": "examples/server/client-logging/", + "Mcp\\Example\\Server\\CombinedRegistration\\": "examples/server/combined-registration/", + "Mcp\\Example\\Server\\Elicitation\\": "examples/server/elicitation/", + "Mcp\\Example\\Server\\ComplexToolSchema\\": "examples/server/complex-tool-schema/", + "Mcp\\Example\\Server\\Conformance\\": "examples/server/conformance/", + "Mcp\\Example\\Server\\CustomDependencies\\": "examples/server/custom-dependencies/", + "Mcp\\Example\\Server\\CustomMethodHandlers\\": "examples/server/custom-method-handlers/", + "Mcp\\Example\\Server\\DiscoveryCalculator\\": "examples/server/discovery-calculator/", + "Mcp\\Example\\Server\\DiscoveryUserProfile\\": "examples/server/discovery-userprofile/", + "Mcp\\Example\\Server\\EnvVariables\\": "examples/server/env-variables/", + "Mcp\\Example\\Server\\ExplicitRegistration\\": "examples/server/explicit-registration/", + "Mcp\\Example\\Server\\McpApps\\": "examples/server/mcp-apps/", + "Mcp\\Example\\Server\\OAuthKeycloak\\": "examples/server/oauth-keycloak/", + "Mcp\\Example\\Server\\OAuthMicrosoft\\": "examples/server/oauth-microsoft/", + "Mcp\\Example\\Server\\SchemaShowcase\\": "examples/server/schema-showcase/", + "Mcp\\Tests\\": "tests/" + }, + "classmap": [ + "tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc" + ] + }, + "config": { + "allow-plugins": { + "php-http/discovery": false, + "phpdocumentor/shim": true + }, + "sort-packages": true + } +} diff --git a/admin/vendor/mcp/sdk/phpdoc.dist.xml b/admin/vendor/mcp/sdk/phpdoc.dist.xml new file mode 100644 index 0000000..7c705bd --- /dev/null +++ b/admin/vendor/mcp/sdk/phpdoc.dist.xml @@ -0,0 +1,41 @@ + + + MCP PHP SDK + + + .phpdoc/build/api + + + + + src + + + vendor/**/* + tests/**/* + + + phpstan-type + phpstan-type-import + template + template-covariant + template-extends + template-implements + extends + implements + + + + + + + diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/CompletionProvider.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/CompletionProvider.php new file mode 100644 index 0000000..9e8dc80 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/CompletionProvider.php @@ -0,0 +1,38 @@ + + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class CompletionProvider +{ + /** + * @param class-string|ProviderInterface|null $provider if a class-string, it will be resolved + * from the container at the point of use + * @param ?array $values a list of values to use for completion + */ + public function __construct( + public ?string $providerClass = null, + public string|ProviderInterface|null $provider = null, + public ?array $values = null, + public ?string $enum = null, + ) { + if (1 !== \count(array_filter([$provider, $values, $enum]))) { + throw new InvalidArgumentException('Only one of provider, values, or enum can be set.'); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/McpPrompt.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpPrompt.php new file mode 100644 index 0000000..cd47fe2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpPrompt.php @@ -0,0 +1,40 @@ + + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class McpPrompt +{ + /** + * @param ?string $name overrides the prompt name (defaults to method name) + * @param ?string $title Optional human-readable title for display in UI + * @param ?string $description Optional description of the prompt. Defaults to method DocBlock summary. + * @param ?Icon[] $icons Optional list of icon URLs representing the prompt + * @param ?array $meta Optional metadata + */ + public function __construct( + public ?string $name = null, + public ?string $title = null, + public ?string $description = null, + public ?array $icons = null, + public ?array $meta = null, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResource.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResource.php new file mode 100644 index 0000000..0f51657 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResource.php @@ -0,0 +1,49 @@ + + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class McpResource +{ + /** + * @param string $uri the specific URI identifying this resource instance + * @param ?string $name a short identifier for this resource; defaults to the method name + * @param ?string $title optional human-readable title for display in UI + * @param ?string $description optional description; defaults to class DocBlock summary + * @param ?string $mimeType the MIME type, if known and constant for this resource + * @param ?int $size the size in bytes, if known and constant + * @param ?Annotations $annotations optional annotations describing the resource + * @param ?Icon[] $icons optional icons representing the resource + * @param ?array $meta optional metadata + */ + public function __construct( + public string $uri, + public ?string $name = null, + public ?string $title = null, + public ?string $description = null, + public ?string $mimeType = null, + public ?int $size = null, + public ?Annotations $annotations = null, + public ?array $icons = null, + public ?array $meta = null, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResourceTemplate.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResourceTemplate.php new file mode 100644 index 0000000..6a2044d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpResourceTemplate.php @@ -0,0 +1,44 @@ + + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class McpResourceTemplate +{ + /** + * @param string $uriTemplate the URI template string (RFC 6570) + * @param ?string $name a short identifier for the template type; defaults to the method name + * @param ?string $title optional human-readable title for display in UI + * @param ?string $description optional description; defaults to class DocBlock summary + * @param ?string $mimeType optional default MIME type for matching resources + * @param ?Annotations $annotations optional annotations describing the resource template + * @param ?array $meta optional metadata + */ + public function __construct( + public string $uriTemplate, + public ?string $name = null, + public ?string $title = null, + public ?string $description = null, + public ?string $mimeType = null, + public ?Annotations $annotations = null, + public ?array $meta = null, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/McpTool.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpTool.php new file mode 100644 index 0000000..1c71e2c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/McpTool.php @@ -0,0 +1,42 @@ + + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class McpTool +{ + /** + * @param string|null $name The name of the tool (defaults to the method name) + * @param string|null $title Optional human-readable title for display in UI + * @param string|null $description The description of the tool (defaults to the DocBlock/inferred) + * @param ToolAnnotations|null $annotations Optional annotations describing tool behavior + * @param ?Icon[] $icons Optional list of icon URLs representing the tool + * @param ?array $meta Optional metadata + * @param array $outputSchema Optional JSON Schema object for defining the expected output structure + */ + public function __construct( + public ?string $name = null, + public ?string $title = null, + public ?string $description = null, + public ?ToolAnnotations $annotations = null, + public ?array $icons = null, + public ?array $meta = null, + public ?array $outputSchema = null, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Attribute/Schema.php b/admin/vendor/mcp/sdk/src/Capability/Attribute/Schema.php new file mode 100644 index 0000000..80ec4b5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Attribute/Schema.php @@ -0,0 +1,258 @@ +, + * type?: string, + * description?: string, + * enum?: array, + * format?: string, + * minLength?: int, + * maxLength?: int, + * pattern?: string, + * minimum?: int, + * maximum?: int, + * exclusiveMinimum?: int, + * exclusiveMaximum?: int, + * multipleOf?: int|float, + * items?: array, + * minItems?: int, + * maxItems?: int, + * uniqueItems?: bool, + * properties?: array, + * required?: array, + * additionalProperties?: bool|array, + * } + * + * @author Kyrian Obikwelu + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)] +class Schema +{ + /** + * The complete JSON schema array. + * If provided, it takes precedence over individual properties like $type, $properties, etc. + * + * @var ?array + */ + public ?array $definition = null; + + /** + * Alternatively, provide individual top-level schema keywords. + * These are used if $definition is null. + */ + public ?string $type = null; + public ?string $description = null; + public mixed $default = null; + /** + * @var ?array + */ + public ?array $enum = null; // list of allowed values + public ?string $format = null; // e.g., 'email', 'date-time' + + // Constraints for string + public ?int $minLength = null; + public ?int $maxLength = null; + public ?string $pattern = null; + + // Constraints for number/integer + public int|float|null $minimum = null; + public int|float|null $maximum = null; + public ?bool $exclusiveMinimum = null; + public ?bool $exclusiveMaximum = null; + public int|float|null $multipleOf = null; + + // Constraints for array + /** + * @var ?array + */ + public ?array $items = null; // JSON schema for array items + public ?int $minItems = null; + public ?int $maxItems = null; + public ?bool $uniqueItems = null; + + // Constraints for object (primarily used when Schema is on a method or an object-typed parameter) + /** + * @var ?array + */ + public ?array $properties = null; // [propertyName => [schema array], ...] + /** + * @var ?array + */ + public ?array $required = null; // [propertyName, ...] + /** + * @var bool|array|null + */ + public bool|array|null $additionalProperties = null; // true, false, or a schema array + + /** + * @param ?array $definition A complete JSON schema array. If provided, other parameters are ignored. + * @param ?string $type the JSON schema type + * @param ?string $description description of the element + * @param ?array $enum allowed enum values + * @param ?string $format String format (e.g., 'date-time', 'email'). + * @param ?int $minLength minimum length for strings + * @param ?int $maxLength maximum length for strings + * @param ?string $pattern regex pattern for strings + * @param int|float|null $minimum minimum value for numbers/integers + * @param int|float|null $maximum maximum value for numbers/integers + * @param ?bool $exclusiveMinimum exclusive minimum + * @param ?bool $exclusiveMaximum exclusive maximum + * @param int|float|null $multipleOf must be a multiple of this value + * @param ?array $items JSON Schema for items if type is 'array' + * @param ?int $minItems minimum items for an array + * @param ?int $maxItems maximum items for an array + * @param ?bool $uniqueItems whether array items must be unique + * @param ?array $properties Property definitions if type is 'object'. [name => schema_array]. + * @param ?array $required list of required properties for an object + * @param bool|array|null $additionalProperties policy for additional properties in an object + */ + public function __construct( + ?array $definition = null, + ?string $type = null, + ?string $description = null, + ?array $enum = null, + ?string $format = null, + ?int $minLength = null, + ?int $maxLength = null, + ?string $pattern = null, + int|float|null $minimum = null, + int|float|null $maximum = null, + ?bool $exclusiveMinimum = null, + ?bool $exclusiveMaximum = null, + int|float|null $multipleOf = null, + ?array $items = null, + ?int $minItems = null, + ?int $maxItems = null, + ?bool $uniqueItems = null, + ?array $properties = null, + ?array $required = null, + bool|array|null $additionalProperties = null, + ) { + if (null !== $definition) { + $this->definition = $definition; + } else { + $this->type = $type; + $this->description = $description; + $this->enum = $enum; + $this->format = $format; + $this->minLength = $minLength; + $this->maxLength = $maxLength; + $this->pattern = $pattern; + $this->minimum = $minimum; + $this->maximum = $maximum; + $this->exclusiveMinimum = $exclusiveMinimum; + $this->exclusiveMaximum = $exclusiveMaximum; + $this->multipleOf = $multipleOf; + $this->items = $items; + $this->minItems = $minItems; + $this->maxItems = $maxItems; + $this->uniqueItems = $uniqueItems; + $this->properties = $properties; + $this->required = $required; + $this->additionalProperties = $additionalProperties; + } + } + + /** + * Converts the attribute's definition to a JSON schema array. + * + * @return SchemaAttributeData + */ + public function toArray(): array + { + if (null !== $this->definition) { + return [ + 'definition' => $this->definition, + ]; + } + + $schema = []; + if (null !== $this->type) { + $schema['type'] = $this->type; + } + if (null !== $this->description) { + $schema['description'] = $this->description; + } + if (null !== $this->enum) { + $schema['enum'] = $this->enum; + } + if (null !== $this->format) { + $schema['format'] = $this->format; + } + + // String + if (null !== $this->minLength) { + $schema['minLength'] = $this->minLength; + } + if (null !== $this->maxLength) { + $schema['maxLength'] = $this->maxLength; + } + if (null !== $this->pattern) { + $schema['pattern'] = $this->pattern; + } + + // Numeric + if (null !== $this->minimum) { + $schema['minimum'] = $this->minimum; + } + if (null !== $this->maximum) { + $schema['maximum'] = $this->maximum; + } + if (null !== $this->exclusiveMinimum) { + $schema['exclusiveMinimum'] = $this->exclusiveMinimum; + } + if (null !== $this->exclusiveMaximum) { + $schema['exclusiveMaximum'] = $this->exclusiveMaximum; + } + if (null !== $this->multipleOf) { + $schema['multipleOf'] = $this->multipleOf; + } + + // Array + if (null !== $this->items) { + $schema['items'] = $this->items; + } + if (null !== $this->minItems) { + $schema['minItems'] = $this->minItems; + } + if (null !== $this->maxItems) { + $schema['maxItems'] = $this->maxItems; + } + if (null !== $this->uniqueItems) { + $schema['uniqueItems'] = $this->uniqueItems; + } + + // Object + if (null !== $this->properties) { + $schema['properties'] = $this->properties; + } + if (null !== $this->required) { + $schema['required'] = $this->required; + } + if (null !== $this->additionalProperties) { + $schema['additionalProperties'] = $this->additionalProperties; + } + + return $schema; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Completion/EnumCompletionProvider.php b/admin/vendor/mcp/sdk/src/Capability/Completion/EnumCompletionProvider.php new file mode 100644 index 0000000..7864e4e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Completion/EnumCompletionProvider.php @@ -0,0 +1,52 @@ + + */ +class EnumCompletionProvider implements ProviderInterface +{ + /** + * @var string[] + */ + private array $values; + + /** + * @param class-string $enumClass + */ + public function __construct(string $enumClass) + { + if (!enum_exists($enumClass)) { + throw new InvalidArgumentException(\sprintf('Class "%s" is not an enum.', $enumClass)); + } + + $this->values = array_map( + static fn ($case) => isset($case->value) && \is_string($case->value) ? $case->value : $case->name, + $enumClass::cases() + ); + } + + public function getCompletions(string $currentValue): array + { + if (empty($currentValue)) { + return $this->values; + } + + return array_values(array_filter( + $this->values, + static fn (string $value) => str_starts_with($value, $currentValue) + )); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Completion/ListCompletionProvider.php b/admin/vendor/mcp/sdk/src/Capability/Completion/ListCompletionProvider.php new file mode 100644 index 0000000..5d48f4b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Completion/ListCompletionProvider.php @@ -0,0 +1,38 @@ + + */ +class ListCompletionProvider implements ProviderInterface +{ + /** + * @param string[] $values + */ + public function __construct( + private array $values, + ) { + } + + public function getCompletions(string $currentValue): array + { + if (empty($currentValue)) { + return $this->values; + } + + return array_values(array_filter( + $this->values, + static fn (string $value) => str_starts_with($value, $currentValue) + )); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Completion/ProviderInterface.php b/admin/vendor/mcp/sdk/src/Capability/Completion/ProviderInterface.php new file mode 100644 index 0000000..84f3f23 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Completion/ProviderInterface.php @@ -0,0 +1,27 @@ + + */ +interface ProviderInterface +{ + /** + * Get completions for a given current value. + * + * @param string $currentValue the current value to get completions for + * + * @return string[] the completions + */ + public function getCompletions(string $currentValue): array; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/CachedDiscoverer.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/CachedDiscoverer.php new file mode 100644 index 0000000..d5ed852 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/CachedDiscoverer.php @@ -0,0 +1,100 @@ + + */ +final class CachedDiscoverer implements DiscovererInterface +{ + private const CACHE_PREFIX = 'mcp_discovery_'; + + public function __construct( + private readonly DiscovererInterface $discoverer, + private readonly CacheInterface $cache, + private readonly LoggerInterface $logger, + ) { + } + + /** + * Discover MCP elements in the specified directories with caching. + * + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() + */ + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = self::DEFAULT_NAME_PATERNS): DiscoveryState + { + $cacheKey = $this->generateCacheKey($basePath, $directories, $excludeDirs); + + $cachedResult = $this->cache->get($cacheKey); + if (null !== $cachedResult) { + $this->logger->debug('Using cached discovery results', [ + 'cache_key' => $cacheKey, + 'base_path' => $basePath, + 'directories' => $directories, + ]); + + return $cachedResult; + } + + $this->logger->debug('Cache miss, performing fresh discovery', [ + 'cache_key' => $cacheKey, + 'base_path' => $basePath, + 'directories' => $directories, + ]); + + $discoveryState = $this->discoverer->discover($basePath, $directories, $excludeDirs, $namePatterns); + + $this->cache->set($cacheKey, $discoveryState); + + return $discoveryState; + } + + /** + * Generate a cache key based on discovery parameters. + * + * @param array $directories + * @param array $excludeDirs + */ + private function generateCacheKey(string $basePath, array $directories, array $excludeDirs): string + { + $keyData = [ + 'base_path' => $basePath, + 'directories' => $directories, + 'exclude_dirs' => $excludeDirs, + ]; + + return self::CACHE_PREFIX.md5(serialize($keyData)); + } + + /** + * Clear the discovery cache. + * Useful for development or when files change. + */ + public function clearCache(): void + { + $this->cache->clear(); + $this->logger->info('Discovery cache cleared'); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/Discoverer.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/Discoverer.php new file mode 100644 index 0000000..5b7e476 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/Discoverer.php @@ -0,0 +1,451 @@ + + */ +final class Discoverer implements DiscovererInterface +{ + public function __construct( + private readonly LoggerInterface $logger = new NullLogger(), + private ?DocBlockParser $docBlockParser = null, + private ?SchemaGeneratorInterface $schemaGenerator = null, + ) { + if (!class_exists(Finder::class)) { + throw new RuntimeException('File-based discovery requires symfony/finder. Run: composer require symfony/finder'); + } + + $this->docBlockParser = $docBlockParser ?? new DocBlockParser(logger: $this->logger); + $this->schemaGenerator = $schemaGenerator ?? new SchemaGenerator($this->docBlockParser); + } + + /** + * Discover MCP elements in the specified directories and return the discovery state. + * + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() + */ + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = self::DEFAULT_NAME_PATERNS): DiscoveryState + { + $startTime = microtime(true); + $discoveredCount = [ + 'tools' => 0, + 'resources' => 0, + 'prompts' => 0, + 'resourceTemplates' => 0, + ]; + + $namePatterns = !empty($namePatterns) ? $namePatterns : self::DEFAULT_NAME_PATERNS; + + $tools = []; + $resources = []; + $prompts = []; + $resourceTemplates = []; + + try { + $finder = new Finder(); + $absolutePaths = []; + foreach ($directories as $dir) { + $path = rtrim($basePath, '/').'/'.ltrim($dir, '/'); + if (is_dir($path)) { + $absolutePaths[] = $path; + } + } + + if (empty($absolutePaths)) { + $this->logger->warning('No valid discovery directories found to scan.', [ + 'configured_paths' => $directories, + 'base_path' => $basePath, + ]); + + return new DiscoveryState(); + } + + $finder->files() + ->in($absolutePaths) + ->exclude($excludeDirs) + ->name($namePatterns); + + foreach ($finder as $file) { + $this->processFile($file, $discoveredCount, $tools, $resources, $prompts, $resourceTemplates); + } + } catch (\Throwable $e) { + $this->logger->error('Error during file finding process for MCP discovery'.json_encode($e->getTrace(), \JSON_PRETTY_PRINT), [ + 'exception' => $e, + ]); + } + + $duration = microtime(true) - $startTime; + $this->logger->info('Attribute discovery finished.', [ + 'duration_sec' => round($duration, 3), + 'tools' => $discoveredCount['tools'], + 'resources' => $discoveredCount['resources'], + 'prompts' => $discoveredCount['prompts'], + 'resourceTemplates' => $discoveredCount['resourceTemplates'], + ]); + + return new DiscoveryState($tools, $resources, $prompts, $resourceTemplates); + } + + /** + * Process a single PHP file for MCP elements on classes or methods. + * + * @param DiscoveredCount $discoveredCount + * @param array $tools + * @param array $resources + * @param array $prompts + * @param array $resourceTemplates + */ + private function processFile(SplFileInfo $file, array &$discoveredCount, array &$tools, array &$resources, array &$prompts, array &$resourceTemplates): void + { + $className = $this->getClassFromFile($file); + if (!$className) { + $this->logger->warning('No valid class found in file', ['file' => $file->getPathname()]); + + return; + } + + try { + $reflectionClass = new \ReflectionClass($className); + + if ($reflectionClass->isAbstract() || $reflectionClass->isInterface() || $reflectionClass->isTrait() || $reflectionClass->isEnum()) { + return; + } + + $processedViaClassAttribute = false; + if ($reflectionClass->hasMethod('__invoke')) { + $invokeMethod = $reflectionClass->getMethod('__invoke'); + if ($invokeMethod->isPublic() && !$invokeMethod->isStatic()) { + $attributeTypes = [McpTool::class, McpResource::class, McpPrompt::class, McpResourceTemplate::class]; + foreach ($attributeTypes as $attributeType) { + $classAttribute = $reflectionClass->getAttributes($attributeType, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null; + if ($classAttribute) { + $this->processMethod($invokeMethod, $discoveredCount, $classAttribute, $tools, $resources, $prompts, $resourceTemplates); + $processedViaClassAttribute = true; + break; + } + } + } + } + + if (!$processedViaClassAttribute) { + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if ( + $method->getDeclaringClass()->getName() !== $reflectionClass->getName() + || $method->isStatic() || $method->isAbstract() || $method->isConstructor() || $method->isDestructor() || '__invoke' === $method->getName() + ) { + continue; + } + $attributeTypes = [McpTool::class, McpResource::class, McpPrompt::class, McpResourceTemplate::class]; + foreach ($attributeTypes as $attributeType) { + $methodAttribute = $method->getAttributes($attributeType, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null; + if ($methodAttribute) { + $this->processMethod($method, $discoveredCount, $methodAttribute, $tools, $resources, $prompts, $resourceTemplates); + break; + } + } + } + } + } catch (\ReflectionException $e) { + $this->logger->error('Reflection error processing file for MCP discovery', ['file' => $file->getPathname(), 'class' => $className, 'exception' => $e]); + } catch (\Throwable $e) { + $this->logger->error('Unexpected error processing file for MCP discovery', [ + 'file' => $file->getPathname(), + 'class' => $className, + 'exception' => $e, + ]); + } + } + + /** + * Process a method with a given MCP attribute instance. + * Can be called for regular methods or the __invoke method of an invokable class. + * + * @param \ReflectionMethod $method The target method (e.g., regular method or __invoke). + * @param DiscoveredCount $discoveredCount pass by reference to update counts + * @param \ReflectionAttribute $attribute the ReflectionAttribute instance found (on method or class) + * @param array $tools + * @param array $resources + * @param array $prompts + * @param array $resourceTemplates + */ + private function processMethod(\ReflectionMethod $method, array &$discoveredCount, \ReflectionAttribute $attribute, array &$tools, array &$resources, array &$prompts, array &$resourceTemplates): void + { + $className = $method->getDeclaringClass()->getName(); + $methodName = $method->getName(); + $attributeClassName = $attribute->getName(); + + try { + $instance = $attribute->newInstance(); + + switch ($attributeClassName) { + case McpTool::class: + $name = ElementMetadataResolver::resolveName($method, $instance->name); + $description = ElementMetadataResolver::resolveDescription($method, $instance->description, $this->docBlockParser); + $inputSchema = $this->schemaGenerator->generate($method); + $outputSchema = $this->schemaGenerator->generateOutputSchema($method); + $tool = new Tool( + name: $name, + title: $instance->title, + inputSchema: $inputSchema, + description: $description, + annotations: $instance->annotations, + icons: $instance->icons, + meta: $instance->meta, + outputSchema: $outputSchema, + ); + $tools[$name] = new ToolReference($tool, [$className, $methodName]); + ++$discoveredCount['tools']; + break; + + case McpResource::class: + $name = ElementMetadataResolver::resolveName($method, $instance->name); + $description = ElementMetadataResolver::resolveDescription($method, $instance->description, $this->docBlockParser); + $resource = new ResourceDefinition( + $instance->uri, + $name, + $instance->title, + $description, + $instance->mimeType, + $instance->annotations, + $instance->size, + $instance->icons, + $instance->meta, + ); + $resources[$instance->uri] = new ResourceReference($resource, [$className, $methodName]); + + ++$discoveredCount['resources']; + break; + + case McpPrompt::class: + $docBlock = $this->docBlockParser->parseDocBlock($method->getDocComment() ?? null); + $name = ElementMetadataResolver::resolveName($method, $instance->name); + $description = ElementMetadataResolver::resolveDescription($method, $instance->description, $this->docBlockParser); + $arguments = []; + $paramTags = $this->docBlockParser->getParamTags($docBlock); + foreach ($method->getParameters() as $param) { + $reflectionType = $param->getType(); + if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) { + continue; + } + $paramTag = $paramTags['$'.$param->getName()] ?? null; + $arguments[] = new PromptArgument($param->getName(), $paramTag ? trim((string) $paramTag->getDescription()) : null, !$param->isOptional() && !$param->isDefaultValueAvailable()); + } + $prompt = new Prompt($name, $instance->title, $description, $arguments, $instance->icons, $instance->meta); + $completionProviders = $this->getCompletionProviders($method); + $prompts[$name] = new PromptReference($prompt, [$className, $methodName], $completionProviders); + ++$discoveredCount['prompts']; + break; + + case McpResourceTemplate::class: + $name = ElementMetadataResolver::resolveName($method, $instance->name); + $description = ElementMetadataResolver::resolveDescription($method, $instance->description, $this->docBlockParser); + $mimeType = $instance->mimeType; + $annotations = $instance->annotations; + $meta = $instance->meta ?? null; + $resourceTemplate = new ResourceTemplate($instance->uriTemplate, $name, $instance->title, $description, $mimeType, $annotations, $meta); + $completionProviders = $this->getCompletionProviders($method); + $resourceTemplates[$instance->uriTemplate] = new ResourceTemplateReference($resourceTemplate, [$className, $methodName], $completionProviders); + ++$discoveredCount['resourceTemplates']; + break; + } + } catch (ExceptionInterface $e) { + $this->logger->error("Failed to process MCP attribute on {$className}::{$methodName}", [ + 'attribute' => $attributeClassName, + 'exception' => $e, + ]); + } catch (\Throwable $e) { + $this->logger->error("Unexpected error processing attribute on {$className}::{$methodName}", [ + 'attribute' => $attributeClassName, + 'exception' => $e, + ]); + } + } + + /** + * @return array + */ + private function getCompletionProviders(\ReflectionMethod $reflectionMethod): array + { + $completionProviders = []; + foreach ($reflectionMethod->getParameters() as $param) { + $reflectionType = $param->getType(); + if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) { + continue; + } + + $completionAttributes = $param->getAttributes(CompletionProvider::class, \ReflectionAttribute::IS_INSTANCEOF); + if (!empty($completionAttributes)) { + $attributeInstance = $completionAttributes[0]->newInstance(); + + if ($attributeInstance->provider) { + $completionProviders[$param->getName()] = $attributeInstance->provider; + } elseif ($attributeInstance->providerClass) { + $completionProviders[$param->getName()] = $attributeInstance->provider; + } elseif ($attributeInstance->values) { + $completionProviders[$param->getName()] = new ListCompletionProvider($attributeInstance->values); + } elseif ($attributeInstance->enum) { + $completionProviders[$param->getName()] = new EnumCompletionProvider($attributeInstance->enum); + } + } + } + + return $completionProviders; + } + + /** + * Attempt to determine the FQCN from a PHP file path. + * Uses tokenization to extract namespace and class name. + * + * @return class-string|null the FQCN or null if not found/determinable + */ + private function getClassFromFile(SplFileInfo $file): ?string + { + $this->logger->debug('Processing file', ['path' => $file->getPathname()]); + + try { + $content = $file->getContents(); + } catch (\Throwable $e) { + $this->logger->warning("Failed to read file content during class discovery: {$file->getPathname()}", [ + 'exception' => $e, + ]); + + return null; + } + + if (\strlen($content) > 500 * 1024) { + $this->logger->warning('Skipping large file during class discovery.', ['file' => $file->getPathname()]); + + return null; + } + + try { + $tokens = token_get_all($content); + } catch (\Throwable $e) { + $this->logger->warning("Failed to tokenize file during class discovery: {$file->getPathname()}", [ + 'exception' => $e, + ]); + + return null; + } + + $namespace = ''; + $namespaceFound = false; + $level = 0; + $potentialClasses = []; + + $tokenCount = \count($tokens); + for ($i = 0; $i < $tokenCount; ++$i) { + if (\is_array($tokens[$i]) && \T_NAMESPACE === $tokens[$i][0]) { + $namespace = ''; + for ($j = $i + 1; $j < $tokenCount; ++$j) { + if (';' === $tokens[$j] || '{' === $tokens[$j]) { + $namespaceFound = true; + $i = $j; + break; + } + if (\is_array($tokens[$j]) && \in_array($tokens[$j][0], [\T_STRING, \T_NAME_QUALIFIED])) { + $namespace .= $tokens[$j][1]; + } elseif (\T_NS_SEPARATOR === $tokens[$j][0]) { + $namespace .= '\\'; + } + } + if ($namespaceFound) { + break; + } + } + } + $namespace = trim($namespace, '\\'); + + for ($i = 0; $i < $tokenCount; ++$i) { + $token = $tokens[$i]; + if ('{' === $token) { + ++$level; + + continue; + } + if ('}' === $token) { + --$level; + + continue; + } + + if ($level === ($namespaceFound && str_contains($content, "namespace {$namespace} {") ? 1 : 0)) { + if (\is_array($token) && \in_array($token[0], [\T_CLASS, \T_INTERFACE, \T_TRAIT, \defined('T_ENUM') ? \T_ENUM : -1])) { + for ($j = $i + 1; $j < $tokenCount; ++$j) { + if (\is_array($tokens[$j]) && \T_STRING === $tokens[$j][0]) { + $className = $tokens[$j][1]; + $potentialClasses[] = $namespace ? $namespace.'\\'.$className : $className; + $i = $j; + break; + } + if (';' === $tokens[$j] || '{' === $tokens[$j] || ')' === $tokens[$j]) { + break; + } + } + } + } + } + + foreach ($potentialClasses as $potentialClass) { + if (class_exists($potentialClass, true)) { + return $potentialClass; + } + } + + if (!empty($potentialClasses)) { + if (!class_exists($potentialClasses[0], false)) { + $this->logger->debug('getClassFromFile returning potential non-class type. Are you sure this class has been autoloaded?', ['file' => $file->getPathname(), 'type' => $potentialClasses[0]]); + } + + return $potentialClasses[0]; + } + + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscovererInterface.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscovererInterface.php new file mode 100644 index 0000000..2df69f3 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscovererInterface.php @@ -0,0 +1,34 @@ + + */ +interface DiscovererInterface +{ + public const DEFAULT_NAME_PATERNS = ['*.php']; + + /** + * Discover MCP elements in the specified directories and return the discovery state. + * + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() + */ + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = self::DEFAULT_NAME_PATERNS): DiscoveryState; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscoveryState.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscoveryState.php new file mode 100644 index 0000000..59a73ec --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/DiscoveryState.php @@ -0,0 +1,128 @@ + + */ +final class DiscoveryState +{ + /** + * @param array $tools + * @param array $resources + * @param array $prompts + * @param array $resourceTemplates + */ + public function __construct( + private readonly array $tools = [], + private readonly array $resources = [], + private readonly array $prompts = [], + private readonly array $resourceTemplates = [], + ) { + } + + /** + * @return array + */ + public function getTools(): array + { + return $this->tools; + } + + /** + * @return array + */ + public function getResources(): array + { + return $this->resources; + } + + /** + * @return array + */ + public function getPrompts(): array + { + return $this->prompts; + } + + /** + * @return array + */ + public function getResourceTemplates(): array + { + return $this->resourceTemplates; + } + + /** + * Returns the subset of this state whose keys are absent from $next. + * + * Asymmetric by design: entries whose keys exist in both states are excluded + * regardless of value. Used to identify owned entries that a fresh discovery + * no longer produces. + */ + public function obsoletedBy(self $next): self + { + return new self( + array_diff_key($this->tools, $next->tools), + array_diff_key($this->resources, $next->resources), + array_diff_key($this->prompts, $next->prompts), + array_diff_key($this->resourceTemplates, $next->resourceTemplates), + ); + } + + /** + * Check if this state contains any discovered elements. + */ + public function isEmpty(): bool + { + return empty($this->tools) + && empty($this->resources) + && empty($this->prompts) + && empty($this->resourceTemplates); + } + + /** + * Get the total count of discovered elements. + */ + public function getElementCount(): int + { + return \count($this->tools) + + \count($this->resources) + + \count($this->prompts) + + \count($this->resourceTemplates); + } + + /** + * Get a breakdown of discovered elements by type. + * + * @return array{tools: int, resources: int, prompts: int, resourceTemplates: int} + */ + public function getElementCounts(): array + { + return [ + 'tools' => \count($this->tools), + 'resources' => \count($this->resources), + 'prompts' => \count($this->prompts), + 'resourceTemplates' => \count($this->resourceTemplates), + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/DocBlockParser.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/DocBlockParser.php new file mode 100644 index 0000000..eb14a14 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/DocBlockParser.php @@ -0,0 +1,125 @@ + + */ +class DocBlockParser +{ + private DocBlockFactoryInterface $docBlockFactory; + + public function __construct( + ?DocBlockFactoryInterface $docBlockFactory = null, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + $this->docBlockFactory = $docBlockFactory ?? DocBlockFactory::createInstance(); + } + + /** + * Safely parses a DocComment string into a DocBlock object. + */ + public function parseDocBlock(string|false|null $docComment): ?DocBlock + { + if (false === $docComment || null === $docComment || empty($docComment)) { + return null; + } + try { + return $this->docBlockFactory->create($docComment); + } catch (\Throwable $e) { + // Log error or handle gracefully if invalid DocBlock syntax is encountered + $this->logger->warning('Failed to parse DocBlock', [ + 'exception' => $e, + ]); + + return null; + } + } + + /** + * Gets the description from a DocBlock (summary + description body). + */ + public function getDescription(?DocBlock $docBlock): ?string + { + if (!$docBlock) { + return null; + } + $summary = trim($docBlock->getSummary()); + $descriptionBody = trim((string) $docBlock->getDescription()); + + if ($summary && $descriptionBody) { + return $summary."\n\n".$descriptionBody; + } + if ($summary) { + return $summary; + } + if ($descriptionBody) { + return $descriptionBody; + } + + return null; + } + + /** + * Extracts "@param" tag information from a DocBlock, keyed by variable name (e.g., '$paramName'). + * + * @return array + */ + public function getParamTags(?DocBlock $docBlock): array + { + if (!$docBlock) { + return []; + } + + /** @var array $paramTags */ + $paramTags = []; + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag instanceof Param && $tag->getVariableName()) { + $paramTags['$'.$tag->getVariableName()] = $tag; + } + } + + return $paramTags; + } + + /** + * Gets the description string from a Param tag. + */ + public function getParamDescription(?Param $paramTag): ?string + { + return $paramTag ? (trim((string) $paramTag->getDescription()) ?: null) : null; + } + + /** + * Gets the type string from a Param tag. + */ + public function getParamTypeString(?Param $paramTag): ?string + { + if ($paramTag && $paramTag->getType()) { + $typeFromTag = trim((string) $paramTag->getType()); + if (!empty($typeFromTag)) { + return ltrim($typeFromTag, '\\'); + } + } + + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/ElementMetadataResolver.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/ElementMetadataResolver.php new file mode 100644 index 0000000..3c755dd --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/ElementMetadataResolver.php @@ -0,0 +1,44 @@ + + */ +final class ElementMetadataResolver +{ + private function __construct() + { + } + + /** + * Explicit names win; otherwise invokable classes are named after the class, + * regular handlers after the method. + */ + public static function resolveName(\ReflectionMethod $method, ?string $name): string + { + $methodName = $method->getName(); + + return $name ?? ('__invoke' === $methodName ? $method->getDeclaringClass()->getShortName() : $methodName); + } + + /** + * Explicit descriptions win; otherwise the summary of the method's doc block is used. + */ + public static function resolveDescription(\ReflectionMethod $method, ?string $description, DocBlockParser $docBlockParser): ?string + { + return $description ?? $docBlockParser->getDescription($docBlockParser->parseDocBlock($method->getDocComment() ?? null)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/HandlerResolver.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/HandlerResolver.php new file mode 100644 index 0000000..8cadcd3 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/HandlerResolver.php @@ -0,0 +1,96 @@ + + */ +class HandlerResolver +{ + /** + * Validates and resolves a handler to a ReflectionMethod or ReflectionFunction instance. + * + * A handler can be: + * - A Closure: function() { ... } + * - An array: [ClassName::class, 'methodName'] (resolved on a new or container-provided instance) + * - An array: [$instance, 'methodName'] (method on a pre-built object instance) + * - An array: [ClassName::class, 'staticMethod'] (static method, if callable) + * - A string: InvokableClassName::class (which will resolve to its '__invoke' method) + * + * @param Handler $handler the handler to resolve + * + * @throws InvalidArgumentException If the handler format is invalid, the class/method doesn't exist, + * or the method is unsuitable (e.g., private, abstract). + */ + public static function resolve(\Closure|array|string $handler): \ReflectionMethod|\ReflectionFunction + { + if ($handler instanceof \Closure) { + return new \ReflectionFunction($handler); + } + + if (\is_array($handler)) { + // A Closure in slot 0 must fall through to the format error rather than + // be treated as an instance, where "$closure::class" would yield the + // misleading class name "Closure". + $target = $handler[0] ?? null; + $hasValidTarget = (\is_string($target) || \is_object($target)) && !$target instanceof \Closure; + + if (2 !== \count($handler) || !$hasValidTarget || !isset($handler[1]) || !\is_string($handler[1])) { + throw new InvalidArgumentException('Invalid array handler format. Expected [ClassName::class, \'methodName\'] or [$instance, \'methodName\'].'); + } + [$classOrObject, $methodName] = $handler; + $className = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; + if (!class_exists($className)) { + throw new InvalidArgumentException(\sprintf('Handler class "%s" not found for array handler.', $className)); + } + if (!method_exists($className, $methodName)) { + throw new InvalidArgumentException(\sprintf('Handler method "%s" not found in class "%s" for array handler.', $methodName, $className)); + } + } elseif (class_exists($handler)) { + $className = $handler; + $methodName = '__invoke'; + if (!method_exists($className, $methodName)) { + throw new InvalidArgumentException(\sprintf('Invokable handler class "%s" must have a public "__invoke" method.', $className)); + } + } else { + throw new InvalidArgumentException('Invalid handler format. Expected Closure, [ClassName::class, \'methodName\'] or InvokableClassName::class string.'); + } + + try { + $reflectionMethod = new \ReflectionMethod($className, $methodName); + + // For discovered elements (non-manual), still reject static methods + // For manual elements, we'll allow static methods since they're callable + if (!$reflectionMethod->isPublic()) { + throw new InvalidArgumentException(\sprintf('Handler method "%s::%s" must be public.', $className, $methodName)); + } + if ($reflectionMethod->isAbstract()) { + throw new InvalidArgumentException(\sprintf('Handler method "%s::%s" must not be abstract.', $className, $methodName)); + } + if ($reflectionMethod->isConstructor() || $reflectionMethod->isDestructor()) { + throw new InvalidArgumentException(\sprintf('Handler method "%s::%s" cannot be a constructor or destructor.', $className, $methodName)); + } + + return $reflectionMethod; + } catch (\ReflectionException $e) { + // This typically occurs if class_exists passed but ReflectionMethod still fails (rare) + throw new InvalidArgumentException(\sprintf('Reflection error for handler "%s::%s": %s', $className, $methodName, $e->getMessage()), 0, $e); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGenerator.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGenerator.php new file mode 100644 index 0000000..357c9e9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGenerator.php @@ -0,0 +1,964 @@ + + * } + * @phpstan-type InferredParameterSchema array{ + * type?: string|array, + * description?: string, + * default?: mixed, + * enum?: array, + * items?: array, + * } + * @phpstan-type VariadicParameterSchema array{ + * type: 'array', + * items?: array, + * description?: string, + * parameter_schema?: array + * } + * + * @author Kyrian Obikwelu + */ +final class SchemaGenerator implements SchemaGeneratorInterface +{ + public function __construct( + private readonly DocBlockParser $docBlockParser, + ) { + } + + /** + * Generates a JSON Schema object (as a PHP array) for parameters. + * + * @return array + */ + public function generate(\Reflector $reflection): array + { + if ($reflection instanceof \ReflectionClass) { + throw new BadMethodCallException('Schema generation from ReflectionClass is not implemented yet. Use ReflectionMethod or ReflectionFunction instead.'); + } + + if (!$reflection instanceof \ReflectionMethod && !$reflection instanceof \ReflectionFunction) { + throw new BadMethodCallException(\sprintf('Schema generation from %s is not supported. Use ReflectionMethod or ReflectionFunction instead.', $reflection::class)); + } + + $methodSchema = $this->extractMethodLevelSchema($reflection); + + if ($methodSchema && isset($methodSchema['definition'])) { + return $methodSchema['definition']; + } + + $parametersInfo = $this->parseParametersInfo($reflection); + + return $this->buildSchemaFromParameters($parametersInfo, $methodSchema); + } + + /** + * Generates a JSON Schema object (as a PHP array) for a method's or function's return type. + * + * Only returns an outputSchema if explicitly provided in the McpTool attribute. + * Per MCP spec, outputSchema should only be present when explicitly provided. + * + * @return ?array + */ + public function generateOutputSchema(\Reflector $reflection): ?array + { + if ($reflection instanceof \ReflectionClass) { + throw new BadMethodCallException('Schema generation from ReflectionClass is not implemented yet. Use ReflectionMethod or ReflectionFunction instead.'); + } + + if (!$reflection instanceof \ReflectionMethod && !$reflection instanceof \ReflectionFunction) { + throw new BadMethodCallException(\sprintf('Schema generation from %s is not supported. Use ReflectionMethod or ReflectionFunction instead.', $reflection::class)); + } + + // Only return outputSchema if explicitly provided in McpTool attribute + $mcpToolAttrs = $reflection->getAttributes(McpTool::class, \ReflectionAttribute::IS_INSTANCEOF); + if ($mcpToolAttrs) { + $mcpToolInstance = $mcpToolAttrs[0]->newInstance(); + + return $mcpToolInstance->outputSchema; + } + + return null; + } + + /** + * Extracts method-level or function-level Schema attribute. + * + * @return SchemaAttributeData + */ + private function extractMethodLevelSchema(\ReflectionFunctionAbstract $reflection): ?array + { + $schemaAttrs = $reflection->getAttributes(Schema::class, \ReflectionAttribute::IS_INSTANCEOF); + if (empty($schemaAttrs)) { + return null; + } + + /** @var Schema $schemaAttr */ + $schemaAttr = $schemaAttrs[0]->newInstance(); + + return $schemaAttr->toArray(); + } + + /** + * Extracts parameter-level Schema attribute. + * + * @return SchemaAttributeData + */ + private function extractParameterLevelSchema(\ReflectionParameter $parameter): array + { + $schemaAttrs = $parameter->getAttributes(Schema::class, \ReflectionAttribute::IS_INSTANCEOF); + if (empty($schemaAttrs)) { + return []; + } + + /** @var Schema $schemaAttr */ + $schemaAttr = $schemaAttrs[0]->newInstance(); + + return $schemaAttr->toArray(); + } + + /** + * Builds the final schema from parameter information and method-level schema. + * + * @param ParameterInfo[] $parametersInfo + * @param SchemaAttributeData $methodSchema + * + * @return array + */ + private function buildSchemaFromParameters(array $parametersInfo, ?array $methodSchema): array + { + $schema = [ + 'type' => 'object', + 'properties' => [], + 'required' => [], + ]; + + // Apply method-level schema as base + if ($methodSchema) { + $schema = array_merge($schema, $methodSchema); + if (!isset($schema['type'])) { + $schema['type'] = 'object'; + } + if (!isset($schema['properties'])) { + $schema['properties'] = []; + } + if (!isset($schema['required'])) { + $schema['required'] = []; + } + } + + foreach ($parametersInfo as $paramInfo) { + $paramName = $paramInfo['name']; + + $methodLevelParamSchema = $schema['properties'][$paramName] ?? null; + + $paramSchema = $this->buildParameterSchema($paramInfo, $methodLevelParamSchema); + + $schema['properties'][$paramName] = $paramSchema; + + if ($paramInfo['required'] && !\in_array($paramName, $schema['required'])) { + $schema['required'][] = $paramName; + } elseif (!$paramInfo['required'] && ($key = array_search($paramName, $schema['required'])) !== false) { + unset($schema['required'][$key]); + $schema['required'] = array_values($schema['required']); // Re-index + } + } + + // Clean up empty properties + if (empty($schema['properties'])) { + $schema['properties'] = new \stdClass(); + } + if (empty($schema['required'])) { + unset($schema['required']); + } + + return $schema; + } + + /** + * Builds the final schema for a single parameter by merging all three levels. + * + * @param ParameterInfo $paramInfo + * @param array|null $methodLevelParamSchema + */ + private function buildParameterSchema(array $paramInfo, ?array $methodLevelParamSchema): array + { + if ($paramInfo['is_variadic']) { + return $this->ensureArrayItems($this->buildVariadicParameterSchema($paramInfo)); + } + + $inferredSchema = $this->buildInferredParameterSchema($paramInfo); + + // Method-level takes precedence over inferred schema + $mergedSchema = $inferredSchema; + if ($methodLevelParamSchema) { + $mergedSchema = array_merge($inferredSchema, $methodLevelParamSchema); + } + + // Parameter-level takes highest precedence + $parameterLevelSchema = $paramInfo['parameter_schema']; + if (!empty($parameterLevelSchema)) { + // A complete definition replaces the schema, as it does at method level. + if (isset($parameterLevelSchema['definition']) && \is_array($parameterLevelSchema['definition'])) { + $mergedSchema = $parameterLevelSchema['definition']; + + // The default comes from the signature, not from the schema. + if (!\array_key_exists('default', $mergedSchema) && \array_key_exists('default', $inferredSchema)) { + $mergedSchema['default'] = $inferredSchema['default']; + } + } else { + $mergedSchema = array_merge($mergedSchema, $parameterLevelSchema); + } + } + + // Run after all merges so that when a Schema attribute reshapes the parameter + // (e.g. to `object`), the array `items` invariant is only enforced on what is + // genuinely still an array. + return $this->ensureArrayItems($mergedSchema); + } + + /** + * Guarantees an array-typed schema always declares `items`. + * + * `items` is optional in JSON Schema, but some strict clients reject an array schema + * without it. When no element type could be inferred, default to the empty schema `{}` + * (matches anything) — represented as `new \stdClass()` so it serializes to `{}` rather + * than `[]`. + * + * @param array $schema + * + * @return array + */ + private function ensureArrayItems(array $schema): array + { + $type = $schema['type'] ?? null; + $isArray = 'array' === $type || (\is_array($type) && \in_array('array', $type, true)); + + if ($isArray && !isset($schema['items'])) { + $schema['items'] = new \stdClass(); + } + + return $schema; + } + + /** + * Builds parameter schema from inferred type and docblock information only. + * Returns empty array for variadic parameters (handled separately). + * + * @param ParameterInfo $paramInfo + * + * @return InferredParameterSchema + */ + private function buildInferredParameterSchema(array $paramInfo): array + { + $paramSchema = []; + + // Variadic parameters are handled separately + if ($paramInfo['is_variadic']) { + return []; + } + + // Infer JSON Schema types + $jsonTypes = $this->inferParameterTypes($paramInfo); + + if (1 === \count($jsonTypes)) { + $paramSchema['type'] = $jsonTypes[0]; + } elseif (\count($jsonTypes) > 1) { + $paramSchema['type'] = $jsonTypes; + } + + // Add description from docblock + if ($paramInfo['description']) { + $paramSchema['description'] = $paramInfo['description']; + } + + // Add default value only if parameter actually has a default + if ($paramInfo['has_default']) { + $paramSchema['default'] = $paramInfo['default_value']; + } + + // Handle enums + $paramSchema = $this->applyEnumConstraints($paramSchema, $paramInfo); + + // Handle array items + return $this->applyArrayConstraints($paramSchema, $paramInfo); + } + + /** + * Builds schema for variadic parameters. + * + * @param ParameterInfo $paramInfo + * + * @return VariadicParameterSchema + */ + private function buildVariadicParameterSchema(array $paramInfo): array + { + $paramSchema = ['type' => 'array']; + + // Add description from docblock before parameter-level schema is merged in, + // so a definition's own description takes precedence (matches buildParameterSchema). + if ($paramInfo['description']) { + $paramSchema['description'] = $paramInfo['description']; + } + + // Apply parameter-level Schema attributes first + if (!empty($paramInfo['parameter_schema'])) { + $parameterLevelSchema = $paramInfo['parameter_schema']; + + $paramSchema = isset($parameterLevelSchema['definition']) && \is_array($parameterLevelSchema['definition']) + ? $parameterLevelSchema['definition'] + : array_merge($paramSchema, $parameterLevelSchema); + + // Ensure type is always array for variadic + $paramSchema['type'] = 'array'; + } + + // If no items specified by Schema attribute, infer from type + if (!isset($paramSchema['items'])) { + $itemJsonTypes = $this->mapPhpTypeToJsonSchemaType($paramInfo['type_string']); + $nonNullItemTypes = array_filter($itemJsonTypes, static fn ($t) => 'null' !== $t); + + if (1 === \count($nonNullItemTypes)) { + $paramSchema['items'] = ['type' => $nonNullItemTypes[0]]; + } + } + + return $paramSchema; + } + + /** + * Infers JSON Schema types for a parameter. + * + * @param ParameterInfo $paramInfo + */ + private function inferParameterTypes(array $paramInfo): array + { + $jsonTypes = $this->mapPhpTypeToJsonSchemaType($paramInfo['type_string']); + + if ($paramInfo['allows_null'] && 'mixed' !== strtolower($paramInfo['type_string']) && !\in_array('null', $jsonTypes)) { + $jsonTypes[] = 'null'; + } + + if (\count($jsonTypes) > 1) { + // Sort but ensure null comes first for consistency + $nullIndex = array_search('null', $jsonTypes); + if (false !== $nullIndex) { + unset($jsonTypes[$nullIndex]); + sort($jsonTypes); + array_unshift($jsonTypes, 'null'); + } else { + sort($jsonTypes); + } + } + + return $jsonTypes; + } + + /** + * Applies enum constraints to parameter schema. + */ + private function applyEnumConstraints(array $paramSchema, array $paramInfo): array + { + $reflectionType = $paramInfo['reflection_type_object']; + + if (!($reflectionType instanceof \ReflectionNamedType) || $reflectionType->isBuiltin() || !enum_exists($reflectionType->getName())) { + return $paramSchema; + } + + $enumClass = $reflectionType->getName(); + $enumReflection = new \ReflectionEnum($enumClass); + $backingTypeReflection = $enumReflection->getBackingType(); + + if ($enumReflection->isBacked() && $backingTypeReflection instanceof \ReflectionNamedType) { + $paramSchema['enum'] = array_column($enumClass::cases(), 'value'); + $jsonBackingType = match ($backingTypeReflection->getName()) { + 'int' => 'integer', + 'string' => 'string', + default => null, + }; + + if ($jsonBackingType) { + if (isset($paramSchema['type']) && \is_array($paramSchema['type']) && \in_array('null', $paramSchema['type'])) { + $paramSchema['type'] = [$jsonBackingType, 'null']; + $paramSchema['enum'][] = null; + } else { + $paramSchema['type'] = $jsonBackingType; + } + } + } else { + // Non-backed enum - use names as enum values + $paramSchema['enum'] = array_column($enumClass::cases(), 'name'); + if (isset($paramSchema['type']) && \is_array($paramSchema['type']) && \in_array('null', $paramSchema['type'])) { + $paramSchema['type'] = ['string', 'null']; + $paramSchema['enum'][] = null; + } else { + $paramSchema['type'] = 'string'; + } + } + + return $paramSchema; + } + + /** + * Applies array-specific constraints to parameter schema. + */ + private function applyArrayConstraints(array $paramSchema, array $paramInfo): array + { + if (!isset($paramSchema['type'])) { + return $paramSchema; + } + + $typeString = $paramInfo['type_string']; + $allowsNull = $paramInfo['allows_null']; + + // Handle object-like arrays using array{} syntax + if (preg_match('/^array\s*{/i', $typeString)) { + $objectSchema = $this->inferArrayItemsType($typeString); + if (\is_array($objectSchema) && isset($objectSchema['properties'])) { + $paramSchema = array_merge($paramSchema, $objectSchema); + $paramSchema['type'] = $allowsNull ? ['object', 'null'] : 'object'; + } + } + // Handle regular arrays + elseif (\in_array('array', $this->mapPhpTypeToJsonSchemaType($typeString))) { + // `inferArrayItemsType()` expects a single array type, not a union — for + // `string[]|int` it would see the whole string, match nothing, and lose + // the element-type constraint. Isolate the branch(es) that are arrays. + $arrayBranches = array_values(array_filter( + self::splitUnion($typeString), + fn (string $branch): bool => \in_array('array', $this->mapPhpTypeToJsonSchemaType($branch), true), + )); + if ([] === $arrayBranches) { + $arrayBranches = [$typeString]; + } + + // Several array branches (`string[]|int[]`) can disagree on their + // element type. Applying just one would reject values valid under + // the other, so `items` only applies when every array branch agrees. + $itemsTypes = array_map($this->inferArrayItemsType(...), $arrayBranches); + $itemsType = 1 === \count(array_unique(array_map(json_encode(...), $itemsTypes))) ? $itemsTypes[0] : 'any'; + + if ('any' !== $itemsType) { + if (\is_string($itemsType)) { + $paramSchema['items'] = ['type' => $itemsType]; + } else { + if (!isset($itemsType['type']) && isset($itemsType['properties'])) { + $itemsType = array_merge(['type' => 'object'], $itemsType); + } + $paramSchema['items'] = $itemsType; + } + } + + // Only when the parameter is an array and nothing else. A union + // like `string[]|int` keeps both branches: `items` constrains the + // instance only when it *is* an array, so the two coexist and + // overwriting the type here would drop the scalar branch — + // rejecting a value the handler accepts. + $otherTypes = array_values(array_diff( + (array) $paramSchema['type'], + ['array', 'null'], + )); + + if ([] === $otherTypes) { + $paramSchema['type'] = $allowsNull ? ['array', 'null'] : 'array'; + + if (\is_array($paramSchema['type'])) { + sort($paramSchema['type']); + } + } elseif ($allowsNull && !\in_array('null', (array) $paramSchema['type'], true)) { + $paramSchema['type'] = [...(array) $paramSchema['type'], 'null']; + sort($paramSchema['type']); + } + } + + return $paramSchema; + } + + /** + * Parses detailed information about a method's parameters. + * + * @return ParameterInfo[] + */ + private function parseParametersInfo(\ReflectionMethod|\ReflectionFunction $reflection): array + { + $docComment = $reflection->getDocComment() ?: null; + $docBlock = $this->docBlockParser->parseDocBlock($docComment); + $paramTags = $this->docBlockParser->getParamTags($docBlock); + $parametersInfo = []; + + foreach ($reflection->getParameters() as $rp) { + $reflectionType = $rp->getType(); + + if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin() + && InjectableParameters::supports($reflectionType->getName()) + ) { + continue; + } + + $paramName = $rp->getName(); + if (\in_array(strtolower($paramName), ['_session', '_request'], true)) { + throw new InvalidArgumentException(\sprintf('Handler method "%s::%s" has parameter named "%s" which is not allowed. Please change the name of that parameter.', $reflection->class, $reflection->name, $paramName)); + } + $paramTag = $paramTags['$'.$paramName] ?? null; + + $typeString = $this->getParameterTypeString($rp, $paramTag); + $description = $this->docBlockParser->getParamDescription($paramTag); + $hasDefault = $rp->isDefaultValueAvailable(); + $defaultValue = $hasDefault ? $rp->getDefaultValue() : null; + $isVariadic = $rp->isVariadic(); + + $parameterSchema = $this->extractParameterLevelSchema($rp); + + if ($defaultValue instanceof \BackedEnum) { + $defaultValue = $defaultValue->value; + } + + if ($defaultValue instanceof \UnitEnum) { + $defaultValue = $defaultValue->name; + } + + $allowsNull = false; + if ($reflectionType && $reflectionType->allowsNull()) { + $allowsNull = true; + } elseif ($hasDefault && null === $defaultValue) { + $allowsNull = true; + } elseif (str_contains($typeString, 'null') || 'mixed' === strtolower($typeString)) { + $allowsNull = true; + } + + $parametersInfo[] = [ + 'name' => $paramName, + 'doc_block_tag' => $paramTag, + 'reflection_param' => $rp, + 'reflection_type_object' => $reflectionType, + 'type_string' => $typeString, + 'description' => $description, + 'required' => !$rp->isOptional(), + 'allows_null' => $allowsNull, + 'default_value' => $defaultValue, + 'has_default' => $hasDefault, + 'is_variadic' => $isVariadic, + 'parameter_schema' => $parameterSchema, + ]; + } + + return $parametersInfo; + } + + /** + * Determines the type string for a parameter, prioritizing DocBlock. + */ + private function getParameterTypeString(\ReflectionParameter $rp, ?Param $paramTag): string + { + $docBlockType = $this->docBlockParser->getParamTypeString($paramTag); + $isDocBlockTypeGeneric = false; + + if (null !== $docBlockType) { + if (\in_array(strtolower($docBlockType), ['mixed', 'unknown', ''])) { + $isDocBlockTypeGeneric = true; + } + } else { + $isDocBlockTypeGeneric = true; // No tag or no type in tag implies generic + } + + $reflectionType = $rp->getType(); + $reflectionTypeString = null; + if ($reflectionType) { + $reflectionTypeString = $this->getTypeStringFromReflection($reflectionType, $rp->allowsNull()); + } + + // Prioritize Reflection if DocBlock type is generic AND Reflection provides a more specific type + if ($isDocBlockTypeGeneric && null !== $reflectionTypeString && 'mixed' !== $reflectionTypeString) { + return $reflectionTypeString; + } + + // Otherwise, use the DocBlock type if it was valid and non-generic + if (null !== $docBlockType && !$isDocBlockTypeGeneric) { + // Consider if DocBlock adds nullability missing from reflection + if (false !== stripos($docBlockType, 'null') && $reflectionTypeString && false === stripos($reflectionTypeString, 'null') && !str_ends_with($reflectionTypeString, '|null')) { + // If reflection didn't capture null, but docblock did, append |null (if not already mixed) + if ('mixed' !== $reflectionTypeString) { + return $reflectionTypeString.'|null'; + } + } + + return $docBlockType; + } + + // Fallback to Reflection type even if it was generic ('mixed') + if (null !== $reflectionTypeString) { + return $reflectionTypeString; + } + + // Default to 'mixed' if nothing else found + return 'mixed'; + } + + /** + * Converts a ReflectionType object into a type string representation. + */ + private function getTypeStringFromReflection(?\ReflectionType $type, bool $nativeAllowsNull): string + { + if (null === $type) { + return 'mixed'; + } + + $types = []; + if ($type instanceof \ReflectionUnionType) { + foreach ($type->getTypes() as $innerType) { + $types[] = $this->getTypeStringFromReflection($innerType, $innerType->allowsNull()); + } + if ($nativeAllowsNull) { + $types = array_filter($types, static fn ($t) => 'null' !== strtolower($t)); + } + $typeString = implode('|', array_unique(array_filter($types))); + } elseif ($type instanceof \ReflectionIntersectionType) { + foreach ($type->getTypes() as $innerType) { + $types[] = $this->getTypeStringFromReflection($innerType, false); + } + $typeString = implode('&', array_unique(array_filter($types))); + } elseif ($type instanceof \ReflectionNamedType) { + $typeString = $type->getName(); + } else { + return 'mixed'; + } + + $typeString = match (strtolower($typeString)) { + 'bool' => 'boolean', + 'int' => 'integer', + 'float', 'double' => 'number', + 'str' => 'string', + default => $typeString, + }; + + $isNullable = $nativeAllowsNull; + if ($type instanceof \ReflectionNamedType && 'mixed' === $type->getName()) { + $isNullable = true; + } + + if ($type instanceof \ReflectionUnionType && !$nativeAllowsNull) { + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof \ReflectionNamedType && 'null' === strtolower($innerType->getName())) { + $isNullable = true; + break; + } + } + } + + if ($isNullable && 'mixed' !== $typeString && false === stripos($typeString, 'null')) { + if (!str_ends_with($typeString, '|null') && !str_ends_with($typeString, '&null')) { + $typeString .= '|null'; + } + } + + // Remove leading backslash from class names, but handle built-ins like 'int' or unions like 'int|string' + if (str_contains($typeString, '\\')) { + $parts = preg_split('/([|&])/', $typeString, -1, \PREG_SPLIT_DELIM_CAPTURE); + $processedParts = array_map(static fn ($part) => str_starts_with($part, '\\') ? ltrim($part, '\\') : $part, $parts); + $typeString = implode('', $processedParts); + } + + return $typeString ?: 'mixed'; + } + + /** + * Splits a type string on its top-level `|`, leaving generics and shapes + * intact. + * + * `int|string` splits; `array` and `array{a: int|string}` do + * not, because the `|` there is a parameter of the outer type rather than + * an alternative to it. + * + * @return list + */ + public static function splitUnion(string $type): array + { + $branches = []; + $depth = 0; + $current = ''; + + foreach (str_split($type) as $character) { + if (\in_array($character, ['<', '{', '('], true)) { + ++$depth; + } elseif (\in_array($character, ['>', '}', ')'], true)) { + $depth = max(0, $depth - 1); + } elseif ('|' === $character && 0 === $depth) { + $branches[] = trim($current); + $current = ''; + + continue; + } + + $current .= $character; + } + + $branches[] = trim($current); + + return array_values(array_filter($branches, static fn (string $branch): bool => '' !== $branch)); + } + + /** + * Maps a PHP type string (potentially a union) to an array of JSON Schema type names. + * + * @return string[] + */ + private function mapPhpTypeToJsonSchemaType(string $phpTypeString): array + { + $normalizedType = strtolower(trim($phpTypeString)); + + // PRIORITY 1: Handle unions before anything else. A `|` at the top + // level joins alternatives; one inside `<>` or `{}` belongs to a + // generic (`array`) and is not a union of the whole type. + // Checked first because a branch may itself be an array shape — and + // `string[]|int` read as "an array" silently loses the `int`. + $branches = self::splitUnion($normalizedType); + + if (\count($branches) > 1) { + $jsonTypes = []; + foreach ($branches as $branch) { + $jsonTypes = array_merge($jsonTypes, $this->mapPhpTypeToJsonSchemaType($branch)); + } + + return array_values(array_unique($jsonTypes)); + } + + // PRIORITY 2: Check for array{} syntax which should be treated as object + if (preg_match('/^array\s*{/i', $normalizedType)) { + return ['object']; + } + + // PRIORITY 3: Check for array syntax (T[] or generics) + if ( + str_contains($normalizedType, '[]') + || preg_match('/^(array|list|iterable|collection)]+\s*>$/i', $normalizedType)) { + return ['integer']; + } + + // PRIORITY 5: Handle simple built-in types + return match ($normalizedType) { + 'string', 'scalar' => ['string'], + '?string' => ['null', 'string'], + 'int', 'integer' => ['integer'], + '?int', '?integer' => ['null', 'integer'], + 'float', 'double', 'number' => ['number'], + '?float', '?double', '?number' => ['null', 'number'], + 'bool', 'boolean' => ['boolean'], + '?bool', '?boolean' => ['null', 'boolean'], + 'array' => ['array'], + '?array' => ['null', 'array'], + 'object', 'stdclass' => ['object'], + '?object', '?stdclass' => ['null', 'object'], + 'null' => ['null'], + 'resource', 'callable' => ['object'], + 'mixed' => [], + 'void', 'never' => [], + default => ['object'], + }; + } + + /** + * Infers the 'items' schema type for an array based on DocBlock type hints. + * + * @return string|array + */ + private function inferArrayItemsType(string $phpTypeString): string|array + { + $normalizedType = trim($phpTypeString); + + // Strip a top-level nullable union (e.g. `string[]|null`, `null|int[]`) so the + // element type is still recovered; array nullability is handled separately via + // `allows_null`. Internal unions such as `array` are left untouched. + $normalizedType = trim((string) preg_replace('/^null\s*\|\s*|\s*\|\s*null$/i', '', $normalizedType)); + + // Case 1: Simple T[] syntax (e.g., string[], int[], bool[], etc.) + if (preg_match('/^(\\??)([\w\\\\]+)\\s*\\[\\]$/i', $normalizedType, $matches)) { + $itemType = strtolower($matches[2]); + + return $this->mapSimpleTypeToJsonSchema($itemType); + } + + // Case 2: Generic array syntax (e.g., array, array, etc.) + if (preg_match('/^(\\??)array\s*<\s*([\w\\\\|]+)\s*>$/i', $normalizedType, $matches)) { + $itemType = strtolower($matches[2]); + + return $this->mapSimpleTypeToJsonSchema($itemType); + } + + // Case 3: Nested array> syntax or T[][] syntax + if ( + preg_match('/^(\\??)array\s*<\s*array\s*<\s*([\w\\\\|]+)\s*>\s*>$/i', $normalizedType, $matches) + || preg_match('/^(\\??)([\w\\\\]+)\s*\[\]\[\]$/i', $normalizedType, $matches) + ) { + $innerType = $this->mapSimpleTypeToJsonSchema(isset($matches[2]) ? strtolower($matches[2]) : 'any'); /* @phpstan-ignore isset.offset */ + + // Return a schema for array with items being arrays + return [ + 'type' => 'array', + 'items' => [ + 'type' => $innerType, + ], + ]; + } + + // Case 4: Object-like array syntax (e.g., array{name: string, age: int}) + if (preg_match('/^(\\??)array\s*\{(.+)\}$/is', $normalizedType, $matches)) { + return $this->parseObjectLikeArray($matches[2]); + } + + return 'any'; + } + + /** + * Parses object-like array syntax into a JSON Schema object. + * + * @return array{ + * type: 'object', + * properties?: array, + * required?: array + * } + */ + private function parseObjectLikeArray(string $propertiesStr): array + { + $properties = []; + $required = []; + + // Parse properties from the string, handling nested structures + $depth = 0; + $buffer = ''; + + for ($i = 0; $i < \strlen($propertiesStr); ++$i) { + $char = $propertiesStr[$i]; + + // Track nested braces + if ('{' === $char) { + ++$depth; + $buffer .= $char; + } elseif ('}' === $char) { + --$depth; + $buffer .= $char; + } + // Property separator (comma) + elseif (',' === $char && 0 === $depth) { + // Process the completed property + $this->parsePropertyDefinition(trim($buffer), $properties, $required); + $buffer = ''; + } else { + $buffer .= $char; + } + } + + // Process the last property + if (!empty($buffer)) { + $this->parsePropertyDefinition(trim($buffer), $properties, $required); + } + + if (!empty($properties)) { + return [ + 'type' => 'object', + 'properties' => $properties, + 'required' => $required, + ]; + } + + return ['type' => 'object']; + } + + /** + * Parses a single property definition from an object-like array syntax. + */ + private function parsePropertyDefinition(string $propDefinition, array &$properties, array &$required): void + { + // Match property name and type + if (preg_match('/^([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*:\s*(.+)$/i', $propDefinition, $matches)) { + $propName = $matches[1]; + $propType = trim($matches[2]); + + // Add to required properties + $required[] = $propName; + + // Check for nested array{} syntax + if (preg_match('/^array\s*\{(.+)\}$/is', $propType, $nestedMatches)) { + $nestedSchema = $this->parseObjectLikeArray($nestedMatches[1]); + $properties[$propName] = $nestedSchema; + } + // Check for array or T[] syntax + elseif ( + preg_match('/^array\s*<\s*([\w\\\\|]+)\s*>$/i', $propType, $arrayMatches) + || preg_match('/^([\w\\\\]+)\s*\[\]$/i', $propType, $arrayMatches) + ) { + $itemType = $arrayMatches[1] ?? 'any'; /* @phpstan-ignore nullCoalesce.offset */ + $properties[$propName] = [ + 'type' => 'array', + 'items' => [ + 'type' => $this->mapSimpleTypeToJsonSchema($itemType), + ], + ]; + } + // Simple type + else { + $properties[$propName] = ['type' => $this->mapSimpleTypeToJsonSchema($propType)]; + } + } + } + + /** + * Helper method to map basic PHP types to JSON Schema types. + */ + private function mapSimpleTypeToJsonSchema(string $type): string + { + return match (strtolower($type)) { + 'string' => 'string', + 'int', 'integer' => 'integer', + 'bool', 'boolean' => 'boolean', + 'float', 'double', 'number' => 'number', + 'array' => 'array', + 'object', 'stdclass' => 'object', + default => \in_array(strtolower($type), ['datetime', 'datetimeinterface']) ? 'string' : 'object', + }; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGeneratorInterface.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGeneratorInterface.php new file mode 100644 index 0000000..c21d3cd --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaGeneratorInterface.php @@ -0,0 +1,41 @@ + + */ +interface SchemaGeneratorInterface +{ + /** + * Generates a JSON Schema for input parameters. + * + * The returned schema must be a valid JSON Schema object (type: 'object') + * with properties corresponding to a tool's parameters. + * + * @return array{ + * type: 'object', + * properties: array|object, + * required?: string[] + * } + */ + public function generate(\Reflector $reflection): array; + + /** + * Generates a JSON Schema for output/result. + * + * @return ?array + */ + public function generateOutputSchema(\Reflector $reflection): ?array; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaValidator.php b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaValidator.php new file mode 100644 index 0000000..56174bd --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Discovery/SchemaValidator.php @@ -0,0 +1,335 @@ + + */ +class SchemaValidator +{ + private ?Validator $jsonSchemaValidator = null; + + public function __construct( + private LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * Validates data against a JSON schema. + * + * @param mixed $data the data to validate (should generally be decoded JSON) + * @param array|object $schema the JSON Schema definition (as PHP array or object) + * + * @return list array of validation errors, empty if valid + */ + public function validateAgainstJsonSchema(mixed $data, array|object $schema): array + { + if (\is_array($data) && empty($data)) { + $data = new \stdClass(); + } + + try { + // --- Schema Preparation --- + if (\is_array($schema)) { + $schemaJson = json_encode($schema, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES); + $schemaObject = json_decode($schemaJson, false, 512, \JSON_THROW_ON_ERROR); + } elseif (\is_object($schema)) { + // This might be overly cautious but safer against varied inputs. + $schemaJson = json_encode($schema, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES); + $schemaObject = json_decode($schemaJson, false, 512, \JSON_THROW_ON_ERROR); + } else { + throw new InvalidArgumentException('Schema must be an array or object.'); + } + + // --- Data Preparation --- + // Opis Validator generally prefers objects for object validation + $dataToValidate = $this->convertDataForValidator($data); + } catch (\JsonException $e) { + $this->logger->error('MCP SDK: Invalid schema structure provided for validation (JSON conversion failed).', ['exception' => $e]); + + return [['pointer' => '', 'keyword' => 'internal', 'message' => 'Invalid schema definition provided (JSON error).']]; + } catch (InvalidArgumentException $e) { + $this->logger->error('MCP SDK: Invalid schema structure provided for validation.', ['exception' => $e]); + + return [['pointer' => '', 'keyword' => 'internal', 'message' => $e->getMessage()]]; + } catch (\Throwable $e) { + $this->logger->error('MCP SDK: Error preparing data/schema for validation.', ['exception' => $e]); + + return [['pointer' => '', 'keyword' => 'internal', 'message' => 'Internal validation preparation error.']]; + } + + $validator = $this->getJsonSchemaValidator(); + + try { + $result = $validator->validate($dataToValidate, $schemaObject); + } catch (\Throwable $e) { + $this->logger->error('MCP SDK: JSON Schema validation failed internally.', [ + 'exception' => $e, + 'data' => json_encode($dataToValidate), + 'schema' => json_encode($schemaObject), + ]); + + return [['pointer' => '', 'keyword' => 'internal', 'message' => 'Schema validation process failed: '.$e->getMessage()]]; + } + + if ($result->isValid()) { + return []; + } + + $formattedErrors = []; + $topError = $result->error(); + + if ($topError) { + $this->collectSubErrors($topError, $formattedErrors); + } + + if (empty($formattedErrors) && $topError) { // Fallback + $formattedErrors[] = [ + 'pointer' => $this->formatJsonPointerPath($topError->data()->path()), + 'keyword' => $topError->keyword(), + 'message' => $this->formatValidationError($topError), + ]; + } + + return $formattedErrors; + } + + /** + * Get or create the JSON Schema validator instance. + */ + private function getJsonSchemaValidator(): Validator + { + if (null === $this->jsonSchemaValidator) { + $this->jsonSchemaValidator = new Validator(); + // Potentially configure resolver here if needed later + } + + return $this->jsonSchemaValidator; + } + + /** + * Recursively converts associative arrays to stdClass objects for validator compatibility. + */ + private function convertDataForValidator(mixed $data): mixed + { + if (\is_array($data)) { + // Check if it's an associative array (keys are not sequential numbers 0..N-1) + if (!empty($data) && array_keys($data) !== range(0, \count($data) - 1)) { + $obj = new \stdClass(); + foreach ($data as $key => $value) { + $obj->{$key} = $this->convertDataForValidator($value); + } + + return $obj; + } + + // It's a list (sequential array), convert items recursively + return array_map([$this, 'convertDataForValidator'], $data); + } elseif (\is_object($data) && $data instanceof \stdClass) { + // Deep copy/convert stdClass objects as well + $obj = new \stdClass(); + foreach (get_object_vars($data) as $key => $value) { + $obj->{$key} = $this->convertDataForValidator($value); + } + + return $obj; + } + + // Leave other objects and scalar types as they are + return $data; + } + + /** + * Recursively collects leaf validation errors. + * + * @param Error[] $collectedErrors + */ + private function collectSubErrors(ValidationError $error, array &$collectedErrors): void + { + $subErrors = $error->subErrors(); + if (empty($subErrors)) { + $collectedErrors[] = [ + 'pointer' => $this->formatJsonPointerPath($error->data()->path()), + 'keyword' => $error->keyword(), + 'message' => $this->formatValidationError($error), + ]; + } else { + foreach ($subErrors as $subError) { + $this->collectSubErrors($subError, $collectedErrors); + } + } + } + + /** + * Formats the path array into a JSON Pointer string. + * + * @param string[]|int[]|null $pathComponents + */ + private function formatJsonPointerPath(?array $pathComponents): string + { + if (empty($pathComponents)) { + return '/'; + } + $escapedComponents = array_map(static function ($component) { + $componentStr = (string) $component; + + return str_replace(['~', '/'], ['~0', '~1'], $componentStr); + }, $pathComponents); + + return '/'.implode('/', $escapedComponents); + } + + /** + * Formats an Opis SchemaValidationError into a user-friendly message. + */ + private function formatValidationError(ValidationError $error): string + { + $keyword = $error->keyword(); + $args = $error->args(); + $message = "Constraint `{$keyword}` failed."; + + switch (strtolower($keyword)) { + case 'required': + $missing = $args['missing'] ?? []; + $formattedMissing = implode(', ', array_map(static fn ($p) => "`{$p}`", $missing)); + $message = "Missing required properties: {$formattedMissing}."; + break; + case 'type': + $expected = implode('|', (array) ($args['expected'] ?? [])); + $used = $error->data()->type() ?? 'unknown'; + $message = "Invalid type. Expected `{$expected}`, but received `{$used}`."; + break; + case 'enum': + $schemaData = $error->schema()->info()->data(); + $allowedValues = []; + if (\is_object($schemaData) && property_exists($schemaData, 'enum') && \is_array($schemaData->enum)) { + $allowedValues = $schemaData->enum; + } elseif (\is_array($schemaData) && isset($schemaData['enum']) && \is_array($schemaData['enum'])) { + $allowedValues = $schemaData['enum']; + } else { + $this->logger->warning("MCP SDK: Could not retrieve 'enum' values from schema info for error.", ['error_args' => $args]); + } + if (empty($allowedValues)) { + $message = 'Value does not match the allowed enumeration.'; + } else { + $formattedAllowed = array_map(static function ($v) { /* ... formatting logic ... */ + if (\is_string($v)) { + return '"'.$v.'"'; + } + if (\is_bool($v)) { + return $v ? 'true' : 'false'; + } + if (null === $v) { + return 'null'; + } + + return (string) $v; + }, $allowedValues); + $message = 'Value must be one of the allowed values: '.implode(', ', $formattedAllowed).'.'; + } + break; + case 'const': + $expected = json_encode($args['expected'] ?? 'null', \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + $message = "Value must be equal to the constant value: {$expected}."; + break; + case 'minLength': // Corrected casing + $min = $args['min'] ?? '?'; + $message = "String must be at least {$min} characters long."; + break; + case 'maxLength': // Corrected casing + $max = $args['max'] ?? '?'; + $message = "String must not be longer than {$max} characters."; + break; + case 'pattern': + $pattern = $args['pattern'] ?? '?'; + $message = "String does not match the required pattern: `{$pattern}`."; + break; + case 'minimum': + $min = $args['min'] ?? '?'; + $message = "Number must be greater than or equal to {$min}."; + break; + case 'maximum': + $max = $args['max'] ?? '?'; + $message = "Number must be less than or equal to {$max}."; + break; + case 'exclusiveMinimum': // Corrected casing + $min = $args['min'] ?? '?'; + $message = "Number must be strictly greater than {$min}."; + break; + case 'exclusiveMaximum': // Corrected casing + $max = $args['max'] ?? '?'; + $message = "Number must be strictly less than {$max}."; + break; + case 'multipleOf': // Corrected casing + $value = $args['value'] ?? '?'; + $message = "Number must be a multiple of {$value}."; + break; + case 'minItems': // Corrected casing + $min = $args['min'] ?? '?'; + $message = "Array must contain at least {$min} items."; + break; + case 'maxItems': // Corrected casing + $max = $args['max'] ?? '?'; + $message = "Array must contain no more than {$max} items."; + break; + case 'uniqueItems': // Corrected casing + $message = 'Array items must be unique.'; + break; + case 'minProperties': // Corrected casing + $min = $args['min'] ?? '?'; + $message = "Object must have at least {$min} properties."; + break; + case 'maxProperties': // Corrected casing + $max = $args['max'] ?? '?'; + $message = "Object must have no more than {$max} properties."; + break; + case 'additionalProperties': // Corrected casing + $unexpected = $args['properties'] ?? []; + $formattedUnexpected = implode(', ', array_map(static fn ($p) => "`{$p}`", $unexpected)); + $message = "Object contains unexpected additional properties: {$formattedUnexpected}."; + break; + case 'format': + $format = $args['format'] ?? 'unknown'; + $message = "Value does not match the required format: `{$format}`."; + break; + default: + $builtInMessage = $error->message(); + if ($builtInMessage && 'The data must match the schema' !== $builtInMessage) { + $placeholders = $args; + $builtInMessage = preg_replace_callback('/\{(\w+)\}/', static function ($match) use ($placeholders) { + $key = $match[1]; + $value = $placeholders[$key] ?? '{'.$key.'}'; + + return \is_array($value) ? json_encode($value) : (string) $value; + }, $builtInMessage); + $message = $builtInMessage; + } + break; + } + + return $message; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Formatter/PromptResultFormatter.php b/admin/vendor/mcp/sdk/src/Capability/Formatter/PromptResultFormatter.php new file mode 100644 index 0000000..207c7e2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Formatter/PromptResultFormatter.php @@ -0,0 +1,211 @@ + + * @author Mateu Aguiló Bosch + */ +final class PromptResultFormatter +{ + /** + * Formats the raw result of a prompt generator into an array of MCP PromptMessages. + * + * @param mixed $promptGenerationResult expected: array of message structures + * + * @return PromptMessage[] array of PromptMessage objects + * + * @throws \RuntimeException if the result cannot be formatted + * @throws \JsonException if JSON encoding fails + */ + public function format(mixed $promptGenerationResult): array + { + if ($promptGenerationResult instanceof PromptMessage) { + return [$promptGenerationResult]; + } + + if (!\is_array($promptGenerationResult)) { + throw new RuntimeException('Prompt generator method must return an array of messages.'); + } + + if (empty($promptGenerationResult)) { + return []; + } + + if (\is_array($promptGenerationResult)) { + $allArePromptMessages = true; + $hasPromptMessages = false; + + foreach ($promptGenerationResult as $item) { + if ($item instanceof PromptMessage) { + $hasPromptMessages = true; + } else { + $allArePromptMessages = false; + } + } + + if ($allArePromptMessages && $hasPromptMessages) { + return $promptGenerationResult; + } + + if ($hasPromptMessages) { + $result = []; + foreach ($promptGenerationResult as $index => $item) { + if ($item instanceof PromptMessage) { + $result[] = $item; + } else { + $result = array_merge($result, $this->format($item)); + } + } + + return $result; + } + + if (!array_is_list($promptGenerationResult)) { + if (isset($promptGenerationResult['user']) || isset($promptGenerationResult['assistant'])) { + $result = []; + if (isset($promptGenerationResult['user'])) { + $userContent = $this->formatContent($promptGenerationResult['user']); + $result[] = new PromptMessage(Role::User, $userContent); + } + if (isset($promptGenerationResult['assistant'])) { + $assistantContent = $this->formatContent($promptGenerationResult['assistant']); + $result[] = new PromptMessage(Role::Assistant, $assistantContent); + } + + return $result; + } + + if (isset($promptGenerationResult['role']) && isset($promptGenerationResult['content'])) { + return [$this->formatMessage($promptGenerationResult)]; + } + + throw new RuntimeException('Associative array must contain either role/content keys or user/assistant keys.'); + } + + $formattedMessages = []; + foreach ($promptGenerationResult as $index => $message) { + if ($message instanceof PromptMessage) { + $formattedMessages[] = $message; + } else { + $formattedMessages[] = $this->formatMessage($message, $index); + } + } + + return $formattedMessages; + } + + throw new RuntimeException('Invalid prompt generation result format.'); + } + + /** + * Formats a single message into a PromptMessage. + */ + private function formatMessage(mixed $message, ?int $index = null): PromptMessage + { + $indexStr = null !== $index ? " at index {$index}" : ''; + + if (!\is_array($message) || !\array_key_exists('role', $message) || !\array_key_exists('content', $message)) { + throw new RuntimeException("Invalid message format{$indexStr}. Expected an array with 'role' and 'content' keys."); + } + + $role = $message['role'] instanceof Role ? $message['role'] : Role::tryFrom($message['role']); + if (null === $role) { + throw new RuntimeException("Invalid role '{$message['role']}' in prompt message{$indexStr}. Only 'user' or 'assistant' are supported."); + } + + $content = $this->formatContent($message['content'], $index); + + return new PromptMessage($role, $content); + } + + /** + * Formats content into a proper Content object. + */ + private function formatContent(mixed $content, ?int $index = null): TextContent|ImageContent|AudioContent|ResourceLink|EmbeddedResource + { + $indexStr = null !== $index ? " at index {$index}" : ''; + + if ($content instanceof Content) { + if ( + $content instanceof TextContent || $content instanceof ImageContent + || $content instanceof AudioContent || $content instanceof ResourceLink + || $content instanceof EmbeddedResource + ) { + return $content; + } + throw new RuntimeException("Invalid Content type{$indexStr}. PromptMessage only supports TextContent, ImageContent, AudioContent, ResourceLink, or EmbeddedResource."); + } + + if (\is_string($content)) { + return new TextContent($content); + } + + if (\is_array($content) && isset($content['type'])) { + return $this->formatTypedContent($content, $index); + } + + if (\is_scalar($content) || null === $content) { + $stringContent = null === $content ? '(null)' : (\is_bool($content) ? ($content ? 'true' : 'false') : (string) $content); + + return new TextContent($stringContent); + } + + $jsonContent = json_encode($content, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR); + + return new TextContent($jsonContent); + } + + /** + * Formats typed content arrays into Content objects. + * + * Delegates to the schema classes' fromArray() so optional fields + * (annotations, mimeType, _meta, ...) carry over instead of being dropped. + * + * @param array $content + */ + private function formatTypedContent(array $content, ?int $index = null): TextContent|ImageContent|AudioContent|ResourceLink|EmbeddedResource + { + $indexStr = null !== $index ? " at index {$index}" : ''; + $type = $content['type']; + + if ('resource' === $type && isset($content['resource']) && \is_array($content['resource']) && !isset($content['resource']['mimeType'])) { + // EmbeddedResource::fromArray() leaves a missing mimeType unset; this + // formatter has always defaulted it, so keep that for compatibility. + $content['resource']['mimeType'] = isset($content['resource']['text']) ? 'text/plain' : 'application/octet-stream'; + } + + try { + return match ($type) { + 'text' => TextContent::fromArray($content), + 'image' => ImageContent::fromArray($content), + 'audio' => AudioContent::fromArray($content), + 'resource' => EmbeddedResource::fromArray($content), + 'resource_link' => ResourceLink::fromArray($content), + default => throw new RuntimeException("Invalid content type '{$type}'{$indexStr}."), + }; + } catch (InvalidArgumentException $e) { + throw new RuntimeException("Invalid '{$type}' content{$indexStr}: {$e->getMessage()}", 0, $e); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Formatter/ResourceResultFormatter.php b/admin/vendor/mcp/sdk/src/Capability/Formatter/ResourceResultFormatter.php new file mode 100644 index 0000000..bac2bc7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Formatter/ResourceResultFormatter.php @@ -0,0 +1,200 @@ + + * @author Mateu Aguiló Bosch + */ +final class ResourceResultFormatter +{ + /** + * Formats the raw result of a resource read operation into MCP ResourceContent items. + * + * @param mixed $readResult the raw result from the resource handler method + * @param string $uri the URI of the resource that was read + * @param string|null $mimeType the MIME type from the ResourceDefinition + * @param mixed $meta optional metadata to include in the ResourceContents + * + * @return ResourceContents[] array of ResourceContents objects + * + * @throws RuntimeException If the result cannot be formatted. + * + * Supported result types: + * - ResourceContents: Used as-is + * - EmbeddedResource: Resource is extracted from the EmbeddedResource + * - string: Converted to text content with guessed or provided MIME type + * - stream resource: Read and converted to blob with provided MIME type + * - array with 'blob' key: Used as blob content + * - array with 'text' key: Used as text content + * - SplFileInfo: Read and converted to blob + * - array: Converted to JSON if MIME type is application/json or contains 'json' + * For other MIME types, will try to convert to JSON with a warning + */ + public function format(mixed $readResult, string $uri, ?string $mimeType = null, mixed $meta = null): array + { + if ($readResult instanceof ResourceContents) { + return [$readResult]; + } + + if ($readResult instanceof EmbeddedResource) { + return [$readResult->resource]; + } + + if (\is_array($readResult)) { + if (empty($readResult)) { + return [new TextResourceContents($uri, 'application/json', '[]', $meta)]; + } + + $allAreResourceContents = true; + $hasResourceContents = false; + $allAreEmbeddedResource = true; + $hasEmbeddedResource = false; + + foreach ($readResult as $item) { + if ($item instanceof ResourceContents) { + $hasResourceContents = true; + $allAreEmbeddedResource = false; + } elseif ($item instanceof EmbeddedResource) { + $hasEmbeddedResource = true; + $allAreResourceContents = false; + } else { + $allAreResourceContents = false; + $allAreEmbeddedResource = false; + } + } + + if ($allAreResourceContents && $hasResourceContents) { + return $readResult; + } + + if ($allAreEmbeddedResource && $hasEmbeddedResource) { + return array_map(static fn ($item) => $item->resource, $readResult); + } + + if ($hasResourceContents || $hasEmbeddedResource) { + $result = []; + foreach ($readResult as $item) { + if ($item instanceof ResourceContents) { + $result[] = $item; + } elseif ($item instanceof EmbeddedResource) { + $result[] = $item->resource; + } else { + $result = array_merge($result, $this->format($item, $uri, $mimeType, $meta)); + } + } + + return $result; + } + } + + if (\is_string($readResult)) { + $mimeType = $mimeType ?? $this->guessMimeTypeFromString($readResult); + + return [new TextResourceContents($uri, $mimeType, $readResult, $meta)]; + } + + if (\is_resource($readResult) && 'stream' === get_resource_type($readResult)) { + $result = BlobResourceContents::fromStream( + $uri, + $readResult, + $mimeType ?? 'application/octet-stream', + $meta + ); + + @fclose($readResult); + + return [$result]; + } + + if (\is_array($readResult) && isset($readResult['blob']) && \is_string($readResult['blob'])) { + $mimeType = $readResult['mimeType'] ?? $mimeType ?? 'application/octet-stream'; + + return [new BlobResourceContents($uri, $mimeType, $readResult['blob'], $meta)]; + } + + if (\is_array($readResult) && isset($readResult['text']) && \is_string($readResult['text'])) { + $mimeType = $readResult['mimeType'] ?? $mimeType ?? 'text/plain'; + + return [new TextResourceContents($uri, $mimeType, $readResult['text'], $meta)]; + } + + if ($readResult instanceof \SplFileInfo && $readResult->isFile() && $readResult->isReadable()) { + if ($mimeType && str_contains(strtolower($mimeType), 'text')) { + return [new TextResourceContents($uri, $mimeType, file_get_contents($readResult->getPathname()), $meta)]; + } + + return [BlobResourceContents::fromSplFileInfo($uri, $readResult, $mimeType, $meta)]; + } + + if (\is_array($readResult)) { + if ($mimeType && (str_contains(strtolower($mimeType), 'json') + || 'application/json' === $mimeType)) { + try { + $jsonString = json_encode($readResult, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT); + + return [new TextResourceContents($uri, $mimeType, $jsonString, $meta)]; + } catch (\JsonException $e) { + throw new RuntimeException(\sprintf('Failed to encode array as JSON for URI "%s": %s', $uri, $e->getMessage())); + } + } + + try { + $jsonString = json_encode($readResult, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT); + $mimeType = $mimeType ?? 'application/json'; + + return [new TextResourceContents($uri, $mimeType, $jsonString, $meta)]; + } catch (\JsonException $e) { + throw new RuntimeException(\sprintf('Failed to encode array as JSON for URI "%s": %s', $uri, $e->getMessage())); + } + } + + throw new RuntimeException(\sprintf('Cannot format resource read result for URI "%s". Handler method returned unhandled type: ', $uri).\gettype($readResult)); + } + + /** + * Guesses MIME type from string content (very basic). + */ + private function guessMimeTypeFromString(string $content): string + { + $trimmed = ltrim($content); + + if (str_starts_with($trimmed, '<') && str_ends_with(rtrim($content), '>')) { + if (str_contains($trimmed, ' + * @author Mateu Aguiló Bosch + */ +final class ToolResultFormatter +{ + /** + * Formats the result of a tool execution into an array of MCP Content items. + * + * - If the result is already a Content object, it's wrapped in an array. + * - If the result is an array: + * - If all elements are Content objects, the array is returned as is. + * - If it's a mixed array (Content and non-Content items), non-Content items are + * individually formatted (scalars to TextContent, others to JSON TextContent). + * - If it's an array with no Content items, the entire array is JSON-encoded into a single TextContent. + * - Scalars (string, int, float, bool) are wrapped in TextContent. + * - null is represented as TextContent('(null)'). + * - Other objects are JSON-encoded and wrapped in TextContent. + * + * @param mixed $toolExecutionResult the raw value returned by the tool's PHP method + * + * @return Content[] the content items for CallToolResult + * + * @throws \JsonException if JSON encoding fails for non-Content array/object results + */ + public function format(mixed $toolExecutionResult): array + { + if ($toolExecutionResult instanceof Content) { + return [$toolExecutionResult]; + } + + if (\is_array($toolExecutionResult)) { + if (empty($toolExecutionResult)) { + return [new TextContent('[]')]; + } + + $allAreContent = true; + $hasContent = false; + + foreach ($toolExecutionResult as $item) { + if ($item instanceof Content) { + $hasContent = true; + } else { + $allAreContent = false; + } + } + + if ($allAreContent && $hasContent) { + return $toolExecutionResult; + } + + if ($hasContent) { + $result = []; + foreach ($toolExecutionResult as $item) { + if ($item instanceof Content) { + $result[] = $item; + } else { + $result = array_merge($result, $this->format($item)); + } + } + + return $result; + } + } + + if (null === $toolExecutionResult) { + return [new TextContent('(null)')]; + } + + if (\is_bool($toolExecutionResult)) { + return [new TextContent($toolExecutionResult ? 'true' : 'false')]; + } + + if (\is_scalar($toolExecutionResult)) { + return [new TextContent($toolExecutionResult)]; + } + + $jsonResult = json_encode( + $toolExecutionResult, + \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR | \JSON_INVALID_UTF8_SUBSTITUTE + ); + + return [new TextContent($jsonResult)]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Logger/ClientLogger.php b/admin/vendor/mcp/sdk/src/Capability/Logger/ClientLogger.php new file mode 100644 index 0000000..f94deb6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Logger/ClientLogger.php @@ -0,0 +1,82 @@ + + * @author Tobias Nyholm + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +final class ClientLogger extends AbstractLogger +{ + public function __construct( + private ClientGateway $client, + private SessionInterface $session, + ) { + } + + /** + * Logs with an arbitrary level. + * + * @param string|\Stringable $message + * @param array $context + */ + public function log($level, $message, array $context = []): void + { + // Convert PSR-3 level to MCP LoggingLevel + $mcpLevel = $this->convertToMcpLevel($level); + if (null === $mcpLevel) { + return; // Unknown level, skip MCP notification + } + + $minimumLevel = $this->session->get(Protocol::SESSION_LOGGING_LEVEL, ''); + $minimumLevel = LoggingLevel::tryFrom($minimumLevel) ?? LoggingLevel::Warning; + + if (!$mcpLevel->isAtLeast($minimumLevel)) { + return; + } + + $this->client->log($mcpLevel, $message); + } + + /** + * Converts PSR-3 log level to MCP LoggingLevel. + * + * @param mixed $level PSR-3 level + * + * @return LoggingLevel|null MCP level or null if unknown + */ + private function convertToMcpLevel($level): ?LoggingLevel + { + return match (strtolower((string) $level)) { + 'emergency' => LoggingLevel::Emergency, + 'alert' => LoggingLevel::Alert, + 'critical' => LoggingLevel::Critical, + 'error' => LoggingLevel::Error, + 'warning' => LoggingLevel::Warning, + 'notice' => LoggingLevel::Notice, + 'info' => LoggingLevel::Info, + 'debug' => LoggingLevel::Debug, + default => null, + }; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry.php b/admin/vendor/mcp/sdk/src/Capability/Registry.php new file mode 100644 index 0000000..9784043 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry.php @@ -0,0 +1,480 @@ + + */ +final class Registry implements RegistryInterface +{ + /** + * @var array + */ + private array $tools = []; + + /** + * @var array + */ + private array $resources = []; + + /** + * @var array + */ + private array $prompts = []; + + /** + * @var array + */ + private array $resourceTemplates = []; + + private bool $loaded = false; + + private bool $loading = false; + + public function __construct( + private readonly ?EventDispatcherInterface $eventDispatcher = null, + private readonly LoggerInterface $logger = new NullLogger(), + private readonly NameValidator $nameValidator = new NameValidator(), + private readonly ?LoaderInterface $loader = null, + ) { + } + + /** + * Runs the configured loader once, on demand. Reads trigger this automatically, so element + * loading is deferred to the first read (request time) rather than eager at build time — under a + * persistent runtime a source not yet ready at build no longer freezes the registry empty. + * + * `loaded` is set only after success, so a transient failure is retried on the next read. The + * `loading` guard lets a loader read the registry during its own run (e.g. discovery's identity + * check) without re-entering the load. + */ + public function load(): void + { + if ($this->loaded || $this->loading || null === $this->loader) { + return; + } + + $this->loadFrom($this->loader); + + $this->loaded = true; + } + + /** + * Runs $loader with the change events its registrations would dispatch suppressed, since they + * describe the registry filling up rather than changing. + * + * Re-entrant-safe, and failure propagates. Does not mark the registry loaded: $loader is the + * caller's, and the one the constructor took is still owed its run. + */ + public function loadFrom(LoaderInterface $loader): void + { + if ($this->loading) { + return; + } + + $this->loading = true; + try { + $loader->load($this); + } finally { + $this->loading = false; + } + } + + public function registerTool(Tool $tool, callable|array|string $handler): ToolReference + { + if (!$this->nameValidator->isValid($tool->name)) { + $this->logger->warning( + \sprintf('Tool name "%s" is invalid. Tool names should only contain letters (a-z, A-Z), numbers, dots, hyphens, underscores, and forward slashes.', $tool->name), + ); + } + + $reference = new ToolReference($tool, $handler); + $this->tools[$tool->name] = $reference; + + $this->dispatch(new ToolListChangedEvent()); + + return $reference; + } + + public function registerResource(ResourceDefinition $resource, callable|array|string $handler): ResourceReference + { + $reference = new ResourceReference($resource, $handler); + $this->resources[$resource->uri] = $reference; + + $this->dispatch(new ResourceListChangedEvent()); + + return $reference; + } + + public function registerResourceTemplate( + ResourceTemplate $template, + callable|array|string $handler, + array $completionProviders = [], + ): ResourceTemplateReference { + $reference = new ResourceTemplateReference($template, $handler, $completionProviders); + $this->resourceTemplates[$template->uriTemplate] = $reference; + + $this->dispatch(new ResourceTemplateListChangedEvent()); + + return $reference; + } + + public function registerPrompt( + Prompt $prompt, + callable|array|string $handler, + array $completionProviders = [], + ): PromptReference { + $reference = new PromptReference($prompt, $handler, $completionProviders); + $this->prompts[$prompt->name] = $reference; + + $this->dispatch(new PromptListChangedEvent()); + + return $reference; + } + + public function unregisterTool(string $name): void + { + if (!isset($this->tools[$name])) { + return; + } + + unset($this->tools[$name]); + + $this->dispatch(new ToolListChangedEvent()); + } + + public function unregisterResource(string $uri): void + { + if (!isset($this->resources[$uri])) { + return; + } + + unset($this->resources[$uri]); + + $this->dispatch(new ResourceListChangedEvent()); + } + + public function unregisterResourceTemplate(string $uriTemplate): void + { + if (!isset($this->resourceTemplates[$uriTemplate])) { + return; + } + + unset($this->resourceTemplates[$uriTemplate]); + + $this->dispatch(new ResourceTemplateListChangedEvent()); + } + + public function unregisterPrompt(string $name): void + { + if (!isset($this->prompts[$name])) { + return; + } + + unset($this->prompts[$name]); + + $this->dispatch(new PromptListChangedEvent()); + } + + public function hasTool(string $name): bool + { + $this->load(); + + return isset($this->tools[$name]); + } + + public function hasResource(string $uri): bool + { + $this->load(); + + return isset($this->resources[$uri]); + } + + public function hasResourceTemplate(string $uriTemplate): bool + { + $this->load(); + + return isset($this->resourceTemplates[$uriTemplate]); + } + + public function hasPrompt(string $name): bool + { + $this->load(); + + return isset($this->prompts[$name]); + } + + public function hasTools(): bool + { + $this->load(); + + return [] !== $this->tools; + } + + public function getTools(?int $limit = null, ?string $cursor = null): Page + { + $this->load(); + + $tools = []; + foreach ($this->tools as $toolReference) { + $tools[$toolReference->tool->name] = $toolReference->tool; + } + + if (null === $limit) { + return new Page($tools, null); + } + + $paginatedTools = $this->paginateResults($tools, $limit, $cursor); + + $nextCursor = $this->calculateNextCursor( + \count($tools), + $cursor, + $limit + ); + + return new Page($paginatedTools, $nextCursor); + } + + public function getTool(string $name): ToolReference + { + $this->load(); + + return $this->tools[$name] ?? throw new ToolNotFoundException($name); + } + + public function hasResources(): bool + { + $this->load(); + + return [] !== $this->resources; + } + + public function getResources(?int $limit = null, ?string $cursor = null): Page + { + $this->load(); + + $resources = []; + foreach ($this->resources as $resourceReference) { + $resources[$resourceReference->resource->uri] = $resourceReference->resource; + } + + if (null === $limit) { + return new Page($resources, null); + } + + $paginatedResources = $this->paginateResults($resources, $limit, $cursor); + + $nextCursor = $this->calculateNextCursor( + \count($resources), + $cursor, + $limit + ); + + return new Page($paginatedResources, $nextCursor); + } + + public function getResource( + string $uri, + bool $includeTemplates = true, + ): ResourceReference|ResourceTemplateReference { + $this->load(); + + $registration = $this->resources[$uri] ?? null; + if ($registration) { + return $registration; + } + + if ($includeTemplates) { + foreach ($this->resourceTemplates as $template) { + if ($template->matches($uri)) { + return $template; + } + } + } + + $this->logger->debug('No resource matched URI.', ['uri' => $uri]); + + throw new ResourceNotFoundException($uri); + } + + public function hasResourceTemplates(): bool + { + $this->load(); + + return [] !== $this->resourceTemplates; + } + + public function getResourceTemplates(?int $limit = null, ?string $cursor = null): Page + { + $this->load(); + + $templates = []; + foreach ($this->resourceTemplates as $templateReference) { + $templates[$templateReference->resourceTemplate->uriTemplate] = $templateReference->resourceTemplate; + } + + if (null === $limit) { + return new Page($templates, null); + } + + $paginatedTemplates = $this->paginateResults($templates, $limit, $cursor); + + $nextCursor = $this->calculateNextCursor( + \count($templates), + $cursor, + $limit + ); + + return new Page($paginatedTemplates, $nextCursor); + } + + public function getResourceTemplate(string $uriTemplate): ResourceTemplateReference + { + $this->load(); + + return $this->resourceTemplates[$uriTemplate] ?? throw new ResourceNotFoundException($uriTemplate); + } + + public function hasPrompts(): bool + { + $this->load(); + + return [] !== $this->prompts; + } + + public function getPrompts(?int $limit = null, ?string $cursor = null): Page + { + $this->load(); + + $prompts = []; + foreach ($this->prompts as $promptReference) { + $prompts[$promptReference->prompt->name] = $promptReference->prompt; + } + + if (null === $limit) { + return new Page($prompts, null); + } + + $paginatedPrompts = $this->paginateResults($prompts, $limit, $cursor); + + $nextCursor = $this->calculateNextCursor( + \count($prompts), + $cursor, + $limit + ); + + return new Page($paginatedPrompts, $nextCursor); + } + + public function getPrompt(string $name): PromptReference + { + $this->load(); + + return $this->prompts[$name] ?? throw new PromptNotFoundException($name); + } + + /** + * Suppressed while the deferred loader runs, since it only sets up initial state. + */ + private function dispatch(object $event): void + { + if ($this->loading) { + return; + } + + $this->eventDispatcher?->dispatch($event); + } + + /** + * Calculate next cursor for pagination. + * + * @param int $totalItems Count of all items + * @param string|null $currentCursor Current cursor position + * @param int $limit Number requested/returned per page + */ + private function calculateNextCursor(int $totalItems, ?string $currentCursor, int $limit): ?string + { + $currentOffset = 0; + + if (null !== $currentCursor) { + $decodedCursor = base64_decode($currentCursor, true); + if (false !== $decodedCursor && is_numeric($decodedCursor)) { + $currentOffset = (int) $decodedCursor; + } + } + + $nextOffset = $currentOffset + $limit; + + if ($nextOffset < $totalItems) { + return base64_encode((string) $nextOffset); + } + + return null; + } + + /** + * Helper method to paginate results using cursor-based pagination. + * + * @param array $items The full array of items to paginate The full array of items to paginate + * @param int $limit Maximum number of items to return + * @param string|null $cursor Base64 encoded offset position + * + * @return array Paginated results + * + * @throws InvalidCursorException When cursor is invalid (MCP error code -32602) + */ + private function paginateResults(array $items, int $limit, ?string $cursor = null): array + { + $offset = 0; + if (null !== $cursor) { + $decodedCursor = base64_decode($cursor, true); + + if (false === $decodedCursor || !is_numeric($decodedCursor)) { + throw new InvalidCursorException($cursor); + } + + $offset = (int) $decodedCursor; + + // Validate offset is within reasonable bounds + if ($offset < 0 || $offset > \count($items)) { + throw new InvalidCursorException($cursor); + } + } + + return array_values(\array_slice($items, $offset, $limit)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Container.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Container.php new file mode 100644 index 0000000..92c4a90 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Container.php @@ -0,0 +1,188 @@ + + */ +final class Container implements ContainerInterface +{ + /** + * @var array Cache for already created instances (shared singletons) + */ + private array $instances = []; + + /** + * @var array Track classes currently being resolved to detect circular dependencies + */ + private array $resolving = []; + + /** + * Finds an entry of the container by its identifier and returns it. + * + * @param string $id identifier of the entry to look for (usually a FQCN) + * + * @return mixed entry + * + * @throws NotFoundExceptionInterface no entry was found for **this** identifier + * @throws ContainerExceptionInterface Error while retrieving the entry (e.g., dependency resolution failure, circular dependency). + */ + public function get(string $id): mixed + { + // 1. Check instance cache + if (isset($this->instances[$id])) { + return $this->instances[$id]; + } + + // 2. Check if class exists + if (!class_exists($id) && !interface_exists($id)) { // Also check interface for bindings + throw new ServiceNotFoundException(\sprintf('Class, interface, or entry "%s" not found.', $id)); + } + + // 7. Circular Dependency Check + if (isset($this->resolving[$id])) { + throw new ContainerException("Circular dependency detected while resolving '{$id}'. Resolution path: ".implode(' -> ', array_keys($this->resolving))." -> {$id}"); + } + + $this->resolving[$id] = true; // Mark as currently resolving + + try { + // 3. Reflect on the class + $reflector = new \ReflectionClass($id); + + // Check if class is instantiable (abstract classes, interfaces cannot be directly instantiated) + if (!$reflector->isInstantiable()) { + // We might have an interface bound to a concrete class via set() + // This check is slightly redundant due to class_exists but good practice + throw new ContainerException("Class '{$id}' is not instantiable (e.g., abstract class or interface without explicit binding)."); + } + + // 4. Get the constructor + $constructor = $reflector->getConstructor(); + + // 5. If no constructor or constructor has no parameters, instantiate directly + if (null === $constructor || 0 === $constructor->getNumberOfParameters()) { + $instance = $reflector->newInstance(); + } else { + // 6. Constructor has parameters, attempt to resolve them + $parameters = $constructor->getParameters(); + $resolvedArgs = []; + + foreach ($parameters as $parameter) { + $resolvedArgs[] = $this->resolveParameter($parameter, $id); + } + + // Instantiate with resolved arguments + $instance = $reflector->newInstanceArgs($resolvedArgs); + } + + // Cache the instance + $this->instances[$id] = $instance; + + return $instance; + } catch (\ReflectionException $e) { + throw new ContainerException(\sprintf('Reflection failed for %s.', $id), 0, $e); + } catch (ContainerExceptionInterface $e) { // Re-throw container exceptions directly + throw $e; + } catch (\Throwable $e) { // Catch other instantiation errors + throw new ContainerException("Failed to instantiate or resolve dependencies for '{$id}': ".$e->getMessage(), (int) $e->getCode(), $e); + } finally { + // 7. Remove from resolving stack once done (success or failure) + unset($this->resolving[$id]); + } + } + + /** + * Attempts to resolve a single constructor parameter. + * + * @throws ContainerExceptionInterface if a required dependency cannot be resolved + */ + private function resolveParameter(\ReflectionParameter $parameter, string $consumerClassId): mixed + { + // Check for type hint + $type = $parameter->getType(); + + if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { + // Type hint is a class or interface name + $typeName = $type->getName(); + try { + // Recursively get the dependency + return $this->get($typeName); + } catch (NotFoundExceptionInterface $e) { + // Dependency class not found, fail ONLY if required + if (!$parameter->isOptional() && !$parameter->allowsNull()) { + throw new ContainerException("Unresolvable dependency '{$typeName}' required by '{$consumerClassId}' constructor parameter \${$parameter->getName()}.", 0, $e); + } + // If optional or nullable, proceed (will check allowsNull/Default below) + } catch (ContainerExceptionInterface $e) { + // Dependency itself failed to resolve (e.g., its own deps, circular) + throw new ContainerException("Failed to resolve dependency '{$typeName}' for '{$consumerClassId}' parameter \${$parameter->getName()}: ".$e->getMessage(), 0, $e); + } + } + + // Check if parameter has a default value + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + // Check if parameter allows null (and wasn't resolved above) + if ($parameter->allowsNull()) { + return null; + } + + // Check if it was a built-in type without a default (unresolvable by this basic container) + if ($type instanceof \ReflectionNamedType && $type->isBuiltin()) { + throw new ContainerException("Cannot auto-wire built-in type '{$type->getName()}' for required parameter \${$parameter->getName()} in '{$consumerClassId}' constructor. Provide a default value or use a more advanced container."); + } + + // Check if it was a union/intersection type without a default (also unresolvable) + if (null !== $type && !$type instanceof \ReflectionNamedType) { + throw new ContainerException("Cannot auto-wire complex type (union/intersection) for required parameter \${$parameter->getName()} in '{$consumerClassId}' constructor. Provide a default value or use a more advanced container."); + } + + // If we reach here, it's an untyped, required parameter without a default. + // Or potentially an unresolvable optional class dependency where null is not allowed (edge case). + throw new ContainerException("Cannot resolve required parameter \${$parameter->getName()} for '{$consumerClassId}' constructor (untyped or unresolvable complex type)."); + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Checks explicitly set instances and if the class/interface exists. + * Does not guarantee `get()` will succeed if auto-wiring fails. + */ + public function has(string $id): bool + { + return isset($this->instances[$id]) || class_exists($id) || interface_exists($id); + } + + /** + * Adds a pre-built instance or a factory/binding to the container. + * This basic version only supports pre-built instances (singletons). + */ + public function set(string $id, object $instance): void + { + // Could add support for closures/factories later if needed + $this->instances[$id] = $instance; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ElementReference.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ElementReference.php new file mode 100644 index 0000000..a49b2eb --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ElementReference.php @@ -0,0 +1,28 @@ + + */ +class ElementReference +{ + /** + * @param Handler $handler + */ + public function __construct( + public readonly \Closure|array|string $handler, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/InjectableParameters.php b/admin/vendor/mcp/sdk/src/Capability/Registry/InjectableParameters.php new file mode 100644 index 0000000..4e8eba2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/InjectableParameters.php @@ -0,0 +1,63 @@ + $arguments the raw argument bag including the internal "_session" and "_request" entries + */ + public static function resolve(string $typeName, array $arguments): ?object + { + if (RequestContext::class === $typeName && isset($arguments['_session'], $arguments['_request'])) { + return new RequestContext($arguments['_session'], $arguments['_request']); + } + + if (ClientGateway::class === $typeName && isset($arguments['_session'])) { + return new ClientGateway($arguments['_session']); + } + + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ChainLoader.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ChainLoader.php new file mode 100644 index 0000000..6c30a16 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ChainLoader.php @@ -0,0 +1,38 @@ + + */ +final class ChainLoader implements LoaderInterface +{ + /** + * @param LoaderInterface[] $loaders + */ + public function __construct( + private readonly array $loaders, + ) { + } + + public function load(RegistryInterface $registry): void + { + foreach ($this->loaders as $loader) { + $loader->load($registry); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/DiscoveryLoader.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/DiscoveryLoader.php new file mode 100644 index 0000000..d2b9538 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/DiscoveryLoader.php @@ -0,0 +1,185 @@ + + */ +final class DiscoveryLoader implements LoaderInterface +{ + private DiscoveryState $owned; + + /** + * @param string[] $scanDirs + * @param string[] $excludeDirs + * @param string[] $namePatterns + */ + public function __construct( + private string $basePath, + private array $scanDirs, + private array $excludeDirs, + private DiscovererInterface $discoverer, + private array $namePatterns = DiscovererInterface::DEFAULT_NAME_PATERNS, + private LoggerInterface $logger = new NullLogger(), + ) { + $this->owned = new DiscoveryState(); + } + + public function load(RegistryInterface $registry): void + { + $discovered = $this->discoverer->discover($this->basePath, $this->scanDirs, $this->excludeDirs, $this->namePatterns); + + $this->unregisterOwned($registry, $this->owned->obsoletedBy($discovered)); + $this->owned = $this->writeDiscovered($registry, $discovered); + } + + /** + * Unregisters entries we previously wrote that the registry still attributes to us. + * Entries overwritten by someone else are left untouched (identity check fails). + */ + private function unregisterOwned(RegistryInterface $registry, DiscoveryState $obsolete): void + { + foreach ($obsolete->getTools() as $name => $owned) { + if ($registry->hasTool($name) && $registry->getTool($name) === $owned) { + $registry->unregisterTool($name); + } + } + foreach ($obsolete->getResources() as $uri => $owned) { + if ($registry->hasResource($uri) && $registry->getResource($uri, false) === $owned) { + $registry->unregisterResource($uri); + } + } + foreach ($obsolete->getResourceTemplates() as $uriTemplate => $owned) { + if ($registry->hasResourceTemplate($uriTemplate) && $registry->getResourceTemplate($uriTemplate) === $owned) { + $registry->unregisterResourceTemplate($uriTemplate); + } + } + foreach ($obsolete->getPrompts() as $name => $owned) { + if ($registry->hasPrompt($name) && $registry->getPrompt($name) === $owned) { + $registry->unregisterPrompt($name); + } + } + } + + /** + * Writes the discovered state into the registry, skipping entries that a conflicting + * registration already holds. Returns the new owned state (only the writes we actually performed). + */ + private function writeDiscovered(RegistryInterface $registry, DiscoveryState $discovered): DiscoveryState + { + $tools = []; + foreach ($discovered->getTools() as $name => $reference) { + if (!$this->mayWriteTool($registry, $name)) { + continue; + } + $tools[$name] = $registry->registerTool($reference->tool, $reference->handler); + } + + $resources = []; + foreach ($discovered->getResources() as $uri => $reference) { + if (!$this->mayWriteResource($registry, $uri)) { + continue; + } + $resources[$uri] = $registry->registerResource($reference->resource, $reference->handler); + } + + $resourceTemplates = []; + foreach ($discovered->getResourceTemplates() as $uriTemplate => $reference) { + if (!$this->mayWriteResourceTemplate($registry, $uriTemplate)) { + continue; + } + $resourceTemplates[$uriTemplate] = $registry->registerResourceTemplate( + $reference->resourceTemplate, + $reference->handler, + $reference->completionProviders, + ); + } + + $prompts = []; + foreach ($discovered->getPrompts() as $name => $reference) { + if (!$this->mayWritePrompt($registry, $name)) { + continue; + } + $prompts[$name] = $registry->registerPrompt( + $reference->prompt, + $reference->handler, + $reference->completionProviders, + ); + } + + return new DiscoveryState($tools, $resources, $prompts, $resourceTemplates); + } + + private function mayWriteTool(RegistryInterface $registry, string $name): bool + { + if (!$registry->hasTool($name) || $registry->getTool($name) === ($this->owned->getTools()[$name] ?? null)) { + return true; + } + + $this->logger->debug(\sprintf( + 'Ignoring discovered tool "%s": a conflicting manual or runtime registration already exists.', + $name, + )); + + return false; + } + + private function mayWriteResource(RegistryInterface $registry, string $uri): bool + { + if (!$registry->hasResource($uri) || $registry->getResource($uri, false) === ($this->owned->getResources()[$uri] ?? null)) { + return true; + } + + $this->logger->debug(\sprintf( + 'Ignoring discovered resource "%s": a conflicting manual or runtime registration already exists.', + $uri, + )); + + return false; + } + + private function mayWriteResourceTemplate(RegistryInterface $registry, string $uriTemplate): bool + { + if (!$registry->hasResourceTemplate($uriTemplate) || $registry->getResourceTemplate($uriTemplate) === ($this->owned->getResourceTemplates()[$uriTemplate] ?? null)) { + return true; + } + + $this->logger->debug(\sprintf( + 'Ignoring discovered resource template "%s": a conflicting manual or runtime registration already exists.', + $uriTemplate, + )); + + return false; + } + + private function mayWritePrompt(RegistryInterface $registry, string $name): bool + { + if (!$registry->hasPrompt($name) || $registry->getPrompt($name) === ($this->owned->getPrompts()[$name] ?? null)) { + return true; + } + + $this->logger->debug(\sprintf( + 'Ignoring discovered prompt "%s": a conflicting manual or runtime registration already exists.', + $name, + )); + + return false; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ExplicitElementLoader.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ExplicitElementLoader.php new file mode 100644 index 0000000..4733ab9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ExplicitElementLoader.php @@ -0,0 +1,105 @@ + + */ +final class ExplicitElementLoader implements LoaderInterface +{ + /** + * @param list $tools + * @param list $resources + * @param list}> $resourceTemplates + * @param list}> $prompts + */ + public function __construct( + private readonly array $tools = [], + private readonly array $resources = [], + private readonly array $resourceTemplates = [], + private readonly array $prompts = [], + ) { + } + + public function load(RegistryInterface $registry): void + { + foreach ($this->tools as $entry) { + $handler = $entry['handler']; + $registry->registerTool($entry['definition'], $this->boundClosure( + static function (array $arguments) use ($handler): mixed { + $gateway = new ClientGateway($arguments['_session']); + unset($arguments['_session'], $arguments['_request']); + + return $handler->execute($arguments, $gateway); + }, + )); + } + + foreach ($this->resources as $entry) { + $handler = $entry['handler']; + $registry->registerResource($entry['definition'], $this->boundClosure( + static fn (array $arguments): mixed => $handler->read( + $arguments['uri'], + new ClientGateway($arguments['_session']), + ), + )); + } + + foreach ($this->resourceTemplates as $entry) { + $handler = $entry['handler']; + $registry->registerResourceTemplate($entry['definition'], $this->boundClosure( + static function (array $arguments) use ($handler): mixed { + $gateway = new ClientGateway($arguments['_session']); + $uri = $arguments['uri']; + unset($arguments['_session'], $arguments['_request'], $arguments['uri']); + + return $handler->read($uri, $arguments, $gateway); + }, + ), $entry['completionProviders']); + } + + foreach ($this->prompts as $entry) { + $handler = $entry['handler']; + $registry->registerPrompt($entry['definition'], $this->boundClosure( + static function (array $arguments) use ($handler): mixed { + $gateway = new ClientGateway($arguments['_session']); + unset($arguments['_session'], $arguments['_request']); + + return $handler->get($arguments, $gateway); + }, + ), $entry['completionProviders']); + } + } + + private function boundClosure(\Closure $closure): \Closure + { + return \Closure::bind($closure, null, ReferenceHandler::class); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/LoaderInterface.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/LoaderInterface.php new file mode 100644 index 0000000..a7ad87c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/LoaderInterface.php @@ -0,0 +1,22 @@ + + */ +interface LoaderInterface +{ + public function load(RegistryInterface $registry): void; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ReflectedElementLoader.php b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ReflectedElementLoader.php new file mode 100644 index 0000000..d1e849d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/Loader/ReflectedElementLoader.php @@ -0,0 +1,322 @@ + + * + * @phpstan-import-type Handler from ElementReference + */ +final class ReflectedElementLoader implements LoaderInterface +{ + /** + * @param array{ + * handler: Handler, + * name: ?string, + * title: ?string, + * description: ?string, + * annotations: ?ToolAnnotations, + * icons: ?Icon[], + * meta: ?array, + * outputSchema: ?array + * }[] $tools + * @param array{ + * handler: Handler, + * uri: string, + * name: ?string, + * title: ?string, + * description: ?string, + * mimeType: ?string, + * size: int|null, + * annotations: ?Annotations, + * icons: ?Icon[], + * meta: ?array + * }[] $resources + * @param array{ + * handler: Handler, + * uriTemplate: string, + * name: ?string, + * title: ?string, + * description: ?string, + * mimeType: ?string, + * annotations: ?Annotations, + * meta: ?array + * }[] $resourceTemplates + * @param array{ + * handler: Handler, + * name: ?string, + * description: ?string, + * icons: ?Icon[], + * meta: ?array + * }[] $prompts + */ + public function __construct( + private readonly array $tools = [], + private readonly array $resources = [], + private readonly array $resourceTemplates = [], + private readonly array $prompts = [], + private LoggerInterface $logger = new NullLogger(), + private ?SchemaGeneratorInterface $schemaGenerator = null, + ) { + } + + public function load(RegistryInterface $registry): void + { + $docBlockParser = new DocBlockParser(logger: $this->logger); + $schemaGenerator = $this->schemaGenerator ?? new SchemaGenerator($docBlockParser); + + // Register Tools + foreach ($this->tools as $data) { + try { + $reflection = HandlerResolver::resolve($data['handler']); + + if ($reflection instanceof \ReflectionFunction) { + $name = $data['name'] ?? 'closure_tool_'.spl_object_id($data['handler']); + $description = $data['description'] ?? null; + } else { + $name = ElementMetadataResolver::resolveName($reflection, $data['name'] ?? null); + $description = ElementMetadataResolver::resolveDescription($reflection, $data['description'] ?? null, $docBlockParser); + } + + $inputSchema = $data['inputSchema'] ?? $schemaGenerator->generate($reflection); + + $tool = new Tool( + name: $name, + title: $data['title'] ?? null, + inputSchema: $inputSchema, + description: $description, + annotations: $data['annotations'] ?? null, + icons: $data['icons'] ?? null, + meta: $data['meta'] ?? null, + outputSchema: $data['outputSchema'] ?? null, + ); + $registry->registerTool($tool, $data['handler']); + + $handlerDesc = $this->getHandlerDescription($data['handler']); + $this->logger->debug("Registered manual tool {$name} from handler {$handlerDesc}"); + } catch (\Throwable $e) { + $this->logger->error( + 'Failed to register manual tool', + ['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e], + ); + throw new ConfigurationException("Error registering manual tool '{$data['name']}': {$e->getMessage()}", 0, $e); + } + } + + // Register Resources + foreach ($this->resources as $data) { + try { + $reflection = HandlerResolver::resolve($data['handler']); + + if ($reflection instanceof \ReflectionFunction) { + $name = $data['name'] ?? 'closure_resource_'.spl_object_id($data['handler']); + $description = $data['description'] ?? null; + } else { + $name = ElementMetadataResolver::resolveName($reflection, $data['name'] ?? null); + $description = ElementMetadataResolver::resolveDescription($reflection, $data['description'] ?? null, $docBlockParser); + } + + $resource = new ResourceDefinition( + uri: $data['uri'], + name: $name, + title: $data['title'] ?? null, + description: $description, + mimeType: $data['mimeType'] ?? null, + annotations: $data['annotations'] ?? null, + size: $data['size'] ?? null, + icons: $data['icons'] ?? null, + meta: $data['meta'] ?? null, + ); + $registry->registerResource($resource, $data['handler']); + + $handlerDesc = $this->getHandlerDescription($data['handler']); + $this->logger->debug("Registered manual resource {$name} from handler {$handlerDesc}"); + } catch (\Throwable $e) { + $this->logger->error( + 'Failed to register manual resource', + ['handler' => $data['handler'], 'uri' => $data['uri'], 'exception' => $e], + ); + throw new ConfigurationException("Error registering manual resource '{$data['uri']}': {$e->getMessage()}", 0, $e); + } + } + + // Register Templates + foreach ($this->resourceTemplates as $data) { + try { + $reflection = HandlerResolver::resolve($data['handler']); + + if ($reflection instanceof \ReflectionFunction) { + $name = $data['name'] ?? 'closure_template_'.spl_object_id($data['handler']); + $description = $data['description'] ?? null; + } else { + $name = ElementMetadataResolver::resolveName($reflection, $data['name'] ?? null); + $description = ElementMetadataResolver::resolveDescription($reflection, $data['description'] ?? null, $docBlockParser); + } + + $template = new ResourceTemplate( + uriTemplate: $data['uriTemplate'], + name: $name, + title: $data['title'] ?? null, + description: $description, + mimeType: $data['mimeType'] ?? null, + annotations: $data['annotations'] ?? null, + meta: $data['meta'] ?? null, + ); + $completionProviders = $this->getCompletionProviders($reflection); + $registry->registerResourceTemplate($template, $data['handler'], $completionProviders); + + $handlerDesc = $this->getHandlerDescription($data['handler']); + $this->logger->debug("Registered manual template {$name} from handler {$handlerDesc}"); + } catch (\Throwable $e) { + $this->logger->error( + 'Failed to register manual template', + ['handler' => $data['handler'], 'uriTemplate' => $data['uriTemplate'], 'exception' => $e], + ); + throw new ConfigurationException("Error registering manual resource template '{$data['uriTemplate']}': {$e->getMessage()}", 0, $e); + } + } + + // Register Prompts + foreach ($this->prompts as $data) { + try { + $reflection = HandlerResolver::resolve($data['handler']); + + if ($reflection instanceof \ReflectionFunction) { + $name = $data['name'] ?? 'closure_prompt_'.spl_object_id($data['handler']); + $description = $data['description'] ?? null; + } else { + $name = ElementMetadataResolver::resolveName($reflection, $data['name'] ?? null); + $description = ElementMetadataResolver::resolveDescription($reflection, $data['description'] ?? null, $docBlockParser); + } + + $arguments = []; + $paramTags = $reflection instanceof \ReflectionMethod ? $docBlockParser->getParamTags( + $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null), + ) : []; + foreach ($reflection->getParameters() as $param) { + $reflectionType = $param->getType(); + + // Basic DI check (heuristic) + if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) { + continue; + } + + $paramTag = $paramTags['$'.$param->getName()] ?? null; + $arguments[] = new PromptArgument( + $param->getName(), + $paramTag ? trim((string) $paramTag->getDescription()) : null, + !$param->isOptional() && !$param->isDefaultValueAvailable(), + ); + } + $prompt = new Prompt( + name: $name, + title: $data['title'] ?? null, + description: $description, + arguments: $arguments, + icons: $data['icons'] ?? null, + meta: $data['meta'] ?? null + ); + $completionProviders = $this->getCompletionProviders($reflection); + $registry->registerPrompt($prompt, $data['handler'], $completionProviders); + + $handlerDesc = $this->getHandlerDescription($data['handler']); + $this->logger->debug("Registered manual prompt {$name} from handler {$handlerDesc}"); + } catch (\Throwable $e) { + $this->logger->error( + 'Failed to register manual prompt', + ['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e], + ); + throw new ConfigurationException("Error registering manual prompt '{$data['name']}': {$e->getMessage()}", 0, $e); + } + } + + $this->logger->debug('Manual element registration complete.'); + } + + /** + * @param Handler $handler + */ + private function getHandlerDescription(\Closure|array|string $handler): string + { + if ($handler instanceof \Closure) { + return 'Closure'; + } + + if (\is_array($handler)) { + return \sprintf( + '%s::%s', + \is_object($handler[0]) ? $handler[0]::class : $handler[0], + $handler[1], + ); + } + + return (string) $handler; + } + + /** + * @return array + */ + private function getCompletionProviders(\ReflectionMethod|\ReflectionFunction $reflection): array + { + $completionProviders = []; + foreach ($reflection->getParameters() as $param) { + $reflectionType = $param->getType(); + if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) { + continue; + } + + $completionAttributes = $param->getAttributes( + CompletionProvider::class, + \ReflectionAttribute::IS_INSTANCEOF, + ); + if (!empty($completionAttributes)) { + $attributeInstance = $completionAttributes[0]->newInstance(); + + if ($attributeInstance->provider) { + $completionProviders[$param->getName()] = $attributeInstance->provider; + } elseif ($attributeInstance->providerClass) { + $completionProviders[$param->getName()] = $attributeInstance->providerClass; + } elseif ($attributeInstance->values) { + $completionProviders[$param->getName()] = new ListCompletionProvider($attributeInstance->values); + } elseif ($attributeInstance->enum) { + $completionProviders[$param->getName()] = new EnumCompletionProvider($attributeInstance->enum); + } + } + } + + return $completionProviders; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/PromptReference.php b/admin/vendor/mcp/sdk/src/Capability/Registry/PromptReference.php new file mode 100644 index 0000000..ec7d021 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/PromptReference.php @@ -0,0 +1,51 @@ + + */ +class PromptReference extends ElementReference +{ + /** + * @param Handler $handler + * @param array $completionProviders + */ + public function __construct( + public readonly Prompt $prompt, + \Closure|array|string $handler, + public readonly array $completionProviders = [], + ) { + parent::__construct($handler); + } + + /** + * Formats the raw result of a prompt generator into an array of MCP PromptMessages. + * + * @param mixed $promptGenerationResult expected: array of message structures + * + * @return PromptMessage[] array of PromptMessage objects + * + * @throws \RuntimeException if the result cannot be formatted + * @throws \JsonException if JSON encoding fails + */ + public function formatResult(mixed $promptGenerationResult): array + { + return (new PromptResultFormatter())->format($promptGenerationResult); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandler.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandler.php new file mode 100644 index 0000000..c0084cb --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandler.php @@ -0,0 +1,311 @@ + + */ +final class ReferenceHandler implements ReferenceHandlerInterface +{ + public function __construct( + private readonly ?ContainerInterface $container = null, + ) { + } + + /** + * @param array $arguments + */ + public function handle(ElementReference $reference, array $arguments): mixed + { + // Closures bound to this class as their scope consume the raw argument bag + // directly. Used by ExplicitElementLoader so reflection + name-based parameter + // mapping is bypassed for explicitly registered handler interfaces. + if ($reference->handler instanceof \Closure + && self::class === (new \ReflectionFunction($reference->handler))->getClosureScopeClass()?->getName() + ) { + return ($reference->handler)($arguments); + } + + $session = $arguments['_session']; + + if (\is_string($reference->handler)) { + if (class_exists($reference->handler) && method_exists($reference->handler, '__invoke')) { + $reflection = new \ReflectionMethod($reference->handler, '__invoke'); + $instance = $this->getClassInstance($reference->handler); + $arguments = $this->prepareArguments($reflection, $arguments); + + return \call_user_func($instance, ...$arguments); + } + + if (\function_exists($reference->handler)) { + $reflection = new \ReflectionFunction($reference->handler); + $arguments = $this->prepareArguments($reflection, $arguments); + + return \call_user_func($reference->handler, ...$arguments); + } + } + + if (\is_callable($reference->handler)) { + $reflection = $this->getReflectionForCallable($reference->handler, $session); + $arguments = $this->prepareArguments($reflection, $arguments); + + return \call_user_func($reference->handler, ...$arguments); + } + + if (\is_array($reference->handler)) { + [$className, $methodName] = $reference->handler; + $reflection = new \ReflectionMethod($className, $methodName); + $instance = $this->getClassInstance($className); + $arguments = $this->prepareArguments($reflection, $arguments); + + return \call_user_func([$instance, $methodName], ...$arguments); + } + + throw new InvalidArgumentException('Invalid handler type'); + } + + private function getClassInstance(string $className): object + { + if (null !== $this->container && $this->container->has($className)) { + return $this->container->get($className); + } + + return new $className(); + } + + /** + * @param array $arguments + * + * @return array + */ + private function prepareArguments(\ReflectionFunctionAbstract $reflection, array $arguments): array + { + $finalArgs = []; + + foreach ($reflection->getParameters() as $parameter) { + $paramName = $parameter->getName(); + $paramPosition = $parameter->getPosition(); + + if ($parameter->isVariadic()) { + // Each element is cast individually below; a non-array value is + // wrapped as a single element, since prompt arguments arrive as + // plain strings (Record per the protocol) even + // though tool arguments follow the advertised "array" schema. + $values = $arguments[$paramName] ?? []; + if (!\is_array($values)) { + $values = [$values]; + } elseif (!array_is_list($values)) { + throw RegistryException::invalidParams(\sprintf('Parameter `%s` must be a list of values, not an object.', $paramName)); + } + foreach ($values as $index => $value) { + try { + $finalArgs[] = $this->castArgumentType($value, $parameter); + } catch (InvalidArgumentException $e) { + throw RegistryException::invalidParams(\sprintf('Parameter `%s[%d]`: %s', $paramName, $index, $e->getMessage()), $e); + } catch (\Throwable $e) { + throw RegistryException::internalError(\sprintf('Error processing parameter `%s[%d]`: %s', $paramName, $index, $e->getMessage()), $e); + } + } + continue; + } + + // Check if parameter is a special injectable type + $type = $parameter->getType(); + if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { + $injected = InjectableParameters::resolve($type->getName(), $arguments); + if (null !== $injected) { + $finalArgs[$paramPosition] = $injected; + continue; + } + } + + if (isset($arguments[$paramName])) { + $argument = $arguments[$paramName]; + try { + $finalArgs[$paramPosition] = $this->castArgumentType($argument, $parameter); + } catch (InvalidArgumentException $e) { + throw RegistryException::invalidParams($e->getMessage(), $e); + } catch (\Throwable $e) { + throw RegistryException::internalError("Error processing parameter `{$paramName}`: {$e->getMessage()}", $e); + } + } elseif ($parameter->isDefaultValueAvailable()) { + $finalArgs[$paramPosition] = $parameter->getDefaultValue(); + } elseif ($parameter->allowsNull()) { + $finalArgs[$paramPosition] = null; + } elseif ($parameter->isOptional()) { + continue; + } else { + $reflectionName = $reflection instanceof \ReflectionMethod + ? $reflection->class.'::'.$reflection->name + : 'Closure'; + throw RegistryException::internalError("Missing required argument `{$paramName}` for {$reflectionName}."); + } + } + + return array_values($finalArgs); + } + + /** + * Gets a ReflectionMethod or ReflectionFunction for a callable. + */ + private function getReflectionForCallable(callable $handler, SessionInterface $session): \ReflectionMethod|\ReflectionFunction + { + if (\is_string($handler)) { + return new \ReflectionFunction($handler); + } + + if ($handler instanceof \Closure) { + return new \ReflectionFunction($handler); + } + + if (\is_array($handler) && 2 === \count($handler)) { + [$class, $method] = $handler; + + return new \ReflectionMethod($class, $method); + } + + throw new InvalidArgumentException('Cannot create reflection for this callable type'); + } + + /** + * Attempts type casting based on ReflectionParameter type hints. + * + * @throws InvalidArgumentException if casting is impossible for the required type + */ + private function castArgumentType(mixed $argument, \ReflectionParameter $parameter): mixed + { + $type = $parameter->getType(); + + if (null === $argument) { + if ($type && $type->allowsNull()) { + return null; + } + } + + if (!$type instanceof \ReflectionNamedType) { + return $argument; + } + + $typeName = $type->getName(); + + if (enum_exists($typeName)) { + if (\is_object($argument) && $argument instanceof $typeName) { + return $argument; + } + + if (is_subclass_of($typeName, \BackedEnum::class)) { + $value = $typeName::tryFrom($argument); + if (null === $value) { + throw new InvalidArgumentException("Invalid value '{$argument}' for backed enum {$typeName}. Expected one of its backing values."); + } + + return $value; + } + if (\is_string($argument)) { + foreach ($typeName::cases() as $case) { + if ($case->name === $argument) { + return $case; + } + } + $validNames = array_map(static fn ($c) => $c->name, $typeName::cases()); + throw new InvalidArgumentException("Invalid value '{$argument}' for unit enum {$typeName}. Expected one of: ".implode(', ', $validNames).'.'); + } + throw new InvalidArgumentException("Invalid value type '{$argument}' for unit enum {$typeName}. Expected a string matching a case name."); + } + + try { + return match (strtolower($typeName)) { + 'int', 'integer' => $this->castToInt($argument), + 'string' => (string) $argument, + 'bool', 'boolean' => $this->castToBoolean($argument), + 'float', 'double' => $this->castToFloat($argument), + 'array' => $this->castToArray($argument), + default => $argument, + }; + } catch (\TypeError $e) { + throw new InvalidArgumentException("Value cannot be cast to required type `{$typeName}`.", 0, $e); + } + } + + /** + * Helper to cast strictly to boolean. + */ + private function castToBoolean(mixed $argument): bool + { + if (\is_bool($argument)) { + return $argument; + } + if (1 === $argument || '1' === $argument || 'true' === strtolower((string) $argument)) { + return true; + } + if (0 === $argument || '0' === $argument || 'false' === strtolower((string) $argument)) { + return false; + } + + throw new InvalidArgumentException('Cannot cast value to boolean. Use true/false/1/0.'); + } + + /** + * Helper to cast strictly to integer. + */ + private function castToInt(mixed $argument): int + { + if (\is_int($argument)) { + return $argument; + } + if (is_numeric($argument) && floor((float) $argument) == $argument && !\is_string($argument)) { + return (int) $argument; + } + if (\is_string($argument) && ctype_digit(ltrim($argument, '-'))) { + return (int) $argument; + } + + throw new InvalidArgumentException('Cannot cast value to integer. Expected integer representation.'); + } + + /** + * Helper to cast strictly to float. + */ + private function castToFloat(mixed $argument): float + { + if (\is_float($argument)) { + return $argument; + } + if (\is_int($argument)) { + return (float) $argument; + } + if (is_numeric($argument)) { + return (float) $argument; + } + + throw new InvalidArgumentException('Cannot cast value to float. Expected numeric representation.'); + } + + /** + * Helper to cast strictly to array. + * + * @return array + */ + private function castToArray(mixed $argument): array + { + if (\is_array($argument)) { + return $argument; + } + + throw new InvalidArgumentException('Cannot cast value to array. Expected array.'); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandlerInterface.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandlerInterface.php new file mode 100644 index 0000000..c4e52f1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ReferenceHandlerInterface.php @@ -0,0 +1,37 @@ + + */ +interface ReferenceHandlerInterface +{ + /** + * Handles execution of an MCP element reference. + * + * @param ElementReference $reference the element reference to execute + * @param array $arguments arguments to pass to the handler + * + * @return mixed the result of the element execution + * + * @throws InvalidArgumentException if the handler is invalid + * @throws RegistryException if execution fails + */ + public function handle(ElementReference $reference, array $arguments): mixed; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceReference.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceReference.php new file mode 100644 index 0000000..10d7837 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceReference.php @@ -0,0 +1,59 @@ + + */ +class ResourceReference extends ElementReference +{ + /** + * @param Handler $handler + */ + public function __construct( + public readonly ResourceDefinition $resource, + callable|array|string $handler, + ) { + parent::__construct($handler); + } + + /** + * Formats the raw result of a resource read operation into MCP ResourceContent items. + * + * @param mixed $readResult the raw result from the resource handler method + * @param string $uri the URI of the resource that was read + * @param ?string $mimeType the MIME type from the ResourceDefinition + * + * @return ResourceContents[] array of ResourceContents objects + * + * Supported result types: + * - ResourceContents: Used as-is + * - EmbeddedResource: Resource is extracted from the EmbeddedResource + * - string: Converted to text content with guessed or provided MIME type + * - stream resource: Read and converted to blob with provided MIME type + * - array with 'blob' key: Used as blob content + * - array with 'text' key: Used as text content + * - SplFileInfo: Read and converted to blob + * - array: Converted to JSON if MIME type is application/json or contains 'json' + * For other MIME types, will try to convert to JSON with a warning + */ + public function formatResult(mixed $readResult, string $uri, ?string $mimeType = null): array + { + return (new ResourceResultFormatter())->format($readResult, $uri, $mimeType, $this->resource->meta); + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceTemplateReference.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceTemplateReference.php new file mode 100644 index 0000000..49d03b3 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ResourceTemplateReference.php @@ -0,0 +1,112 @@ + + */ +class ResourceTemplateReference extends ElementReference +{ + /** + * @var array + */ + private array $variableNames; + + private string $uriTemplateRegex; + + /** + * @param Handler $handler + * @param array $completionProviders + */ + public function __construct( + public readonly ResourceTemplate $resourceTemplate, + callable|array|string $handler, + public readonly array $completionProviders = [], + ) { + parent::__construct($handler); + + $this->compileTemplate(); + } + + /** + * @return array + */ + public function getVariableNames(): array + { + return $this->variableNames; + } + + public function matches(string $uri): bool + { + return 1 === preg_match($this->uriTemplateRegex, $uri); + } + + /** @return array */ + public function extractVariables(string $uri): array + { + $matches = []; + + preg_match($this->uriTemplateRegex, $uri, $matches); + + return array_filter($matches, fn ($key) => \in_array($key, $this->variableNames), \ARRAY_FILTER_USE_KEY); + } + + /** + * Formats the raw result of a resource read operation into MCP ResourceContent items. + * + * @param mixed $readResult the raw result from the resource handler method + * @param string $uri the URI of the resource that was read + * + * @return array array of ResourceContents objects + * + * Supported result types: + * - ResourceContents: Used as-is + * - EmbeddedResource: Resource is extracted from the EmbeddedResource + * - string: Converted to text content with guessed or provided MIME type + * - stream resource: Read and converted to blob with provided MIME type + * - array with 'blob' key: Used as blob content + * - array with 'text' key: Used as text content + * - SplFileInfo: Read and converted to blob + * - array: Converted to JSON if MIME type is application/json or contains 'json' + * For other MIME types, will try to convert to JSON with a warning + */ + public function formatResult(mixed $readResult, string $uri, ?string $mimeType = null): array + { + return (new ResourceResultFormatter())->format($readResult, $uri, $mimeType, $this->resourceTemplate->meta); + } + + private function compileTemplate(): void + { + $this->variableNames = []; + $regexParts = []; + + $segments = preg_split('/(\{\w+\})/', $this->resourceTemplate->uriTemplate, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + + foreach ($segments as $segment) { + if (preg_match('/^\{(\w+)\}$/', $segment, $matches)) { + $varName = $matches[1]; + $this->variableNames[] = $varName; + $regexParts[] = '(?P<'.$varName.'>[^/]+)'; + } else { + $regexParts[] = preg_quote($segment, '#'); + } + } + + $this->uriTemplateRegex = '#^'.implode('', $regexParts).'$#'; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Registry/ToolReference.php b/admin/vendor/mcp/sdk/src/Capability/Registry/ToolReference.php new file mode 100644 index 0000000..97ace43 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Registry/ToolReference.php @@ -0,0 +1,142 @@ + + */ +class ToolReference extends ElementReference +{ + /** + * @param Handler $handler + */ + public function __construct( + public readonly Tool $tool, + callable|array|string $handler, + ) { + parent::__construct($handler); + } + + /** + * Formats the result of a tool execution into an array of MCP Content items. + * + * - If the result is already a Content object, it's wrapped in an array. + * - If the result is an array: + * - If all elements are Content objects, the array is returned as is. + * - If it's a mixed array (Content and non-Content items), non-Content items are + * individually formatted (scalars to TextContent, others to JSON TextContent). + * - If it's an array with no Content items, the entire array is JSON-encoded into a single TextContent. + * - Scalars (string, int, float, bool) are wrapped in TextContent. + * - null is represented as TextContent('(null)'). + * - Other objects are JSON-encoded and wrapped in TextContent. + * + * @param mixed $toolExecutionResult the raw value returned by the tool's PHP method + * + * @return Content[] the content items for CallToolResult + * + * @throws \JsonException if JSON encoding fails for non-Content array/object results + */ + public function formatResult(mixed $toolExecutionResult): array + { + return (new ToolResultFormatter())->format($toolExecutionResult); + } + + /** + * Extracts structured content from a tool result using the output schema. + * + * What may be sent as `structuredContent` depends on the protocol revision in + * use. Up to `2025-11-25` it has to be a JSON object, and `outputSchema` is + * restricted to `type: "object"` to match. From `2026-07-28` on (SEP-2106) + * `outputSchema` is any JSON Schema 2020-12 and `structuredContent` is any JSON + * value conforming to it — a list included. + * + * @param mixed $toolExecutionResult the raw value returned by the tool's PHP method + * @param ?ProtocolVersion $protocolVersion revision the result is produced for; defaults to the + * newest handshake revision, whose stricter rule is what + * every revision reachable through `initialize` requires + * + * @return mixed the structured content, or null if not extractable + * + * @throws \JsonException if JSON encoding fails for non-Content array/object results + */ + public function extractStructuredContent(mixed $toolExecutionResult, ?ProtocolVersion $protocolVersion = null): mixed + { + $objectOnly = ($protocolVersion ?? ProtocolVersion::latestHandshake())->requiresObjectStructuredContent(); + + if (\is_array($toolExecutionResult)) { + // A PHP list serializes to a JSON array, which the revisions predating + // SEP-2106 do not allow as `structuredContent` — strict clients reject + // the whole tool call when one is sent. + if ($objectOnly && array_is_list($toolExecutionResult)) { + return null; + } + + foreach ($toolExecutionResult as $item) { + if ($item instanceof Content) { + // Content items are already reflected in the result's `content` + // array; an array holding one or more of them isn't structured + // data. This holds in every revision — it is a duplication rule, + // not a shape rule. + return null; + } + } + + return $toolExecutionResult; + } + + if (\is_object($toolExecutionResult) && !($toolExecutionResult instanceof Content)) { + $jsonResult = json_encode( + $toolExecutionResult, + \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR | \JSON_INVALID_UTF8_SUBSTITUTE + ); + + $decoded = json_decode( + $jsonResult, true, 512, \JSON_THROW_ON_ERROR + ); + + // A plain object always encodes to a JSON object, but `JsonSerializable` + // can hand back anything, scalars included. + if (!\is_array($decoded)) { + return $this->acceptsScalarStructuredContent($objectOnly) ? $decoded : null; + } + + if ($objectOnly && array_is_list($decoded)) { + return null; + } + + return $decoded; + } + + // A scalar is structured content only from SEP-2106 on, and only when the + // tool declared an outputSchema: without one, every string-returning tool + // would start advertising a duplicate of its own `content`. + return $this->acceptsScalarStructuredContent($objectOnly) && \is_scalar($toolExecutionResult) + ? $toolExecutionResult + : null; + } + + /** + * Whether the negotiated revision and the tool's own declaration together allow + * a non-object `structuredContent`. + */ + private function acceptsScalarStructuredContent(bool $objectOnly): bool + { + return !$objectOnly && null !== $this->tool->outputSchema; + } +} diff --git a/admin/vendor/mcp/sdk/src/Capability/RegistryInterface.php b/admin/vendor/mcp/sdk/src/Capability/RegistryInterface.php new file mode 100644 index 0000000..bbbef76 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/RegistryInterface.php @@ -0,0 +1,175 @@ + + * @author Christopher Hertel + */ +interface RegistryInterface +{ + /** + * Registers a tool with its handler. Overwrites any prior registration of the same name. + * Returns the stored reference, whose identity callers may track to detect later overwrites. + * + * @param Handler $handler + */ + public function registerTool(Tool $tool, callable|array|string $handler): ToolReference; + + /** + * Registers a resource with its handler. Overwrites any prior registration of the same URI. + * Returns the stored reference, whose identity callers may track to detect later overwrites. + * + * @param Handler $handler + */ + public function registerResource(ResourceDefinition $resource, callable|array|string $handler): ResourceReference; + + /** + * Registers a resource template with its handler and completion providers. + * Overwrites any prior registration of the same URI template. + * Returns the stored reference, whose identity callers may track to detect later overwrites. + * + * @param Handler $handler + * @param array $completionProviders + */ + public function registerResourceTemplate( + ResourceTemplate $template, + callable|array|string $handler, + array $completionProviders = [], + ): ResourceTemplateReference; + + /** + * Registers a prompt with its handler and completion providers. + * Overwrites any prior registration of the same name. + * Returns the stored reference, whose identity callers may track to detect later overwrites. + * + * @param Handler $handler + * @param array $completionProviders + */ + public function registerPrompt( + Prompt $prompt, + callable|array|string $handler, + array $completionProviders = [], + ): PromptReference; + + /** + * Removes a tool by name. No-op if absent. + */ + public function unregisterTool(string $name): void; + + /** + * Removes a resource by URI. No-op if absent. + */ + public function unregisterResource(string $uri): void; + + /** + * Removes a resource template by URI template. No-op if absent. + */ + public function unregisterResourceTemplate(string $uriTemplate): void; + + /** + * Removes a prompt by name. No-op if absent. + */ + public function unregisterPrompt(string $name): void; + + public function hasTool(string $name): bool; + + public function hasResource(string $uri): bool; + + public function hasResourceTemplate(string $uriTemplate): bool; + + public function hasPrompt(string $name): bool; + + /** + * @return bool true if any tools are registered + */ + public function hasTools(): bool; + + /** + * Gets all registered tools. + */ + public function getTools(?int $limit = null, ?string $cursor = null): Page; + + /** + * Gets a tool reference by name. + * + * @throws ToolNotFoundException + */ + public function getTool(string $name): ToolReference; + + /** + * @return bool true if any resources are registered + */ + public function hasResources(): bool; + + /** + * Gets all registered resources. + */ + public function getResources(?int $limit = null, ?string $cursor = null): Page; + + /** + * Gets a resource reference by URI (includes template matching if enabled). + * + * @throws ResourceNotFoundException + */ + public function getResource(string $uri, bool $includeTemplates = true): ResourceReference|ResourceTemplateReference; + + /** + * @return bool true if any resource templates are registered + */ + public function hasResourceTemplates(): bool; + + /** + * Gets all registered resource templates. + */ + public function getResourceTemplates(?int $limit = null, ?string $cursor = null): Page; + + /** + * Gets a resource template reference by URI template. + * + * @throws ResourceNotFoundException + */ + public function getResourceTemplate(string $uriTemplate): ResourceTemplateReference; + + /** + * @return bool true if any prompts are registered + */ + public function hasPrompts(): bool; + + /** + * Gets all registered prompts. + */ + public function getPrompts(?int $limit = null, ?string $cursor = null): Page; + + /** + * Gets a prompt reference by name. + * + * @throws PromptNotFoundException + */ + public function getPrompt(string $name): PromptReference; +} diff --git a/admin/vendor/mcp/sdk/src/Capability/Tool/NameValidator.php b/admin/vendor/mcp/sdk/src/Capability/Tool/NameValidator.php new file mode 100644 index 0000000..fe9bb08 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Capability/Tool/NameValidator.php @@ -0,0 +1,20 @@ + + */ +class Client +{ + private const RETRY_BASE_DELAY_MS = 100; + + private ?TransportInterface $transport = null; + + public function __construct( + private readonly Protocol $protocol, + private readonly Configuration $config, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * Create a new client builder for fluent configuration. + */ + public static function builder(): Builder + { + return new Builder(); + } + + /** + * Connect to an MCP server using the provided transport. + * + * A failed attempt is closed and retried, see {@see Builder::setMaxRetries()}. + * + * @throws ConnectionException If connection or initialization fails on every attempt + */ + public function connect(TransportInterface $transport): void + { + $this->transport = $transport; + $this->protocol->connect($transport, $this->config); + + $maxAttempts = $this->config->maxRetries + 1; + + for ($attempt = 1; $attempt <= $maxAttempts; ++$attempt) { + try { + $transport->connect(); + + $this->logger->info('Client connected and initialized', ['attempt' => $attempt]); + + return; + } catch (ConnectionException $e) { + // initialize() flags the session before sending the initialized + // notification, so a failure in between leaves the flag set. + $this->protocol->getState()->setInitialized(false); + + $transport->close(); + + if ($attempt === $maxAttempts) { + throw $e; + } + + $this->logger->warning('Connection attempt failed, retrying', [ + 'attempt' => $attempt, + 'max_attempts' => $maxAttempts, + 'exception' => $e, + ]); + + usleep($attempt * self::RETRY_BASE_DELAY_MS * 1000); + } + } + } + + /** + * Check if connected and initialized. + */ + public function isConnected(): bool + { + return null !== $this->transport && $this->protocol->getState()->isInitialized(); + } + + /** + * Get server information from initialization. + */ + public function getServerInfo(): ?Implementation + { + return $this->protocol->getState()->getServerInfo(); + } + + /** + * Get server instructions. + */ + public function getInstructions(): ?string + { + return $this->protocol->getState()->getInstructions(); + } + + /** + * Protocol revision negotiated during the handshake. + * + * This is the version the server answered with, which is not necessarily the + * one configured on the builder: a server that cannot speak the requested + * revision counter-offers one it supports. Null until the handshake completed. + */ + public function getProtocolVersion(): ?ProtocolVersion + { + return $this->protocol->getState()->getProtocolVersion(); + } + + /** + * Send a ping request to the server. + */ + public function ping(): void + { + $request = new PingRequest(); + + $this->sendRequest($request); + } + + /** + * List available tools from the server. + */ + public function listTools(?string $cursor = null): ListToolsResult + { + $request = new ListToolsRequest($cursor); + + $response = $this->sendRequest($request); + + $result = $response->result; + + // Filtered before parsing, because parsing is where a malformed + // `x-mcp-header` annotation throws. One broken definition must cost the + // caller that tool, not the whole listing (SEP-2243). + if (\is_array($result['tools'] ?? null)) { + $result['tools'] = $this->protocol->getToolCatalog()->record($result['tools']); + } + + return ListToolsResult::fromArray($result); + } + + /** + * Call a tool on the server. + * + * @param string $name Tool name + * @param array $arguments Tool arguments + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + * Optional callback for progress updates + */ + public function callTool(string $name, array $arguments = [], ?callable $onProgress = null): CallToolResult + { + $catalog = $this->protocol->getToolCatalog(); + + // A tool the listing showed to be malformed is refused here rather than + // sent: the client cannot produce the headers its annotations demand, + // so the call could only go out misdescribed (SEP-2243). + if ($catalog->isRejected($name)) { + throw new RuntimeException(\sprintf('Refusing to call tool "%s": its "x-mcp-header" annotations are invalid (%s).', $name, $catalog->reasonFor($name))); + } + + $request = new CallToolRequest($name, $arguments); + + $response = $this->sendRequest($request, $onProgress); + + return CallToolResult::fromArray($response->result); + } + + /** + * List available resources from the server. + */ + public function listResources(?string $cursor = null): ListResourcesResult + { + $request = new ListResourcesRequest($cursor); + + $response = $this->sendRequest($request); + + return ListResourcesResult::fromArray($response->result); + } + + /** + * List available resource templates from the server. + */ + public function listResourceTemplates(?string $cursor = null): ListResourceTemplatesResult + { + $request = new ListResourceTemplatesRequest($cursor); + + $response = $this->sendRequest($request); + + return ListResourceTemplatesResult::fromArray($response->result); + } + + /** + * Read a resource by URI. + * + * @param string $uri The resource URI + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + * Optional callback for progress updates + */ + public function readResource(string $uri, ?callable $onProgress = null): ReadResourceResult + { + $request = new ReadResourceRequest($uri); + + $response = $this->sendRequest($request, $onProgress); + + return ReadResourceResult::fromArray($response->result); + } + + /** + * List available prompts from the server. + */ + public function listPrompts(?string $cursor = null): ListPromptsResult + { + $request = new ListPromptsRequest($cursor); + + $response = $this->sendRequest($request); + + return ListPromptsResult::fromArray($response->result); + } + + /** + * Get a prompt from the server. + * + * @param string $name Prompt name + * @param array $arguments Prompt arguments + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + * Optional callback for progress updates + */ + public function getPrompt(string $name, array $arguments = [], ?callable $onProgress = null): GetPromptResult + { + $request = new GetPromptRequest($name, $arguments); + + $response = $this->sendRequest($request, $onProgress); + + return GetPromptResult::fromArray($response->result); + } + + /** + * Request completion suggestions for a prompt or resource argument. + * + * @param PromptReference|ResourceReference $ref The prompt or resource reference + * @param array{name: string, value: string} $argument The argument to complete + */ + public function complete(PromptReference|ResourceReference $ref, array $argument): CompletionCompleteResult + { + $request = new CompletionCompleteRequest($ref, $argument); + + $response = $this->sendRequest($request); + + return CompletionCompleteResult::fromArray($response->result); + } + + /** + * Set the minimum logging level for server log messages. + */ + public function setLoggingLevel(LoggingLevel $level): void + { + $request = new SetLogLevelRequest($level); + + $this->sendRequest($request); + } + + /** + * Notify the server that the client's list of roots has changed. + * + * The server should react by requesting an updated list via roots/list. + * + * @throws RuntimeException if the client did not advertise the `roots.listChanged` capability + * @throws ConnectionException if the client is not connected + */ + public function sendRootsListChanged(): void + { + if (true !== $this->config->capabilities->rootsListChanged) { + throw new RuntimeException('Cannot send a "roots/list_changed" notification without advertising the "roots.listChanged" capability. Build the client with new ClientCapabilities(roots: true, rootsListChanged: true).'); + } + + if (!$this->isConnected()) { + throw new ConnectionException('Client is not connected. Call connect() first.'); + } + + $this->protocol->sendNotification(new RootsListChangedNotification()); + } + + /** + * Send a request to the server and wait for response. + * + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + * + * @return Response + * + * @throws RequestException|ConnectionException + */ + private function sendRequest(Request $request, ?callable $onProgress = null): Response + { + if (!$this->isConnected()) { + throw new ConnectionException('Client is not connected. Call connect() first.'); + } + + $withProgress = null !== $onProgress; + $fiber = new \Fiber(fn () => $this->protocol->request($request, $this->config->requestTimeout, $withProgress)); + $response = $this->transport->runRequest($fiber, $onProgress); + + if ($response instanceof Error) { + throw RequestException::fromError($response); + } + + return $response; + } + + /** + * Disconnect from the server. + */ + public function disconnect(): void + { + if (null !== $this->transport) { + $this->transport->close(); + $this->transport = null; + $this->logger->info('Client disconnected'); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Builder.php b/admin/vendor/mcp/sdk/src/Client/Builder.php new file mode 100644 index 0000000..098b6e6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Builder.php @@ -0,0 +1,215 @@ + + */ +final class Builder +{ + private string $name = 'mcp-php-client'; + private string $version = '1.0.0'; + private ?string $description = null; + private ?string $title = null; + private ?ProtocolVersion $protocolVersion = null; + private ?ClientCapabilities $capabilities = null; + + /** @var array> */ + private array $extensions = []; + private int $initTimeout = 30; + private int $requestTimeout = 120; + private int $maxRetries = 3; + private ?LoggerInterface $logger = null; + + /** @var NotificationHandlerInterface[] */ + private array $notificationHandlers = []; + + /** @var RequestHandlerInterface[] */ + private array $requestHandlers = []; + + /** + * Set the client name and version. + * + * @param ?string $title Display name for UI and end-user contexts. Falls back to $name when absent. + */ + public function setClientInfo(string $name, string $version, ?string $description = null, ?string $title = null): self + { + $this->name = $name; + $this->version = $version; + $this->description = $description; + $this->title = $title; + + return $this; + } + + /** + * Set the protocol version to use. + */ + public function setProtocolVersion(ProtocolVersion $protocolVersion): self + { + $this->protocolVersion = $protocolVersion; + + return $this; + } + + /** + * Set client capabilities. + */ + public function setCapabilities(ClientCapabilities $capabilities): self + { + $this->capabilities = $capabilities; + + return $this; + } + + /** + * Enable one or more MCP protocol extensions, announced to the server under + * `capabilities.extensions` in the initialize request. + * + * @throws InvalidArgumentException if the identifier is not a valid `_meta` prefix + * @throws LogicException if the same extension is enabled more than once + */ + public function enableExtension(ExtensionInterface ...$extensions): self + { + foreach ($extensions as $extension) { + $id = (string) $extension->getId(); + + if (isset($this->extensions[$id])) { + throw new LogicException(\sprintf('Extension "%s" is already enabled.', $id)); + } + + $this->extensions[$id] = $extension->getCapabilities(); + } + + return $this; + } + + /** + * Set initialization timeout in seconds. + */ + public function setInitTimeout(int $seconds): self + { + $this->initTimeout = $seconds; + + return $this; + } + + /** + * Set request timeout in seconds. + */ + public function setRequestTimeout(int $seconds): self + { + $this->requestTimeout = $seconds; + + return $this; + } + + /** + * Set the number of times a failed connection attempt is retried. + * + * Counts retries, not attempts: 3 means one initial attempt plus up to three + * retries. Pass 0 to fail on the first failure. Only applies to + * {@see Client::connect()}; individual requests are never retried. + */ + public function setMaxRetries(int $retries): self + { + $this->maxRetries = $retries; + + return $this; + } + + /** + * Set the logger. + */ + public function setLogger(LoggerInterface $logger): self + { + $this->logger = $logger; + + return $this; + } + + /** + * Add a notification handler for server notifications. + */ + public function addNotificationHandler(NotificationHandlerInterface $handler): self + { + $this->notificationHandlers[] = $handler; + + return $this; + } + + /** + * Add a request handler for server requests (e.g., sampling). + * + * @param RequestHandlerInterface $handler + */ + public function addRequestHandler(RequestHandlerInterface $handler): self + { + $this->requestHandlers[] = $handler; + + return $this; + } + + /** + * Build the client instance. + */ + public function build(): Client + { + $logger = $this->logger ?? new NullLogger(); + + $clientInfo = new Implementation( + $this->name, + $this->version, + $this->description, + title: $this->title, + ); + + $capabilities = $this->capabilities ?? new ClientCapabilities(); + + // Extensions enabled via enableExtension() are folded into caller-supplied + // capabilities too, so setCapabilities() does not silently drop them. + if ([] !== $this->extensions) { + $capabilities = $capabilities->withExtensions($this->extensions); + } + + $config = new Configuration( + clientInfo: $clientInfo, + capabilities: $capabilities, + protocolVersion: $this->protocolVersion ?? ProtocolVersion::V2025_11_25, + initTimeout: $this->initTimeout, + requestTimeout: $this->requestTimeout, + maxRetries: $this->maxRetries, + ); + + $protocol = new Protocol( + requestHandlers: $this->requestHandlers, + notificationHandlers: $this->notificationHandlers, + logger: $logger, + ); + + return new Client($protocol, $config, $logger); + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Configuration.php b/admin/vendor/mcp/sdk/src/Client/Configuration.php new file mode 100644 index 0000000..f0ed6f7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Configuration.php @@ -0,0 +1,46 @@ + + */ +class Configuration +{ + public function __construct( + public readonly Implementation $clientInfo, + public readonly ClientCapabilities $capabilities, + public readonly ProtocolVersion $protocolVersion = ProtocolVersion::V2025_11_25, + public readonly int $initTimeout = 30, + public readonly int $requestTimeout = 120, + public readonly int $maxRetries = 3, + ) { + if ($initTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The initialization timeout must be a positive number of seconds, got %d.', $initTimeout)); + } + + if ($requestTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The request timeout must be a positive number of seconds, got %d.', $requestTimeout)); + } + + if ($maxRetries < 0) { + throw new InvalidArgumentException(\sprintf('The maximum number of retries must be zero or greater, got %d.', $maxRetries)); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Notification/LoggingNotificationHandler.php b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/LoggingNotificationHandler.php new file mode 100644 index 0000000..a6853d4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/LoggingNotificationHandler.php @@ -0,0 +1,47 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +class LoggingNotificationHandler implements NotificationHandlerInterface +{ + /** + * @param callable(LoggingMessageNotification): void $callback + */ + public function __construct( + private readonly mixed $callback, + ) { + trigger_deprecation('mcp/sdk', '0.8', 'MCP logging is deprecated since protocol revision 2026-07-28 (SEP-2577); log to stderr or use OpenTelemetry instead.'); + } + + public function supports(Notification $notification): bool + { + return $notification instanceof LoggingMessageNotification; + } + + public function handle(Notification $notification): void + { + \assert($notification instanceof LoggingMessageNotification); + + ($this->callback)($notification); + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Notification/NotificationHandlerInterface.php b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/NotificationHandlerInterface.php new file mode 100644 index 0000000..82092aa --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/NotificationHandlerInterface.php @@ -0,0 +1,32 @@ + + */ +interface NotificationHandlerInterface +{ + /** + * Check if this handler supports the given notification. + */ + public function supports(Notification $notification): bool; + + /** + * Handle the notification. + */ + public function handle(Notification $notification): void; +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Notification/ProgressNotificationHandler.php b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/ProgressNotificationHandler.php new file mode 100644 index 0000000..3c489bf --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Notification/ProgressNotificationHandler.php @@ -0,0 +1,52 @@ + + * + * @internal + */ +class ProgressNotificationHandler implements NotificationHandlerInterface +{ + public function __construct( + private readonly ClientStateInterface $state, + ) { + } + + public function supports(Notification $notification): bool + { + return $notification instanceof ProgressNotification; + } + + public function handle(Notification $notification): void + { + if (!$notification instanceof ProgressNotification) { + return; + } + + $this->state->storeProgress( + (string) $notification->progressToken, + $notification->progress, + $notification->total, + $notification->message, + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Request/ElicitationCallbackInterface.php b/admin/vendor/mcp/sdk/src/Client/Handler/Request/ElicitationCallbackInterface.php new file mode 100644 index 0000000..fdaea14 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Request/ElicitationCallbackInterface.php @@ -0,0 +1,26 @@ + + * + * @author Johannes Wachter + */ +class ElicitationRequestHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly ElicitationCallbackInterface $callback, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ElicitRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request): Response|Error + { + \assert($request instanceof ElicitRequest); + + try { + $result = $this->callback->__invoke($request); + + return new Response($request->getId(), $result); + } catch (ElicitationException $e) { + $this->logger->error('Elicitation failed: '.$e->getMessage(), ['exception' => $e]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error('Unexpected error during elicitation', ['exception' => $e]); + + return Error::forInternalError('Error while processing elicitation', $request->getId()); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Request/ListRootsRequestHandler.php b/admin/vendor/mcp/sdk/src/Client/Handler/Request/ListRootsRequestHandler.php new file mode 100644 index 0000000..50806c1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Request/ListRootsRequestHandler.php @@ -0,0 +1,73 @@ + + * + * @author Johannes Wachter + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +class ListRootsRequestHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RootsCallbackInterface $callback, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + trigger_deprecation('mcp/sdk', '0.8', 'MCP roots are deprecated since protocol revision 2026-07-28 (SEP-2577); pass directories through tool arguments or resource URIs instead.'); + } + + public function supports(Request $request): bool + { + return $request instanceof ListRootsRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request): Response|Error + { + \assert($request instanceof ListRootsRequest); + + try { + $result = $this->callback->__invoke($request); + + return new Response($request->getId(), $result); + } catch (RootsException $e) { + $this->logger->error('Listing roots failed: '.$e->getMessage(), ['exception' => $e]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error('Unexpected error while listing roots', ['exception' => $e]); + + return Error::forInternalError('Error while listing roots', $request->getId()); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Request/RequestHandlerInterface.php b/admin/vendor/mcp/sdk/src/Client/Handler/Request/RequestHandlerInterface.php new file mode 100644 index 0000000..1c05018 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Request/RequestHandlerInterface.php @@ -0,0 +1,38 @@ + + */ +interface RequestHandlerInterface +{ + /** + * Check if this handler supports the given request. + */ + public function supports(Request $request): bool; + + /** + * Handle the request and return a response or error. + * + * @return Response|Error + */ + public function handle(Request $request): Response|Error; +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Request/RootsCallbackInterface.php b/admin/vendor/mcp/sdk/src/Client/Handler/Request/RootsCallbackInterface.php new file mode 100644 index 0000000..9dcccb2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Request/RootsCallbackInterface.php @@ -0,0 +1,32 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +interface RootsCallbackInterface +{ + public function __invoke(ListRootsRequest $request): ListRootsResult; +} diff --git a/admin/vendor/mcp/sdk/src/Client/Handler/Request/SamplingCallbackInterface.php b/admin/vendor/mcp/sdk/src/Client/Handler/Request/SamplingCallbackInterface.php new file mode 100644 index 0000000..0761de3 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Handler/Request/SamplingCallbackInterface.php @@ -0,0 +1,28 @@ + + * + * @author Kyrian Obikwelu + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +class SamplingRequestHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly SamplingCallbackInterface $callback, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + trigger_deprecation('mcp/sdk', '0.8', 'MCP sampling is deprecated since protocol revision 2026-07-28 (SEP-2577); integrate with an LLM provider\'s API directly instead.'); + } + + public function supports(Request $request): bool + { + return $request instanceof CreateSamplingMessageRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request): Response|Error + { + \assert($request instanceof CreateSamplingMessageRequest); + + try { + $request->validateToolFlow(); + } catch (InvalidArgumentException $e) { + $this->logger->warning('Rejecting sampling request violating the tool flow', ['exception' => $e]); + + return Error::forInvalidParams($e->getMessage(), $request->getId()); + } + + try { + $result = $this->callback->__invoke($request); + + return new Response($request->getId(), $result); + } catch (SamplingException $e) { + $this->logger->error('Sampling failed: '.$e->getMessage(), ['exception' => $e]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error('Unexpected error during sampling', ['exception' => $e]); + + return Error::forInternalError('Error while sampling LLM', $request->getId()); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Protocol.php b/admin/vendor/mcp/sdk/src/Client/Protocol.php new file mode 100644 index 0000000..281aedc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Protocol.php @@ -0,0 +1,689 @@ + + */ +class Protocol +{ + /** + * How many times a request may be re-sent before the client gives up. + * + * Both loops that re-send are bounded by it: a server that keeps asking for + * input, and one that keeps rejecting the offered revision. Neither is + * expected to run more than a round or two, so the cap is only there to + * stop a broken or hostile server from spinning the client forever. + */ + private const MAX_ROUND_TRIPS = 10; + + private ?TransportInterface $transport = null; + private ClientStateInterface $state; + private MessageFactory $messageFactory; + private LoggerInterface $logger; + + /** @var NotificationHandlerInterface[] */ + private array $notificationHandlers; + + /** Set only when the configured revision has no handshake. */ + private ?RequestEnvelope $envelope = null; + + private ?HeaderFactory $headers = null; + + private ToolCatalog $tools; + + private readonly InputRequestResolver $inputRequests; + + /** + * Progress tokens are only required to be unique within a connection, and + * a retry keeps the caller's one — the work being reported on is the same. + */ + private int $progressTokens = 0; + + /** + * @param RequestHandlerInterface[] $requestHandlers + * @param NotificationHandlerInterface[] $notificationHandlers + */ + public function __construct( + private readonly array $requestHandlers = [], + array $notificationHandlers = [], + ?MessageFactory $messageFactory = null, + ?LoggerInterface $logger = null, + ) { + $this->state = new ClientState(); + $this->messageFactory = $messageFactory ?? MessageFactory::make(); + $this->logger = $logger ?? new NullLogger(); + + $this->notificationHandlers = [ + new ProgressNotificationHandler($this->state), + ...$notificationHandlers, + ]; + + $this->tools = new ToolCatalog($this->logger); + $this->inputRequests = new InputRequestResolver($this->requestHandlers, $this->logger); + } + + /** + * What the client knows about the server's tools, from `tools/list`. + * + * Kept on the protocol rather than the facade because it is what makes the + * SEP-2243 headers derivable at send time. + */ + public function getToolCatalog(): ToolCatalog + { + return $this->tools; + } + + /** + * Connect this protocol to a transport. + * + * Sets up message handling callbacks. + * + * @param TransportInterface $transport The transport to connect + * @param Configuration $config The client configuration for initialization + */ + public function connect(TransportInterface $transport, Configuration $config): void + { + $this->transport = $transport; + + // A fresh catalog per connection: it is what a server told this client + // about its tools, and a server reached by reconnecting — the same one + // or another — has said nothing yet. + $this->tools = new ToolCatalog($this->logger); + + if ($config->protocolVersion->isModern()) { + $this->envelope = new RequestEnvelope( + $config->protocolVersion, + $config->capabilities, + $config->clientInfo, + ); + $this->headers = new HeaderFactory($this->tools); + } + + $transport->setState($this->state); + $transport->onInitialize(fn () => $this->initialize($config)); + $transport->onMessage($this->processMessage(...)); + $transport->onError(fn (\Throwable $e) => $this->logger->error('Transport error', ['exception' => $e])); + + if ($transport instanceof HeaderAwareTransportInterface) { + $transport->onHeaders($this->headersFor(...)); + } + + $this->logger->info('Protocol connected to transport', ['transport' => $transport::class]); + } + + /** + * The headers belonging to an encoded message, for a transport that has any. + * + * @return array + */ + private function headersFor(string $payload): array + { + if (null === $this->headers || null === $this->envelope) { + return []; + } + + $decoded = json_decode($payload, true); + + return \is_array($decoded) + ? $this->headers->forMessage($decoded, $this->envelope->protocolVersion()) + : []; + } + + /** + * Ready the connection for use. + * + * Up to 2025-11-25 that means the `initialize` handshake: offer a revision, + * take the server's answer, confirm with `notifications/initialized`. From + * 2026-07-28 there is no handshake at all — see {@see self::discover()}. + * + * @param Configuration $config The client configuration + * + * @return Response>|Error + */ + public function initialize(Configuration $config): Response|Error + { + if (null !== $this->envelope) { + return $this->discover($config); + } + + $offered = $config->protocolVersion; + + $request = new InitializeRequest( + $offered->value, + $config->capabilities, + $config->clientInfo, + ); + + $response = $this->request($request, $config->initTimeout); + + if ($response instanceof Response) { + $initResult = InitializeResult::fromArray($response->result); + + // A counter-offer this SDK cannot speak leaves nothing to fall back to, + // so the handshake fails rather than continuing on a revision neither + // side agrees on. + $negotiated = $initResult->protocolVersion; + if (null === $negotiated || $negotiated->isModern()) { + // fromArray() above already rejected a missing or non-string revision. + $counterOffer = (string) $response->result['protocolVersion']; + + return Error::forInvalidParams(\sprintf( + 'Server responded with unsupported protocol version "%s". Supported versions: %s.', + $counterOffer, + implode(', ', array_map( + static fn (ProtocolVersion $v): string => $v->value, + ProtocolVersion::handshakeVersions(), + )), + ), $response->id); + } + + $this->state->setProtocolVersion($negotiated); + $this->state->setServerInfo($initResult->serverInfo); + $this->state->setInstructions($initResult->instructions); + $this->state->setInitialized(true); + + $this->sendNotification(new InitializedNotification()); + + $this->logger->info('Initialization complete', [ + 'server' => $initResult->serverInfo->name, + 'protocolVersion' => $negotiated->value, + ]); + } + + return $response; + } + + /** + * Stand in for the handshake in the modern era. + * + * There is nothing to negotiate: the revision travels on every request, so + * the connection is usable the moment the transport is. `server/discover` + * is only asked because the facade exposes `getServerInfo()`, and a server + * that will not answer it still serves every other method — so a failure + * here is logged and the connection proceeds. + * + * @return Response> + */ + private function discover(Configuration $config): Response + { + $this->state->setProtocolVersion($config->protocolVersion); + $this->state->setInitialized(true); + + $response = $this->request(new DiscoverRequest(), $config->initTimeout); + + if ($response instanceof Error) { + $this->logger->info('Server did not answer "server/discover"; continuing without its metadata.', [ + 'code' => $response->code, + 'message' => $response->message, + ]); + + return new Response(0, []); + } + + $this->readDiscovery($response->result); + + return $response; + } + + /** + * Read defensively: `server/discover` is optional, so a server may answer + * with something that is not a DiscoverResult at all, and none of it is + * load-bearing for the requests that follow. + * + * @param array $result + */ + private function readDiscovery(array $result): void + { + // Identity is wire vocabulary in this revision, so it rides in `_meta` + // rather than the result body. The top level is read as a fallback + // because that is where the handshake era put it. + $meta = \is_array($result['_meta'] ?? null) ? $result['_meta'] : []; + $serverInfo = $meta[RequestMeta::SERVER_INFO] ?? $result['serverInfo'] ?? null; + + if (\is_array($serverInfo)) { + try { + $this->state->setServerInfo(Implementation::fromArray($serverInfo)); + } catch (\Throwable $e) { + $this->logger->debug('Ignoring unreadable serverInfo from "server/discover".', ['exception' => $e]); + } + } + + if (\is_string($result['instructions'] ?? null)) { + $this->state->setInstructions($result['instructions']); + } + + $this->reconcileVersion($result['supportedVersions'] ?? null); + + $this->logger->info('Discovery complete', [ + 'supportedVersions' => $result['supportedVersions'] ?? null, + ]); + } + + /** + * Move to a revision the server actually speaks, if it said which. + * + * `server/discover` reports rather than negotiates, so a client that asked + * for something the server does not list learns it here — and learning it + * now is far better than a stream of refusals later. A server that stays + * silent about its versions is left alone; the method is optional and + * saying nothing is not the same as saying no. + */ + private function reconcileVersion(mixed $supportedVersions): void + { + if (!\is_array($supportedVersions) || [] === $supportedVersions || null === $this->envelope) { + return; + } + + $current = $this->envelope->protocolVersion(); + + if (\in_array($current->value, $supportedVersions, true)) { + return; + } + + foreach ($supportedVersions as $candidate) { + $version = \is_string($candidate) ? ProtocolVersion::tryFrom($candidate) : null; + + if (null === $version || !$version->isModern()) { + continue; + } + + $this->logger->warning('Server does not speak the configured revision; continuing on one it advertises.', [ + 'configured' => $current->value, + 'using' => $version->value, + ]); + + $this->envelope = $this->envelope->withProtocolVersion($version); + $this->state->setProtocolVersion($version); + + return; + } + + // Everything it offers is handshake era, which this connection cannot + // reach — it has already skipped the handshake. + throw new ConnectionException(\sprintf('Server does not support any modern protocol revision (it advertises %s); the configured "%s" cannot be used against it.', implode(', ', array_map(strval(...), $supportedVersions)), $current->value)); + } + + /** + * Send a request to the server and wait for response. + * + * If a response is immediately available (sync HTTP), returns it. + * Otherwise, suspends the Fiber and waits for the transport to resume it. + * + * In the modern era this is also where the two loops that re-send live: + * answering a server's request for input (SEP-2322), and retrying under a + * revision the server accepts (SEP-2575). Both re-send the same call, so + * they belong together and above the single exchange. + * + * @param Request $request The request to send + * @param int $timeout The timeout in seconds + * @param bool $withProgress Whether to attach a progress token to the request + * + * @return Response>|Error + */ + public function request(Request $request, int $timeout, bool $withProgress = false): Response|Error + { + $payload = $request->withId(0)->jsonSerialize(); + unset($payload['id']); + + if ($withProgress) { + $payload = self::withMeta($payload, ['progressToken' => 'prog-'.++$this->progressTokens]); + } + + if (null === $this->envelope) { + return $this->exchange($payload, $timeout); + } + + for ($attempt = 0; $attempt < self::MAX_ROUND_TRIPS; ++$attempt) { + $response = $this->exchange($payload, $timeout); + + if ($response instanceof Error) { + $retry = $this->withAcceptedVersion($response); + + if (null === $retry) { + return $response; + } + + continue; + } + + $asked = InputRequestResolver::asked($response->result); + + if (null === $asked) { + return $response; + } + + // A fresh `inputResponses`/`requestState` pair every round, never + // merged with the last: the answers belong to the ask that just + // arrived, and carrying an old one forward is how state leaks + // between rounds. + // + // Cast to object: inputResponses is a JSON object keyed by the + // server's ids, but a PHP array with no entries or with sequential + // numeric-string keys encodes as a JSON array instead. + $payload['params'] = [ + ...($payload['params'] ?? []), + 'inputResponses' => (object) $this->inputRequests->resolve($asked), + ]; + + unset($payload['params']['requestState']); + + // Echoed byte-for-byte, and only when the server sent one: the + // value is the server's to read, and inventing or reshaping it + // would break whatever it encodes. + if (\is_string($response->result['requestState'] ?? null)) { + $payload['params']['requestState'] = $response->result['requestState']; + } + + $this->logger->debug('Retrying request with resolved input', [ + 'method' => $payload['method'] ?? null, + 'round' => $attempt + 1, + ]); + } + + return Error::forInternalError(\sprintf('Server asked for input more than %d times without completing the request.', self::MAX_ROUND_TRIPS)); + } + + /** + * Switches the offered revision when the server refuses the current one, + * or null when there is nothing to retry with. + * + * @param Error $error the server's refusal + */ + private function withAcceptedVersion(Error $error): ?ProtocolVersion + { + if (Error::UNSUPPORTED_PROTOCOL_VERSION !== $error->code || null === $this->envelope) { + return null; + } + + $data = \is_array($error->data) ? $error->data : []; + $supported = \is_array($data['supported'] ?? null) ? $data['supported'] : []; + $current = $this->envelope->protocolVersion(); + + foreach ($supported as $candidate) { + $version = \is_string($candidate) ? ProtocolVersion::tryFrom($candidate) : null; + + // Only another modern revision is reachable from here: falling back + // to a handshake era one would mean opening a connection this + // transport already decided it was not going to open. + if (null === $version || !$version->isModern() || $version === $current) { + continue; + } + + $this->logger->info('Server rejected the offered protocol revision, retrying with one it supports.', [ + 'offered' => $current->value, + 'retrying' => $version->value, + ]); + + $this->envelope = $this->envelope->withProtocolVersion($version); + $this->state->setProtocolVersion($version); + + return $version; + } + + return null; + } + + /** + * One request on the wire: assign an id, send, and wait for its answer. + * + * A retry gets a new id, because the previous one is spent — the server has + * already answered it, and reusing it would make the two indistinguishable. + * + * @param array $payload + * + * @return Response>|Error + */ + private function exchange(array $payload, int $timeout): Response|Error + { + $requestId = $this->state->nextRequestId(); + $payload['id'] = $requestId; + + $this->state->addPendingRequest($requestId, $timeout); + + try { + $this->send($payload, 'request'); + + $immediate = $this->state->consumeResponse($requestId); + if (null !== $immediate) { + $this->logger->debug('Received immediate response', ['id' => $requestId]); + + return $immediate; + } + + $this->logger->debug('Suspending fiber for response', ['id' => $requestId]); + + return \Fiber::suspend([ + 'type' => 'await_response', + 'request_id' => $requestId, + 'timeout' => $timeout, + ]); + } finally { + // Only the response path clears it, so a request that timed out or + // whose send() threw would stay pending and fail every later one. + $this->state->removePendingRequest($requestId); + } + } + + /** + * Send a notification to the server (fire and forget). + */ + public function sendNotification(Notification $notification): void + { + $this->send($notification->jsonSerialize(), 'notification'); + } + + /** + * Encode and hand a message to the transport, stamping the per-request + * envelope on the way out when the revision calls for one. + * + * @param array $payload + */ + private function send(array $payload, string $kind): void + { + if (null !== $this->envelope) { + $payload = $this->envelope->stamp($payload); + } + + $this->logger->debug('Sending '.$kind, [ + 'id' => $payload['id'] ?? null, + 'method' => $payload['method'] ?? null, + ]); + + $this->transport?->send(json_encode($payload, \JSON_THROW_ON_ERROR)); + } + + /** + * @param array $payload + * @param array $meta + * + * @return array + */ + private static function withMeta(array $payload, array $meta): array + { + $params = \is_array($payload['params'] ?? null) ? $payload['params'] : []; + $existing = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; + + $params['_meta'] = [...$existing, ...$meta]; + $payload['params'] = $params; + + return $payload; + } + + /** + * Send a response back to the server (for server-initiated requests). + * + * @param Response|Error $response + */ + private function sendResponse(Response|Error $response): void + { + $this->logger->debug('Sending response', ['id' => $response->getId()]); + + $encoded = json_encode($response, \JSON_THROW_ON_ERROR); + $this->transport?->send($encoded); + } + + /** + * Process an incoming message from the server. + * + * Routes to appropriate handler based on message type. + */ + public function processMessage(string $input): void + { + $this->logger->debug('Received message', ['input' => $input]); + + try { + $messages = $this->messageFactory->create($input); + } catch (\JsonException $e) { + $this->logger->warning('Failed to parse message', ['exception' => $e]); + + return; + } + + foreach ($messages as $message) { + if ($message instanceof Response || $message instanceof Error) { + $this->handleResponse($message); + } elseif ($message instanceof Request) { + $this->handleRequest($message); + } elseif ($message instanceof Notification) { + $this->handleNotification($message); + } + } + } + + /** + * Handle a response from the server. + * + * This stores it in session. The transport will pick it up and resume the Fiber. + * + * @param Response|Error $response + */ + private function handleResponse(Response|Error $response): void + { + $requestId = $response->getId(); + + if (null === $requestId) { + $this->logger->warning('Received an id-less error response; cannot correlate it to a request.', ['response' => $response->jsonSerialize()]); + + return; + } + + $this->logger->debug('Handling response', ['id' => $requestId]); + + $this->state->storeResponse($requestId, $response->jsonSerialize()); + } + + /** + * Handle a request from the server (e.g., sampling request). + */ + private function handleRequest(Request $request): void + { + $method = $request::getMethod(); + + $this->logger->debug('Received server request', [ + 'method' => $method, + 'id' => $request->getId(), + ]); + + foreach ($this->requestHandlers as $handler) { + if ($handler->supports($request)) { + try { + $response = $handler->handle($request); + } catch (\Throwable $e) { + $this->logger->error('Unexpected error while handling request', [ + 'method' => $method, + 'exception' => $e, + ]); + + $response = Error::forInternalError( + \sprintf('Unexpected error while handling "%s" request', $method), + $request->getId() + ); + } + + $this->sendResponse($response); + + return; + } + } + + $error = Error::forMethodNotFound( + \sprintf('Client does not handle "%s" requests.', $method), + $request->getId() + ); + + $this->sendResponse($error); + } + + /** + * Handle a notification from the server. + */ + private function handleNotification(Notification $notification): void + { + $method = $notification::getMethod(); + + $this->logger->debug('Received server notification', [ + 'method' => $method, + ]); + + foreach ($this->notificationHandlers as $handler) { + if ($handler->supports($notification)) { + try { + $handler->handle($notification); + } catch (\Throwable $e) { + $this->logger->warning('Notification handler failed', ['exception' => $e]); + } + + return; + } + } + } + + public function getState(): ClientStateInterface + { + return $this->state; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/State/ClientState.php b/admin/vendor/mcp/sdk/src/Client/State/ClientState.php new file mode 100644 index 0000000..a1e1f89 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/State/ClientState.php @@ -0,0 +1,148 @@ + + */ +class ClientState implements ClientStateInterface +{ + private int $requestIdCounter = 1; + private bool $initialized = false; + private ?ProtocolVersion $protocolVersion = null; + private ?Implementation $serverInfo = null; + private ?string $instructions = null; + + /** @var array */ + private array $pendingRequests = []; + + /** @var array> */ + private array $responses = []; + + /** @var array */ + private array $progressUpdates = []; + + public function nextRequestId(): int + { + return $this->requestIdCounter++; + } + + public function addPendingRequest(int|string $requestId, int $timeout): void + { + $this->pendingRequests[$requestId] = [ + 'request_id' => $requestId, + 'timestamp' => time(), + 'timeout' => $timeout, + ]; + } + + public function removePendingRequest(int|string $requestId): void + { + unset($this->pendingRequests[$requestId]); + } + + public function getPendingRequests(): array + { + return $this->pendingRequests; + } + + public function storeResponse(int|string $requestId, array $responseData): void + { + $this->responses[$requestId] = $responseData; + } + + public function consumeResponse(int|string $requestId): Response|Error|null + { + if (!isset($this->responses[$requestId])) { + return null; + } + + $data = $this->responses[$requestId]; + unset($this->responses[$requestId]); + $this->removePendingRequest($requestId); + + if (isset($data['error'])) { + return Error::fromArray($data); + } + + return Response::fromArray($data); + } + + public function setInitialized(bool $initialized): void + { + $this->initialized = $initialized; + } + + public function isInitialized(): bool + { + return $this->initialized; + } + + public function setProtocolVersion(ProtocolVersion $protocolVersion): void + { + $this->protocolVersion = $protocolVersion; + } + + public function getProtocolVersion(): ?ProtocolVersion + { + return $this->protocolVersion; + } + + public function setServerInfo(Implementation $serverInfo): void + { + $this->serverInfo = $serverInfo; + } + + public function getServerInfo(): ?Implementation + { + return $this->serverInfo; + } + + public function setInstructions(?string $instructions): void + { + $this->instructions = $instructions; + } + + public function getInstructions(): ?string + { + return $this->instructions; + } + + public function storeProgress(string $token, float $progress, ?float $total, ?string $message): void + { + $this->progressUpdates[] = [ + 'token' => $token, + 'progress' => $progress, + 'total' => $total, + 'message' => $message, + ]; + } + + public function consumeProgressUpdates(): array + { + $updates = $this->progressUpdates; + $this->progressUpdates = []; + + return $updates; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/State/ClientStateInterface.php b/admin/vendor/mcp/sdk/src/Client/State/ClientStateInterface.php new file mode 100644 index 0000000..a5de42d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/State/ClientStateInterface.php @@ -0,0 +1,128 @@ + + */ +interface ClientStateInterface +{ + /** + * Get the next request ID for outgoing requests. + */ + public function nextRequestId(): int; + + /** + * Add a pending request to track. + * + * @param int|string $requestId The request ID + * @param int $timeout Timeout in seconds + */ + public function addPendingRequest(int|string $requestId, int $timeout): void; + + /** + * Remove a pending request. + */ + public function removePendingRequest(int|string $requestId): void; + + /** + * Get all pending requests. + * + * @return array + */ + public function getPendingRequests(): array; + + /** + * Store a received response. + * + * @param int|string $requestId The request ID + * @param array $responseData The raw response data + */ + public function storeResponse(int|string $requestId, array $responseData): void; + + /** + * Check and consume a response for a request ID. + * + * @return Response>|Error|null + */ + public function consumeResponse(int|string $requestId): Response|Error|null; + + /** + * Set initialization state. + */ + public function setInitialized(bool $initialized): void; + + /** + * Check if connection is initialized. + */ + public function isInitialized(): bool; + + /** + * Store the protocol version negotiated during initialization. + */ + public function setProtocolVersion(ProtocolVersion $protocolVersion): void; + + /** + * Get the protocol version negotiated during initialization. + * + * Null until the handshake has completed. + */ + public function getProtocolVersion(): ?ProtocolVersion; + + /** + * Store the server info from initialization. + */ + public function setServerInfo(Implementation $serverInfo): void; + + /** + * Get the server info from initialization. + */ + public function getServerInfo(): ?Implementation; + + /** + * Store the server instructions from initialization. + */ + public function setInstructions(?string $instructions): void; + + /** + * Get the server instructions from initialization. + */ + public function getInstructions(): ?string; + + /** + * Store progress data received from a notification. + * + * @param string $token The progress token + * @param float $progress Current progress value + * @param float|null $total Total progress value (if known) + * @param string|null $message Progress message + */ + public function storeProgress(string $token, float $progress, ?float $total, ?string $message): void; + + /** + * Consume all pending progress updates. + * + * @return array + */ + public function consumeProgressUpdates(): array; +} diff --git a/admin/vendor/mcp/sdk/src/Client/Stateless/HeaderFactory.php b/admin/vendor/mcp/sdk/src/Client/Stateless/HeaderFactory.php new file mode 100644 index 0000000..428e1c7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Stateless/HeaderFactory.php @@ -0,0 +1,102 @@ + + */ +final class HeaderFactory +{ + public function __construct( + private readonly ToolCatalog $tools, + ) { + } + + /** + * @param array $payload a serialized JSON-RPC message + * + * @return array + */ + public function forMessage(array $payload, ProtocolVersion $protocolVersion): array + { + $method = $payload['method'] ?? null; + $params = \is_array($payload['params'] ?? null) ? $payload['params'] : null; + + // A response to a server-initiated request carries no method to mirror; + // the version header is unconditional and still applies. Trace context + // still rides along if the application put it in `_meta` — the party + // that started the trace is not necessarily the one with a method to + // report. + if (!\is_string($method)) { + return [McpHeader::PROTOCOL_VERSION => $protocolVersion->value, ...$this->traceHeaders($params)]; + } + + $headers = [ + McpHeader::PROTOCOL_VERSION => $protocolVersion->value, + McpHeader::METHOD => $method, + ...$this->traceHeaders($params), + ]; + + if (null !== $name = McpHeader::nameFor($method, $params)) { + // Tool and prompt names are only SHOULD-constrained to header-safe + // characters and a resource URI is not constrained at all, so + // anything unsafe is wrapped rather than dropped or mangled. + $headers[McpHeader::NAME] = McpHeader::encode($name) ?? $name; + } + + if ('tools/call' === $method && \is_string($params['name'] ?? null)) { + $arguments = \is_array($params['arguments'] ?? null) ? $params['arguments'] : []; + + foreach ($this->tools->headersFor($params['name'], $arguments) as $suffix => $value) { + $headers[McpHeader::PARAM_PREFIX.$suffix] = $value; + } + } + + return $headers; + } + + /** + * Mirrors W3C trace context from `_meta` onto its native HTTP headers, so + * an application that only knows how to set `_meta` — the transport-agnostic + * surface — still gets header-based propagation for free on HTTP. + * + * @param array|null $params + * + * @return array + */ + private function traceHeaders(?array $params): array + { + $meta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; + + $headers = []; + foreach (RequestMeta::TRACE_KEYS as $key) { + if (\is_string($meta[$key] ?? null)) { + $headers[$key] = $meta[$key]; + } + } + + return $headers; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Stateless/InputRequestResolver.php b/admin/vendor/mcp/sdk/src/Client/Stateless/InputRequestResolver.php new file mode 100644 index 0000000..a592542 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Stateless/InputRequestResolver.php @@ -0,0 +1,204 @@ + + */ +final class InputRequestResolver +{ + /** + * The only requests a server may park in `inputRequests`. Anything else is + * a server fault, and answering it would be inventing protocol. + * + * @var array> + */ + private const RESOLVABLE = [ + 'elicitation/create' => ElicitRequest::class, + 'sampling/createMessage' => CreateSamplingMessageRequest::class, + 'roots/list' => ListRootsRequest::class, + ]; + + /** + * @param RequestHandlerInterface[] $handlers + */ + public function __construct( + private readonly array $handlers, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * Reads the `inputRequests` map off a result, or null when the result is + * not an ask. + * + * A result with no `resultType` MUST be read as complete, so an absent + * member is not an ask — only the explicit `input_required` is. + * + * @param array $result + * + * @return array|null + */ + public static function asked(array $result): ?array + { + if (($result['resultType'] ?? null) !== 'input_required') { + return null; + } + + return \is_array($result['inputRequests'] ?? null) ? $result['inputRequests'] : []; + } + + /** + * Resolves every ask into the `inputResponses` map the retry carries. + * + * Keys are the server's, and each answer goes back under the key it was + * asked under — the client never reorders or renames them, since that map + * is how the server correlates answers to questions. + * + * @param array $inputRequests + * + * @return array + * + * @throws RuntimeException when an ask cannot be answered at all + */ + public function resolve(array $inputRequests): array + { + $responses = []; + + foreach ($inputRequests as $key => $ask) { + $responses[(string) $key] = $this->answer((string) $key, $ask); + } + + return $responses; + } + + /** + * @return array + */ + private function answer(string $key, mixed $ask): array + { + if (!\is_array($ask) || !\is_string($ask['method'] ?? null)) { + throw new RuntimeException(\sprintf('Server asked for input under "%s" without a method to answer.', $key)); + } + + $method = $ask['method']; + $class = self::RESOLVABLE[$method] ?? null; + + if (null === $class) { + throw new RuntimeException(\sprintf('Server asked for input under "%s" using "%s", which is not a request a client can answer.', $key, $method)); + } + + $params = $ask['params'] ?? null; + if ($params instanceof \stdClass) { + $params = (array) $params; + } + + // The ask is a bare method/params pair, but the handlers speak in + // messages. The id never reaches the wire — the answer is keyed by + // $key — so any id will do. + $request = $class::fromArray([ + 'jsonrpc' => '2.0', + 'id' => $key, + 'method' => $method, + 'params' => \is_array($params) ? $params : null, + ]); + + $this->logger->debug('Resolving multi round-trip input request', [ + 'key' => $key, + 'method' => $method, + ]); + + $result = $this->dispatch($request); + + if ($result instanceof Error) { + throw new RuntimeException(\sprintf('Cannot answer the server\'s "%s" input request under "%s": %s', $method, $key, $result->message)); + } + + return $result; + } + + /** + * @return array|Error + */ + private function dispatch(Request $request): array|Error + { + foreach ($this->handlers as $handler) { + if (!$handler->supports($request)) { + continue; + } + + try { + $response = $handler->handle($request); + } catch (\Throwable $e) { + $this->logger->error('Input request handler failed', [ + 'method' => $request::getMethod(), + 'exception' => $e, + ]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } + + if ($response instanceof Error) { + return $response; + } + + return self::resultOf($response); + } + + return Error::forMethodNotFound( + \sprintf('Client does not handle "%s" requests.', $request::getMethod()), + $request->getId(), + ); + } + + /** + * @param Response $response + * + * @return array + */ + private static function resultOf(Response $response): array + { + $result = $response->result; + + if ($result instanceof \JsonSerializable) { + $result = $result->jsonSerialize(); + } + + if ($result instanceof \stdClass) { + $result = (array) $result; + } + + return \is_array($result) ? $result : []; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Stateless/RequestEnvelope.php b/admin/vendor/mcp/sdk/src/Client/Stateless/RequestEnvelope.php new file mode 100644 index 0000000..fcc1cac --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Stateless/RequestEnvelope.php @@ -0,0 +1,78 @@ + + */ +final class RequestEnvelope +{ + public function __construct( + private readonly ProtocolVersion $protocolVersion, + private readonly ClientCapabilities $capabilities, + private readonly Implementation $clientInfo, + ) { + } + + public function protocolVersion(): ProtocolVersion + { + return $this->protocolVersion; + } + + public function withProtocolVersion(ProtocolVersion $protocolVersion): self + { + return new self($protocolVersion, $this->capabilities, $this->clientInfo); + } + + /** + * Merges the envelope into an encoded message, preserving whatever `_meta` + * the caller already put there — a `progressToken` most of all, which would + * otherwise be dropped and take every progress notification with it. + * + * @param array $payload a serialized JSON-RPC message + * + * @return array + */ + public function stamp(array $payload): array + { + $params = \is_array($payload['params'] ?? null) ? $payload['params'] : []; + $meta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; + + $params['_meta'] = [ + ...$meta, + RequestMeta::PROTOCOL_VERSION => $this->protocolVersion->value, + // ClientCapabilities encodes an empty set as `{}` already; `[]` + // would reach the server as a JSON array and fail its check. + RequestMeta::CLIENT_CAPABILITIES => $this->capabilities, + RequestMeta::CLIENT_INFO => $this->clientInfo, + ]; + + $payload['params'] = $params; + + return $payload; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Stateless/ToolCatalog.php b/admin/vendor/mcp/sdk/src/Client/Stateless/ToolCatalog.php new file mode 100644 index 0000000..3348db0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Stateless/ToolCatalog.php @@ -0,0 +1,206 @@ + + */ +final class ToolCatalog +{ + /** @var array> tool name to input schema */ + private array $schemas = []; + + /** @var array tool name to the reason it was refused */ + private array $rejected = []; + + public function __construct( + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * Records a listing page and returns the tools a caller may actually use. + * + * A tool whose annotations are malformed is dropped from the result, which + * is how the client "rejects" it: it never reaches the caller, so it cannot + * be called, and the tools listed beside it are untouched. + * + * @param list> $tools raw `tools/list` entries + * + * @return list> + */ + public function record(array $tools): array + { + $usable = []; + + foreach ($tools as $tool) { + $name = $tool['name'] ?? null; + $schema = $tool['inputSchema'] ?? null; + + if (!\is_string($name) || !\is_array($schema)) { + $usable[] = $tool; + + continue; + } + + unset($this->rejected[$name], $this->schemas[$name]); + + if (null !== $reason = McpHeader::checkAnnotations($schema)) { + $this->rejected[$name] = $reason; + + $this->logger->warning('Excluding tool with an invalid "x-mcp-header" annotation', [ + 'tool' => $name, + 'reason' => $reason, + ]); + + continue; + } + + $this->schemas[$name] = $schema; + $usable[] = $tool; + } + + return $usable; + } + + /** + * Whether the client refuses to call this tool. + * + * Only a tool that was listed and failed validation is refused; an unknown + * name is not, since the client may legitimately call a tool it never + * listed and the server is the authority on whether it exists. + */ + public function isRejected(string $name): bool + { + return isset($this->rejected[$name]); + } + + public function reasonFor(string $name): ?string + { + return $this->rejected[$name] ?? null; + } + + /** + * The `Mcp-Param-*` headers a call to $name must carry, given its arguments. + * + * An argument that is absent or null contributes no header — the + * specification reads a missing header as a missing value, so sending an + * empty one would assert something different. + * + * @param array $arguments + * + * @return array + */ + public function headersFor(string $name, array $arguments): array + { + $schema = $this->schemas[$name] ?? null; + + if (null === $schema) { + return []; + } + + $headers = []; + + foreach (self::annotations($schema) as $header => $path) { + $value = self::valueAt($arguments, $path); + + if (null === $value) { + continue; + } + + $encoded = McpHeader::encode($value); + + if (null === $encoded) { + continue; + } + + $headers[$header] = $encoded; + } + + return $headers; + } + + /** + * Every `x-mcp-header` annotation in $schema, as header name to the property + * path it mirrors. + * + * Only statically reachable properties count, matching the server's reader: + * a chain through `items`, a composition keyword or a `$ref` cannot be + * resolved without the instance, so an annotation there is out of bounds. + * + * @param array $schema + * @param list $path + * + * @return array> + */ + private static function annotations(array $schema, array $path = []): array + { + $properties = $schema['properties'] ?? null; + + if (!\is_array($properties)) { + return []; + } + + $found = []; + + foreach ($properties as $property => $definition) { + if (!\is_array($definition)) { + continue; + } + + $here = [...$path, (string) $property]; + + if (\is_string($definition['x-mcp-header'] ?? null)) { + $found[$definition['x-mcp-header']] = $here; + } + + $found = [...$found, ...self::annotations($definition, $here)]; + } + + return $found; + } + + /** + * @param array $arguments + * @param list $path + */ + private static function valueAt(array $arguments, array $path): mixed + { + $node = $arguments; + + foreach ($path as $segment) { + if (!\is_array($node) || !\array_key_exists($segment, $node)) { + return null; + } + + $node = $node[$segment]; + } + + return $node; + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Transport/BaseTransport.php b/admin/vendor/mcp/sdk/src/Client/Transport/BaseTransport.php new file mode 100644 index 0000000..a1fc193 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Transport/BaseTransport.php @@ -0,0 +1,126 @@ + + */ +abstract class BaseTransport implements TransportInterface +{ + /** @var callable(): mixed|null */ + protected $initializeCallback; + + /** @var callable(string): void|null */ + protected $messageCallback; + + /** @var callable(\Throwable): void|null */ + protected $errorCallback; + + /** @var callable(string): void|null */ + protected $closeCallback; + + protected ?ClientStateInterface $state = null; + protected LoggerInterface $logger; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + } + + public function onInitialize(callable $listener): void + { + $this->initializeCallback = $listener; + } + + public function onMessage(callable $listener): void + { + $this->messageCallback = $listener; + } + + public function onError(callable $listener): void + { + $this->errorCallback = $listener; + } + + public function onClose(callable $listener): void + { + $this->closeCallback = $listener; + } + + public function setState(ClientStateInterface $state): void + { + $this->state = $state; + } + + /** + * Perform initialization via the registered callback. + * + * @return mixed The result from the initialization callback + * + * @throws RuntimeException If no initialize listener is registered + */ + protected function handleInitialize(): mixed + { + if (!\is_callable($this->initializeCallback)) { + throw new RuntimeException('No initialize listener registered'); + } + + return ($this->initializeCallback)(); + } + + /** + * Handle an incoming message from the server. + */ + protected function handleMessage(string $message): void + { + if (\is_callable($this->messageCallback)) { + try { + ($this->messageCallback)($message); + } catch (\Throwable $e) { + $this->handleError($e); + } + } + } + + /** + * Handle a transport error. + */ + protected function handleError(\Throwable $error): void + { + $this->logger->error('Transport error', ['exception' => $error]); + + if (\is_callable($this->errorCallback)) { + ($this->errorCallback)($error); + } + } + + /** + * Handle connection close. + */ + protected function handleClose(string $reason): void + { + $this->logger->info('Transport closed', ['reason' => $reason]); + + if (\is_callable($this->closeCallback)) { + ($this->closeCallback)($reason); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Transport/HeaderAwareTransportInterface.php b/admin/vendor/mcp/sdk/src/Client/Transport/HeaderAwareTransportInterface.php new file mode 100644 index 0000000..998a044 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Transport/HeaderAwareTransportInterface.php @@ -0,0 +1,37 @@ + + */ +interface HeaderAwareTransportInterface extends TransportInterface +{ + /** + * Register the source of per-message headers. + * + * @param callable(string $payload): array $callback receives the encoded message + */ + public function onHeaders(callable $callback): void; +} diff --git a/admin/vendor/mcp/sdk/src/Client/Transport/HttpTransport.php b/admin/vendor/mcp/sdk/src/Client/Transport/HttpTransport.php new file mode 100644 index 0000000..b5499e8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Transport/HttpTransport.php @@ -0,0 +1,423 @@ + + */ +class HttpTransport extends BaseTransport implements HeaderAwareTransportInterface +{ + private ClientInterface $httpClient; + private RequestFactoryInterface $requestFactory; + private StreamFactoryInterface $streamFactory; + + private ?string $sessionId = null; + + /** @var (callable(string): array)|null */ + private $headerCallback; + + /** @var McpFiber|null */ + private ?\Fiber $activeFiber = null; + + /** @var (callable(float, ?float, ?string): void)|null */ + private $activeProgressCallback; + + /** @var StreamInterface|null Active SSE stream being read */ + private ?StreamInterface $activeStream = null; + + /** @var string Buffer for incomplete SSE data */ + private string $sseBuffer = ''; + + /** + * Default cap on the bytes buffered while waiting for a complete SSE event. + */ + public const DEFAULT_MAX_SSE_BUFFER_BYTES = 8 * 1024 * 1024; + + private readonly int $maxSseBufferBytes; + + /** + * @param string $endpoint The MCP server endpoint URL + * @param array $headers Additional headers to send + * @param ClientInterface|null $httpClient PSR-18 HTTP client (auto-discovered if null) + * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory (auto-discovered if null) + * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory (auto-discovered if null) + * @param int $maxSseBufferBytes Maximum bytes buffered while waiting for a complete + * SSE event. A server that never sends the "\n\n" event + * delimiter would otherwise grow the buffer without bound + * and exhaust client memory; reaching the cap aborts the + * stream instead. Raise it for servers that legitimately + * emit single events larger than the default. + */ + public function __construct( + private readonly string $endpoint, + private readonly array $headers = [], + ?ClientInterface $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?LoggerInterface $logger = null, + int $maxSseBufferBytes = self::DEFAULT_MAX_SSE_BUFFER_BYTES, + ) { + parent::__construct($logger); + + if ($maxSseBufferBytes < 1) { + throw new InvalidArgumentException(\sprintf('The maximum SSE buffer size must be a positive number of bytes, got %d.', $maxSseBufferBytes)); + } + + $this->maxSseBufferBytes = $maxSseBufferBytes; + $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); + $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function connect(): void + { + $this->activeFiber = new \Fiber(fn () => $this->handleInitialize()); + + $this->activeFiber->start(); + + while (!$this->activeFiber->isTerminated()) { + $this->tick(); + } + + $result = $this->activeFiber->getReturn(); + $this->activeFiber = null; + + if ($result instanceof Error) { + throw new ConnectionException('Initialization failed: '.$result->message); + } + + $this->logger->info('HTTP client connected and initialized', ['endpoint' => $this->endpoint]); + } + + public function onHeaders(callable $callback): void + { + $this->headerCallback = $callback; + } + + public function send(string $data): void + { + $request = $this->requestFactory->createRequest('POST', $this->endpoint) + ->withHeader('Content-Type', 'application/json') + ->withHeader('Accept', 'application/json, text/event-stream') + ->withBody($this->streamFactory->createStream($data)); + + if (null !== $this->sessionId) { + $request = $request->withHeader('Mcp-Session-Id', $this->sessionId); + } + + // Protocol-derived first, so an explicitly configured header still wins: + // the caller passing one is making a deliberate choice about this + // connection, and a proxy credential is the usual reason. + foreach ($this->protocolHeaders($data) as $name => $value) { + $request = $request->withHeader($name, $value); + } + + foreach ($this->headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $this->logger->debug('Sending HTTP request', ['data' => $data]); + + try { + $response = $this->httpClient->sendRequest($request); + } catch (\Throwable $e) { + $this->handleError($e); + throw new ConnectionException('HTTP request failed: '.$e->getMessage(), 0, $e); + } + + if ($response->hasHeader('Mcp-Session-Id')) { + $this->sessionId = $response->getHeaderLine('Mcp-Session-Id'); + $this->logger->debug('Received session ID', ['session_id' => $this->sessionId]); + } + + $contentType = strtolower($response->getHeaderLine('Content-Type')); + + if (str_contains($contentType, 'text/event-stream')) { + $this->activeStream = $response->getBody(); + $this->sseBuffer = ''; + } elseif (str_contains($contentType, 'application/json')) { + $body = $response->getBody()->getContents(); + if (!empty($body)) { + $this->handleMessage($body); + } + } + } + + /** + * @param McpFiber $fiber + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + */ + public function runRequest(\Fiber $fiber, ?callable $onProgress = null): Response|Error + { + $this->activeFiber = $fiber; + $this->activeProgressCallback = $onProgress; + $fiber->start(); + + while (!$fiber->isTerminated()) { + $this->tick(); + } + + $this->activeFiber = null; + $this->activeProgressCallback = null; + $this->activeStream = null; + + return $fiber->getReturn(); + } + + public function close(): void + { + if (null !== $this->sessionId) { + try { + $request = $this->requestFactory->createRequest('DELETE', $this->endpoint) + ->withHeader('Mcp-Session-Id', $this->sessionId); + + foreach ($this->headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $this->httpClient->sendRequest($request); + $this->logger->info('Session closed', ['session_id' => $this->sessionId]); + } catch (\Throwable $e) { + $this->logger->warning('Failed to close session', ['exception' => $e]); + } + } + + $this->sessionId = null; + $this->activeStream = null; + $this->handleClose('Transport closed'); + } + + /** + * @return array + */ + private function protocolHeaders(string $payload): array + { + if (!\is_callable($this->headerCallback)) { + return []; + } + + try { + return ($this->headerCallback)($payload); + } catch (\Throwable $e) { + // Headers mirror the body; failing to derive them is a bug worth + // reporting, but dropping the request would be a worse outcome than + // sending it the way an earlier revision would have. + $this->logger->error('Could not derive protocol headers', ['exception' => $e]); + + return []; + } + } + + private function tick(): void + { + $this->processSSEStream(); + $this->processProgress(); + $this->processFiber(); + + usleep(1000); // 1ms + } + + /** + * Read SSE data incrementally from active stream. + */ + private function processSSEStream(): void + { + if (null === $this->activeStream) { + return; + } + + if (!$this->activeStream->eof()) { + $chunk = $this->activeStream->read(4096); + if ('' !== $chunk) { + if (\strlen($this->sseBuffer) + \strlen($chunk) > $this->maxSseBufferBytes) { + $this->abortSseStream(\sprintf('buffered %d bytes without a complete event, exceeding the %d byte limit', \strlen($this->sseBuffer) + \strlen($chunk), $this->maxSseBufferBytes)); + + return; + } + + $this->sseBuffer .= $chunk; + } + } + + while (null !== ($event = $this->extractSSEEvent())) { + if (!empty(trim($event))) { + $this->processSSEEvent($event); + } + } + + if ($this->activeStream->eof()) { + // The stream ended without a trailing blank line: dispatch what is left. + if (!empty(trim($this->sseBuffer))) { + $this->processSSEEvent($this->sseBuffer); + } + + $this->sseBuffer = ''; + $this->activeStream = null; + } + } + + /** + * Tear down the active SSE stream and fail any in-flight request. + * + * The waiting fiber is resolved with an error immediately so the caller + * fails fast, rather than spinning until the request timeout elapses. + */ + private function abortSseStream(string $reason): void + { + $bufferedBytes = \strlen($this->sseBuffer); + $this->sseBuffer = ''; + $this->activeStream = null; + + $this->logger->warning('Aborting SSE stream: '.$reason, [ + 'session_id' => $this->sessionId, + 'buffered_bytes' => $bufferedBytes, + 'max_sse_buffer_bytes' => $this->maxSseBufferBytes, + ]); + + if (null === $this->state) { + return; + } + + foreach ($this->state->getPendingRequests() as $pending) { + $requestId = $pending['request_id']; + $error = Error::forInternalError('SSE stream aborted: '.$reason, $requestId); + $this->state->storeResponse($requestId, $error->jsonSerialize()); + } + } + + /** + * Take the next complete event off the buffer, or null if none is complete yet. + * + * Per the SSE specification, lines are terminated by CRLF, LF or CR, so an + * event is delimited by any pair of those. Servers built on sse-starlette + * (the MCP Python SDK) use CRLF. + */ + private function extractSSEEvent(): ?string + { + $position = null; + $length = 0; + + foreach (["\r\n\r\n", "\n\n", "\r\r"] as $delimiter) { + $found = strpos($this->sseBuffer, $delimiter); + + if (false !== $found && (null === $position || $found < $position)) { + $position = $found; + $length = \strlen($delimiter); + } + } + + if (null === $position) { + return null; + } + + $event = substr($this->sseBuffer, 0, $position); + $this->sseBuffer = substr($this->sseBuffer, $position + $length); + + return $event; + } + + /** + * Parse a single SSE event and handle the message. + */ + private function processSSEEvent(string $event): void + { + $data = ''; + + foreach (preg_split("/\r\n|\r|\n/", $event) ?: [] as $line) { + if (str_starts_with($line, 'data:')) { + $data .= trim(substr($line, 5)); + } + } + + if (!empty($data)) { + $this->handleMessage($data); + } + } + + /** + * Process pending progress updates from session and execute callback. + */ + private function processProgress(): void + { + if (null === $this->activeProgressCallback || null === $this->state) { + return; + } + + $updates = $this->state->consumeProgressUpdates(); + + foreach ($updates as $update) { + try { + ($this->activeProgressCallback)( + $update['progress'], + $update['total'], + $update['message'], + ); + } catch (\Throwable $e) { + $this->logger->warning('Progress callback failed', ['exception' => $e]); + } + } + } + + private function processFiber(): void + { + if (null === $this->activeFiber || !$this->activeFiber->isSuspended()) { + return; + } + + if (null === $this->state) { + return; + } + + $pendingRequests = $this->state->getPendingRequests(); + + foreach ($pendingRequests as $pending) { + $requestId = $pending['request_id']; + $timestamp = $pending['timestamp']; + $timeout = $pending['timeout']; + + $response = $this->state->consumeResponse($requestId); + + if (null !== $response) { + $this->logger->debug('Resuming fiber with response', ['request_id' => $requestId]); + $this->activeFiber->resume($response); + + return; + } + + if (time() - $timestamp >= $timeout) { + $this->logger->warning('Request timed out', ['request_id' => $requestId]); + $error = Error::forInternalError('Request timed out', $requestId); + $this->activeFiber->resume($error); + + return; + } + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Transport/StdioTransport.php b/admin/vendor/mcp/sdk/src/Client/Transport/StdioTransport.php new file mode 100644 index 0000000..f102961 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Transport/StdioTransport.php @@ -0,0 +1,339 @@ + + */ +class StdioTransport extends BaseTransport +{ + /** @var resource|null */ + private $process; + + /** @var resource|null */ + private $stdin; + + /** @var resource|null */ + private $stdout; + + /** @var resource|null */ + private $stderr; + + private string $inputBuffer = ''; + + /** + * Default cap on the bytes buffered while waiting for a complete line. + */ + public const DEFAULT_MAX_BUFFER_SIZE = 4 * 1024 * 1024; + + /** @var McpFiber|null */ + private ?\Fiber $activeFiber = null; + + /** @var (callable(float, ?float, ?string): void)|null */ + private $activeProgressCallback; + + /** + * @param string $command The command to run + * @param array $args Command arguments + * @param string|null $cwd Working directory + * @param array|null $env Environment variables + * @param int $maxBufferSize Maximum bytes buffered while waiting for a complete line. The + * buffer is only drained on a "\n"; a spawned server that streams + * stdout without ever emitting a newline would otherwise grow it + * without bound and exhaust client memory. Reaching the cap aborts + * the read instead. Raise it for servers that emit single frames + * larger than the default. + */ + public function __construct( + private readonly string $command, + private readonly array $args = [], + private readonly ?string $cwd = null, + private readonly ?array $env = null, + ?LoggerInterface $logger = null, + private readonly int $maxBufferSize = self::DEFAULT_MAX_BUFFER_SIZE, + ) { + parent::__construct($logger); + + if ($maxBufferSize < 1) { + throw new InvalidArgumentException(\sprintf('The maximum buffer size must be a positive number of bytes, got %d.', $maxBufferSize)); + } + } + + public function connect(): void + { + $this->spawnProcess(); + + $this->activeFiber = new \Fiber(fn () => $this->handleInitialize()); + + $this->activeFiber->start(); + + while (!$this->activeFiber->isTerminated()) { + $this->tick(); + } + + $result = $this->activeFiber->getReturn(); + $this->activeFiber = null; + + if ($result instanceof Error) { + $this->close(); + throw new ConnectionException('Initialization failed: '.$result->message); + } + + $this->logger->info('Client connected and initialized'); + } + + public function send(string $data): void + { + if (null === $this->stdin || !\is_resource($this->stdin)) { + throw new ConnectionException('Process stdin not available'); + } + + fwrite($this->stdin, $data."\n"); + fflush($this->stdin); + + $this->logger->debug('Sent message to server', ['data' => $data]); + } + + /** + * @param McpFiber $fiber + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + */ + public function runRequest(\Fiber $fiber, ?callable $onProgress = null): Response|Error + { + $this->activeFiber = $fiber; + $this->activeProgressCallback = $onProgress; + $fiber->start(); + + while (!$fiber->isTerminated()) { + $this->tick(); + } + + $this->activeFiber = null; + $this->activeProgressCallback = null; + + return $fiber->getReturn(); + } + + public function close(): void + { + if (\is_resource($this->stdin)) { + fclose($this->stdin); + $this->stdin = null; + } + if (\is_resource($this->stdout)) { + fclose($this->stdout); + $this->stdout = null; + } + if (\is_resource($this->stderr)) { + fclose($this->stderr); + $this->stderr = null; + } + if (\is_resource($this->process)) { + proc_terminate($this->process, 15); // SIGTERM + proc_close($this->process); + $this->process = null; + } + + $this->handleClose('Transport closed'); + } + + private function spawnProcess(): void + { + $descriptors = [ + 0 => ['pipe', 'r'], // stdin + 1 => ['pipe', 'w'], // stdout + 2 => ['pipe', 'w'], // stderr + ]; + + $cmd = escapeshellcmd($this->command); + foreach ($this->args as $arg) { + $cmd .= ' '.escapeshellarg($arg); + } + + $this->process = proc_open( + $cmd, + $descriptors, + $pipes, + $this->cwd, + $this->env + ); + + if (!\is_resource($this->process)) { + throw new ConnectionException('Failed to start process: '.$cmd); + } + + $this->stdin = $pipes[0]; + $this->stdout = $pipes[1]; + $this->stderr = $pipes[2]; + + // Set non-blocking mode for reading + stream_set_blocking($this->stdout, false); + stream_set_blocking($this->stderr, false); + + $this->logger->info('Started MCP server process', ['command' => $cmd]); + } + + private function tick(): void + { + $this->processInput(); + $this->processProgress(); + $this->processFiber(); + $this->processStderr(); + + usleep(1000); // 1ms + } + + /** + * Process pending progress updates from session and execute callback. + */ + private function processProgress(): void + { + if (null === $this->activeProgressCallback || null === $this->state) { + return; + } + + $updates = $this->state->consumeProgressUpdates(); + + foreach ($updates as $update) { + try { + ($this->activeProgressCallback)( + $update['progress'], + $update['total'], + $update['message'], + ); + } catch (\Throwable $e) { + $this->logger->warning('Progress callback failed', ['exception' => $e]); + } + } + } + + private function processInput(): void + { + if (null === $this->stdout || !\is_resource($this->stdout)) { + return; + } + + $data = fread($this->stdout, 8192); + if (false !== $data && '' !== $data) { + if (\strlen($this->inputBuffer) + \strlen($data) > $this->maxBufferSize) { + $this->abortInput(\sprintf('buffered %d bytes without a newline, exceeding the %d byte limit', \strlen($this->inputBuffer) + \strlen($data), $this->maxBufferSize)); + + return; + } + + $this->inputBuffer .= $data; + } + + while (false !== ($pos = strpos($this->inputBuffer, "\n"))) { + $line = substr($this->inputBuffer, 0, $pos); + $this->inputBuffer = substr($this->inputBuffer, $pos + 1); + + $trimmed = trim($line); + if (!empty($trimmed)) { + $this->handleMessage($trimmed); + } + } + } + + /** + * Discard the input buffer and fail any in-flight request. + * + * The waiting fiber is resolved with an error immediately so the caller + * fails fast, rather than spinning until the request timeout elapses. + */ + private function abortInput(string $reason): void + { + $bufferedBytes = \strlen($this->inputBuffer); + $this->inputBuffer = ''; + + $this->logger->warning('Aborting stdio input: '.$reason, [ + 'buffered_bytes' => $bufferedBytes, + 'max_buffer_size' => $this->maxBufferSize, + ]); + + if (null === $this->state) { + return; + } + + foreach ($this->state->getPendingRequests() as $pending) { + $requestId = $pending['request_id']; + $error = Error::forInternalError('stdio input aborted: '.$reason, $requestId); + $this->state->storeResponse($requestId, $error->jsonSerialize()); + } + } + + private function processFiber(): void + { + if (null === $this->activeFiber || !$this->activeFiber->isSuspended()) { + return; + } + + if (null === $this->state) { + return; + } + + $pendingRequests = $this->state->getPendingRequests(); + + foreach ($pendingRequests as $pending) { + $requestId = $pending['request_id']; + $timestamp = $pending['timestamp']; + $timeout = $pending['timeout']; + + // Check if response arrived + $response = $this->state->consumeResponse($requestId); + + if (null !== $response) { + $this->logger->debug('Resuming fiber with response', ['request_id' => $requestId]); + $this->activeFiber->resume($response); + + return; + } + + // Check timeout + if (time() - $timestamp >= $timeout) { + $this->logger->warning('Request timed out', ['request_id' => $requestId]); + $error = Error::forInternalError('Request timed out', $requestId); + $this->activeFiber->resume($error); + + return; + } + } + } + + private function processStderr(): void + { + if (null === $this->stderr || !\is_resource($this->stderr)) { + return; + } + + $stderr = fread($this->stderr, 8192); + if (false !== $stderr && '' !== $stderr) { + $this->logger->debug('Server stderr', ['output' => trim($stderr)]); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Client/Transport/TransportInterface.php b/admin/vendor/mcp/sdk/src/Client/Transport/TransportInterface.php new file mode 100644 index 0000000..4860128 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Client/Transport/TransportInterface.php @@ -0,0 +1,107 @@ +|Error) + * @phpstan-type FiberResume (Response|Error) + * @phpstan-type FiberSuspend array{type: 'await_response', request_id: int, timeout: int} + * @phpstan-type McpFiber \Fiber + * + * @author Kyrian Obikwelu + */ +interface TransportInterface +{ + /** + * Connect to the MCP server and perform initialization handshake. + * + * This method blocks until: + * - Initialization completes successfully + * - Connection fails (throws ConnectionException) + * + * @throws \Mcp\Exception\ConnectionException + */ + public function connect(): void; + + /** + * Send a message to the server immediately. + * + * @param string $data JSON-encoded message + */ + public function send(string $data): void; + + /** + * Run a request fiber to completion. + * + * The transport starts the fiber, runs its internal loop, and resumes + * the fiber when a response arrives or timeout occurs. + * + * During the loop, the transport checks session for progress data and + * executes the callback if provided. + * + * @param McpFiber $fiber The fiber to execute + * @param (callable(float $progress, ?float $total, ?string $message): void)|null $onProgress + * Optional callback for progress updates + * + * @return Response>|Error The response or error + */ + public function runRequest(\Fiber $fiber, ?callable $onProgress = null): Response|Error; + + /** + * Close the transport and clean up resources. + */ + public function close(): void; + + /** + * Register callback for initialization handshake. + * + * The callback should return a Fiber that performs the initialization. + * + * @param callable(): mixed $callback + */ + public function onInitialize(callable $callback): void; + + /** + * Register callback for incoming messages from server. + * + * @param callable(string $message): void $callback + */ + public function onMessage(callable $callback): void; + + /** + * Register callback for transport errors. + * + * @param callable(\Throwable $error): void $callback + */ + public function onError(callable $callback): void; + + /** + * Register callback for when connection closes. + * + * @param callable(string $reason): void $callback + */ + public function onClose(callable $callback): void; + + /** + * Set the client state for runtime state management. + */ + public function setState(ClientStateInterface $state): void; +} diff --git a/admin/vendor/mcp/sdk/src/Event/ErrorEvent.php b/admin/vendor/mcp/sdk/src/Event/ErrorEvent.php new file mode 100644 index 0000000..65ce650 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/ErrorEvent.php @@ -0,0 +1,59 @@ + + */ +final class ErrorEvent +{ + public function __construct( + private Error $error, + private readonly Request $request, + private readonly SessionInterface $session, + private readonly ?\Throwable $throwable, + ) { + } + + public function getError(): Error + { + return $this->error; + } + + public function setError(Error $error): void + { + $this->error = $error; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getThrowable(): ?\Throwable + { + return $this->throwable; + } + + public function getSession(): SessionInterface + { + return $this->session; + } +} diff --git a/admin/vendor/mcp/sdk/src/Event/NotificationEvent.php b/admin/vendor/mcp/sdk/src/Event/NotificationEvent.php new file mode 100644 index 0000000..579d877 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/NotificationEvent.php @@ -0,0 +1,51 @@ + + */ +final class NotificationEvent +{ + public function __construct( + private Notification $notification, + private readonly SessionInterface $session, + ) { + } + + public function getNotification(): Notification + { + return $this->notification; + } + + public function setNotification(Notification $notification): void + { + $this->notification = $notification; + } + + public function getSession(): SessionInterface + { + return $this->session; + } + + public function getMethod(): string + { + return $this->notification::getMethod(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Event/PromptListChangedEvent.php b/admin/vendor/mcp/sdk/src/Event/PromptListChangedEvent.php new file mode 100644 index 0000000..2e86918 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/PromptListChangedEvent.php @@ -0,0 +1,19 @@ + + */ +final class PromptListChangedEvent +{ +} diff --git a/admin/vendor/mcp/sdk/src/Event/RequestEvent.php b/admin/vendor/mcp/sdk/src/Event/RequestEvent.php new file mode 100644 index 0000000..3a7de08 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/RequestEvent.php @@ -0,0 +1,51 @@ + + */ +final class RequestEvent +{ + public function __construct( + private Request $request, + private readonly SessionInterface $session, + ) { + } + + public function getRequest(): Request + { + return $this->request; + } + + public function setRequest(Request $request): void + { + $this->request = $request; + } + + public function getSession(): SessionInterface + { + return $this->session; + } + + public function getMethod(): string + { + return $this->request::getMethod(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Event/ResourceListChangedEvent.php b/admin/vendor/mcp/sdk/src/Event/ResourceListChangedEvent.php new file mode 100644 index 0000000..83120d6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/ResourceListChangedEvent.php @@ -0,0 +1,19 @@ + + */ +final class ResourceListChangedEvent +{ +} diff --git a/admin/vendor/mcp/sdk/src/Event/ResourceTemplateListChangedEvent.php b/admin/vendor/mcp/sdk/src/Event/ResourceTemplateListChangedEvent.php new file mode 100644 index 0000000..0c13f65 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/ResourceTemplateListChangedEvent.php @@ -0,0 +1,19 @@ + + */ +final class ResourceTemplateListChangedEvent +{ +} diff --git a/admin/vendor/mcp/sdk/src/Event/ResponseEvent.php b/admin/vendor/mcp/sdk/src/Event/ResponseEvent.php new file mode 100644 index 0000000..94b9a90 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/ResponseEvent.php @@ -0,0 +1,67 @@ + + */ +final class ResponseEvent +{ + /** + * @param Response $response + */ + public function __construct( + private Response $response, + private readonly Request $request, + private readonly SessionInterface $session, + ) { + } + + /** + * @return Response + */ + public function getResponse(): Response + { + return $this->response; + } + + /** + * @param Response $response + */ + public function setResponse(Response $response): void + { + $this->response = $response; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getSession(): SessionInterface + { + return $this->session; + } + + public function getMethod(): string + { + return $this->request::getMethod(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Event/ToolListChangedEvent.php b/admin/vendor/mcp/sdk/src/Event/ToolListChangedEvent.php new file mode 100644 index 0000000..84d175a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Event/ToolListChangedEvent.php @@ -0,0 +1,19 @@ + + */ +final class ToolListChangedEvent +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/BadMethodCallException.php b/admin/vendor/mcp/sdk/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000..d7c00c1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/BadMethodCallException.php @@ -0,0 +1,19 @@ + + */ +final class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ClientException.php b/admin/vendor/mcp/sdk/src/Exception/ClientException.php new file mode 100644 index 0000000..33e3668 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ClientException.php @@ -0,0 +1,31 @@ + + */ +class ClientException extends Exception +{ + public function __construct( + private readonly Error $error, + ) { + parent::__construct($error->message); + } + + public function getError(): Error + { + return $this->error; + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ClientRegistrationException.php b/admin/vendor/mcp/sdk/src/Exception/ClientRegistrationException.php new file mode 100644 index 0000000..3871146 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ClientRegistrationException.php @@ -0,0 +1,23 @@ + + */ +class ConfigurationException extends InvalidArgumentException +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ConnectionException.php b/admin/vendor/mcp/sdk/src/Exception/ConnectionException.php new file mode 100644 index 0000000..4e4527f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ConnectionException.php @@ -0,0 +1,21 @@ + + */ +class ConnectionException extends Exception +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ContainerException.php b/admin/vendor/mcp/sdk/src/Exception/ContainerException.php new file mode 100644 index 0000000..0a56987 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ContainerException.php @@ -0,0 +1,18 @@ + + */ +final class ElicitationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/Exception.php b/admin/vendor/mcp/sdk/src/Exception/Exception.php new file mode 100644 index 0000000..12ff5f0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/Exception.php @@ -0,0 +1,19 @@ + + */ +class Exception extends \Exception implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ExceptionInterface.php b/admin/vendor/mcp/sdk/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..6344930 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ExceptionInterface.php @@ -0,0 +1,19 @@ + + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/HandlerNotFoundException.php b/admin/vendor/mcp/sdk/src/Exception/HandlerNotFoundException.php new file mode 100644 index 0000000..ef222ed --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/HandlerNotFoundException.php @@ -0,0 +1,19 @@ + + */ +class HandlerNotFoundException extends \InvalidArgumentException implements NotFoundExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/InvalidArgumentException.php b/admin/vendor/mcp/sdk/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..8c85a7c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/InvalidCursorException.php b/admin/vendor/mcp/sdk/src/Exception/InvalidCursorException.php new file mode 100644 index 0000000..5b08743 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/InvalidCursorException.php @@ -0,0 +1,24 @@ + + */ +final class InvalidCursorException extends \InvalidArgumentException implements ExceptionInterface +{ + public function __construct( + public readonly string $cursor, + ) { + parent::__construct(\sprintf('Invalid value for pagination parameter "cursor": "%s"', $cursor)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/InvalidInputMessageException.php b/admin/vendor/mcp/sdk/src/Exception/InvalidInputMessageException.php new file mode 100644 index 0000000..0f96122 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/InvalidInputMessageException.php @@ -0,0 +1,32 @@ + + */ +class InvalidInputMessageException extends \InvalidArgumentException implements ExceptionInterface +{ + private string|int|null $requestId = null; + + public function getRequestId(): string|int|null + { + return $this->requestId; + } + + public function setRequestId(string|int|null $requestId): self + { + $this->requestId = $requestId; + + return $this; + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/LogicException.php b/admin/vendor/mcp/sdk/src/Exception/LogicException.php new file mode 100644 index 0000000..2ebe44c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/LogicException.php @@ -0,0 +1,19 @@ + + */ +final class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/MissingRequestMetaException.php b/admin/vendor/mcp/sdk/src/Exception/MissingRequestMetaException.php new file mode 100644 index 0000000..1499205 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/MissingRequestMetaException.php @@ -0,0 +1,22 @@ + + */ +class MissingRequestMetaException extends InvalidArgumentException +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/MissingRequiredClientCapabilityException.php b/admin/vendor/mcp/sdk/src/Exception/MissingRequiredClientCapabilityException.php new file mode 100644 index 0000000..aa8b031 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/MissingRequiredClientCapabilityException.php @@ -0,0 +1,33 @@ + + */ +class MissingRequiredClientCapabilityException extends \RuntimeException implements ExceptionInterface +{ + public function __construct( + public readonly ClientCapabilities $requiredCapabilities, + string $message = 'Request requires a client capability that was not declared.', + ) { + parent::__construct($message); + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/NotFoundExceptionInterface.php b/admin/vendor/mcp/sdk/src/Exception/NotFoundExceptionInterface.php new file mode 100644 index 0000000..faecad6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/NotFoundExceptionInterface.php @@ -0,0 +1,19 @@ + + */ +interface NotFoundExceptionInterface extends ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/PromptGetException.php b/admin/vendor/mcp/sdk/src/Exception/PromptGetException.php new file mode 100644 index 0000000..7eec0da --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/PromptGetException.php @@ -0,0 +1,19 @@ + + */ +final class PromptGetException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/PromptNotFoundException.php b/admin/vendor/mcp/sdk/src/Exception/PromptNotFoundException.php new file mode 100644 index 0000000..81b7c6e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/PromptNotFoundException.php @@ -0,0 +1,24 @@ + + */ +final class PromptNotFoundException extends \RuntimeException implements NotFoundExceptionInterface +{ + public function __construct( + public readonly string $name, + ) { + parent::__construct(\sprintf('Prompt not found: "%s".', $name)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/RegistryException.php b/admin/vendor/mcp/sdk/src/Exception/RegistryException.php new file mode 100644 index 0000000..c483a01 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/RegistryException.php @@ -0,0 +1,35 @@ + + */ +class RequestException extends Exception +{ + private ?Error $error; + + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, ?Error $error = null) + { + parent::__construct($message, $code, $previous); + $this->error = $error; + } + + public static function fromError(Error $error): self + { + return new self($error->message, $error->code, null, $error); + } + + public function getError(): ?Error + { + return $this->error; + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/RequestStateException.php b/admin/vendor/mcp/sdk/src/Exception/RequestStateException.php new file mode 100644 index 0000000..2dde880 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/RequestStateException.php @@ -0,0 +1,24 @@ + + */ +class RequestStateException extends InvalidArgumentException +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ResourceNotFoundException.php b/admin/vendor/mcp/sdk/src/Exception/ResourceNotFoundException.php new file mode 100644 index 0000000..420ac1a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ResourceNotFoundException.php @@ -0,0 +1,24 @@ + + */ +final class ResourceNotFoundException extends \RuntimeException implements NotFoundExceptionInterface +{ + public function __construct( + public readonly string $uri, + ) { + parent::__construct(\sprintf('Resource not found for uri: "%s".', $uri)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ResourceReadException.php b/admin/vendor/mcp/sdk/src/Exception/ResourceReadException.php new file mode 100644 index 0000000..a89dec8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ResourceReadException.php @@ -0,0 +1,19 @@ + + */ +final class ResourceReadException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/RootsException.php b/admin/vendor/mcp/sdk/src/Exception/RootsException.php new file mode 100644 index 0000000..8d12cb0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/RootsException.php @@ -0,0 +1,24 @@ + + */ +final class RootsException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/RuntimeException.php b/admin/vendor/mcp/sdk/src/Exception/RuntimeException.php new file mode 100644 index 0000000..b68a1bf --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + */ +final class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/SamplingException.php b/admin/vendor/mcp/sdk/src/Exception/SamplingException.php new file mode 100644 index 0000000..17abceb --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/SamplingException.php @@ -0,0 +1,24 @@ + + */ +final class SamplingException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ServiceNotFoundException.php b/admin/vendor/mcp/sdk/src/Exception/ServiceNotFoundException.php new file mode 100644 index 0000000..2d22708 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ServiceNotFoundException.php @@ -0,0 +1,18 @@ + + */ +class TimeoutException extends Exception +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ToolCallException.php b/admin/vendor/mcp/sdk/src/Exception/ToolCallException.php new file mode 100644 index 0000000..01ba9f4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ToolCallException.php @@ -0,0 +1,19 @@ + + */ +final class ToolCallException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Exception/ToolNotFoundException.php b/admin/vendor/mcp/sdk/src/Exception/ToolNotFoundException.php new file mode 100644 index 0000000..0a864e7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Exception/ToolNotFoundException.php @@ -0,0 +1,24 @@ + + */ +final class ToolNotFoundException extends \RuntimeException implements NotFoundExceptionInterface +{ + public function __construct( + public readonly string $name, + ) { + parent::__construct(\sprintf('Tool not found: "%s".', $name)); + } +} diff --git a/admin/vendor/mcp/sdk/src/JsonRpc/MessageFactory.php b/admin/vendor/mcp/sdk/src/JsonRpc/MessageFactory.php new file mode 100644 index 0000000..27860a2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/JsonRpc/MessageFactory.php @@ -0,0 +1,216 @@ + + * @author Kyrian Obikwelu + */ +final class MessageFactory +{ + /** + * Registry of all known message classes that have methods. + * + * @var list|class-string> + */ + private const REGISTERED_MESSAGES = [ + Schema\Notification\CancelledNotification::class, + Schema\Notification\InitializedNotification::class, + Schema\Notification\LoggingMessageNotification::class, + Schema\Notification\ProgressNotification::class, + Schema\Notification\PromptListChangedNotification::class, + Schema\Notification\ResourceListChangedNotification::class, + Schema\Notification\ResourceUpdatedNotification::class, + Schema\Notification\RootsListChangedNotification::class, + Schema\Notification\ToolListChangedNotification::class, + + Schema\Request\CallToolRequest::class, + Schema\Request\CompletionCompleteRequest::class, + Schema\Request\CreateSamplingMessageRequest::class, + Schema\Request\ElicitRequest::class, + Schema\Request\GetPromptRequest::class, + Schema\Request\InitializeRequest::class, + Schema\Request\ListPromptsRequest::class, + Schema\Request\ListResourcesRequest::class, + Schema\Request\ListResourceTemplatesRequest::class, + Schema\Request\ListRootsRequest::class, + Schema\Request\ListToolsRequest::class, + Schema\Request\PingRequest::class, + Schema\Request\ReadResourceRequest::class, + Schema\Request\ResourceSubscribeRequest::class, + Schema\Request\ResourceUnsubscribeRequest::class, + Schema\Request\SetLogLevelRequest::class, + ]; + + /** + * Upper bound on the number of messages accepted in a single batch, guarding + * against amplification where one small request expands into many operations. + */ + public const DEFAULT_MAX_BATCH_SIZE = 100; + + /** + * @param list|class-string> $registeredMessages + * @param int $maxBatchSize Maximum number of messages accepted in a single JSON-RPC batch + */ + public function __construct( + private readonly array $registeredMessages, + private readonly int $maxBatchSize = self::DEFAULT_MAX_BATCH_SIZE, + ) { + if ($this->maxBatchSize < 1) { + throw new InvalidArgumentException('maxBatchSize must be at least 1.'); + } + + foreach ($this->registeredMessages as $messageClass) { + if (!is_subclass_of($messageClass, Request::class) && !is_subclass_of($messageClass, Notification::class)) { + throw new InvalidArgumentException(\sprintf('Message classes must extend %s or %s.', Request::class, Notification::class)); + } + } + } + + /** + * Creates a new Factory instance with all the protocol's default messages. + * + * @param list|class-string> $additional message classes an extension defines + */ + public static function make(int $maxBatchSize = self::DEFAULT_MAX_BATCH_SIZE, array $additional = []): self + { + return new self([...self::REGISTERED_MESSAGES, ...$additional], $maxBatchSize); + } + + /** + * Creates message objects from JSON input. + * + * Supports both single messages and batch requests. Returns an array containing + * MessageInterface objects or InvalidInputMessageException instances for invalid messages. + * + * @return array + * + * @throws \JsonException When the input string is not valid JSON + */ + public function create(string $input): array + { + $data = json_decode($input, true, flags: \JSON_THROW_ON_ERROR); + + // A JSON-RPC payload is a single message (JSON object) or a batch (JSON + // array). Anything else (scalar, null) is invalid input rather than a + // parse error, and must not reach the per-message loop below. + if (!\is_array($data)) { + return [new InvalidInputMessageException('A JSON-RPC message must be a JSON object or a batch array.')]; + } + + // json_decode(assoc: true) maps both objects and arrays to PHP arrays. A + // list is a batch; a non-list (string keys) is a single message. An empty + // array is ambiguous ({} vs []) and invalid as either, so reject it. + if ([] === $data) { + return [new InvalidInputMessageException('A JSON-RPC message must not be empty.')]; + } + + if (array_is_list($data)) { + if (\count($data) > $this->maxBatchSize) { + return [new InvalidInputMessageException(\sprintf('JSON-RPC batch size %d exceeds the maximum allowed batch size of %d.', \count($data), $this->maxBatchSize))]; + } + + $batch = $data; + } else { + $batch = [$data]; + } + + $messages = []; + foreach ($batch as $message) { + try { + if (!\is_array($message)) { + throw new InvalidInputMessageException('A JSON-RPC message must be a JSON object.'); + } + + $messages[] = $this->createMessage($message); + } catch (InvalidInputMessageException $e) { + // Recover the id only when it's a valid JSON-RPC scalar; + // a null or malformed id is left at the exception's null default. + if (\is_array($message) && isset($message['id']) && (\is_string($message['id']) || \is_int($message['id']))) { + $e->setRequestId($message['id']); + } + $messages[] = $e; + } + } + + return $messages; + } + + /** + * Creates a single message object from parsed JSON data. + * + * @param array $data + * + * @throws InvalidInputMessageException + */ + private function createMessage(array $data): MessageInterface + { + try { + if (isset($data['error'])) { + return Error::fromArray($data); + } + + if (isset($data['result'])) { + return Response::fromArray($data); + } + + if (!isset($data['method'])) { + throw new InvalidInputMessageException('Invalid JSON-RPC message: missing "method", "result", or "error" field.'); + } + + if (!\is_string($data['method'])) { + throw new InvalidInputMessageException('Invalid JSON-RPC message: "method" must be a string.'); + } + + $messageClass = $this->findMessageClassByMethod($data['method']); + + return $messageClass::fromArray($data); + } catch (InvalidArgumentException $e) { + throw new InvalidInputMessageException($e->getMessage(), 0, $e); + } + } + + /** + * Finds the registered message class for a given method name. + * + * @return class-string|class-string + * + * @throws InvalidInputMessageException + */ + private function findMessageClassByMethod(string $method): string + { + foreach ($this->registeredMessages as $messageClass) { + if ($messageClass::getMethod() === $method) { + return $messageClass; + } + } + + throw new InvalidInputMessageException(\sprintf('Unknown method "%s".', $method)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Annotations.php b/admin/vendor/mcp/sdk/src/Schema/Annotations.php new file mode 100644 index 0000000..ec64e7a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Annotations.php @@ -0,0 +1,118 @@ + + */ +class Annotations implements \JsonSerializable +{ + /** + * @param Role[]|null $audience Describes who the intended customer of this object or data is. + * + * It can include multiple entries to indicate content useful for multiple audiences (e.g., `[Role::User, Role::Assistant]`). + * @param float|null $priority Describes how important this data is for operating the server. + * + * A value of 1 means "most important," and indicates that the data is + * effectively required, while 0 means "least important," and indicates that + * the data is entirely optional. + */ + public function __construct( + public readonly ?array $audience = null, + public readonly ?float $priority = null, + ) { + if (null !== $this->priority && ($this->priority < 0 || $this->priority > 1)) { + throw new InvalidArgumentException('Annotation priority must be between 0 and 1.'); + } + if (null !== $this->audience) { + foreach ($this->audience as $role) { + if (!$role instanceof Role) { + throw new InvalidArgumentException('All audience members must be instances of Role enum.'); + } + } + } + } + + /** + * @param AnnotationsData $data + */ + public static function fromArray(array $data): self + { + $audience = null; + if (isset($data['audience']) && \is_array($data['audience'])) { + $audience = array_map( + static function (mixed $role): Role { + if (!\is_string($role) || null === $case = Role::tryFrom($role)) { + throw new InvalidArgumentException('Each entry in "audience" must be a valid role.'); + } + + return $case; + }, + $data['audience'], + ); + } + + if (isset($data['priority']) && !\is_float($data['priority']) && !\is_int($data['priority'])) { + throw new InvalidArgumentException('Invalid "priority" in Annotations data; expected a number.'); + } + + return new self( + $audience, + isset($data['priority']) ? (float) $data['priority'] : null + ); + } + + /** + * Hydrates an optional "annotations" field, rejecting a value that is present but not an object. + * + * @param string $context the surrounding schema type, used for the error message + */ + public static function tryFromArray(mixed $data, string $context): ?self + { + if (null === $data) { + return null; + } + + if (!\is_array($data)) { + throw new InvalidArgumentException(\sprintf('Invalid "annotations" in %s data; expected an array.', $context)); + } + + return self::fromArray($data); + } + + /** + * @return AnnotationsData + */ + public function jsonSerialize(): array + { + $data = []; + if (null !== $this->audience) { + $data['audience'] = array_map(static fn (Role $r) => $r->value, $this->audience); + } + if (null !== $this->priority) { + $data['priority'] = $this->priority; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ClientCapabilities.php b/admin/vendor/mcp/sdk/src/Schema/ClientCapabilities.php new file mode 100644 index 0000000..75d50ff --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ClientCapabilities.php @@ -0,0 +1,199 @@ + + */ +class ClientCapabilities implements \JsonSerializable +{ + /** + * @param array $experimental + * @param ?array $extensions protocol extensions the client supports (e.g. io.modelcontextprotocol/ui) + * @param ?bool $samplingContext the `sampling.context` sub-capability + * @param ?bool $samplingTools the `sampling.tools` sub-capability + * @param ?bool $elicitationForm The `elicitation.form` sub-capability. Implied by declaring + * `elicitation` without naming any mode. + * @param ?bool $elicitationUrl the `elicitation.url` sub-capability + * + * The sub-capabilities trail `extensions` rather than sitting next to `sampling` and + * `elicitation` so that existing positional calls keep working. Pass them by name. + */ + public function __construct( + public readonly ?bool $roots = false, + public readonly ?bool $rootsListChanged = null, + public readonly ?bool $sampling = null, + public readonly ?bool $elicitation = null, + public readonly ?array $experimental = null, + public readonly ?array $extensions = null, + public readonly ?bool $samplingContext = null, + public readonly ?bool $samplingTools = null, + public readonly ?bool $elicitationForm = null, + public readonly ?bool $elicitationUrl = null, + ) { + } + + /** + * @param array{ + * roots?: array{ + * listChanged?: bool, + * }, + * sampling?: array{context?: mixed, tools?: mixed}|object, + * elicitation?: array{form?: mixed, url?: mixed}|object|bool, + * experimental?: array, + * extensions?: array, + * } $data + */ + public static function fromArray(array $data): self + { + $rootsEnabled = isset($data['roots']); + $rootsListChanged = null; + if ($rootsEnabled) { + if (\is_array($data['roots']) && \array_key_exists('listChanged', $data['roots'])) { + $rootsListChanged = (bool) $data['roots']['listChanged']; + } elseif (\is_object($data['roots']) && property_exists($data['roots'], 'listChanged')) { + $rootsListChanged = (bool) $data['roots']->listChanged; + } + } + + $sampling = null; + $samplingContext = null; + $samplingTools = null; + if (isset($data['sampling'])) { + $sampling = true; + if (\is_array($data['sampling'])) { + $samplingContext = isset($data['sampling']['context']); + $samplingTools = isset($data['sampling']['tools']); + } elseif (\is_object($data['sampling'])) { + $samplingContext = property_exists($data['sampling'], 'context'); + $samplingTools = property_exists($data['sampling'], 'tools'); + } + } + + $elicitation = null; + $elicitationForm = null; + $elicitationUrl = null; + if (isset($data['elicitation'])) { + $elicitation = true; + $elicitationUrl = self::namesMode($data['elicitation'], 'url'); + // Form mode is the backwards-compatible default: an `elicitation` capability + // naming no mode at all means form, the only shape that existed before `url`. + // Naming any mode is an explicit statement, so `{"url": {}}` is not form. + $elicitationForm = self::namesMode($data['elicitation'], 'form') || !$elicitationUrl; + } + + return new self( + $rootsEnabled, + $rootsListChanged, + $sampling, + $elicitation, + \is_array($data['experimental'] ?? null) ? $data['experimental'] : null, + \is_array($data['extensions'] ?? null) ? $data['extensions'] : null, + $samplingContext, + $samplingTools, + $elicitationForm, + $elicitationUrl, + ); + } + + /** + * A mode is declared by the presence of a (possibly empty) object, so only the + * key matters — not whatever it holds. A boolean `elicitation` names none. + */ + private static function namesMode(mixed $capability, string $name): bool + { + if (\is_array($capability)) { + return \array_key_exists($name, $capability); + } + + if (\is_object($capability)) { + return property_exists($capability, $name); + } + + return false; + } + + /** + * @param array $extensions + */ + public function withExtensions(array $extensions): self + { + return new self( + $this->roots, + $this->rootsListChanged, + $this->sampling, + $this->elicitation, + $this->experimental, + array_replace($this->extensions ?? [], $extensions), + $this->samplingContext, + $this->samplingTools, + $this->elicitationForm, + $this->elicitationUrl, + ); + } + + /** + * @return array{ + * roots?: object, + * sampling?: object, + * elicitation?: object, + * experimental?: object, + * extensions?: object, + * }|\stdClass + */ + public function jsonSerialize(): array|object + { + $data = []; + if ($this->roots || $this->rootsListChanged) { + $data['roots'] = new \stdClass(); + if ($this->rootsListChanged) { + $data['roots']->listChanged = $this->rootsListChanged; + } + } + + if ($this->sampling || $this->samplingContext || $this->samplingTools) { + $data['sampling'] = new \stdClass(); + if ($this->samplingContext) { + $data['sampling']->context = new \stdClass(); + } + if ($this->samplingTools) { + $data['sampling']->tools = new \stdClass(); + } + } + + if ($this->elicitation || $this->elicitationForm || $this->elicitationUrl) { + $data['elicitation'] = new \stdClass(); + if ($this->elicitationForm) { + $data['elicitation']->form = new \stdClass(); + } + if ($this->elicitationUrl) { + $data['elicitation']->url = new \stdClass(); + } + } + + if ($this->experimental) { + $data['experimental'] = (object) $this->experimental; + } + + if ($this->extensions) { + $data['extensions'] = (object) array_map( + static fn (mixed $settings): mixed => \is_array($settings) ? (object) $settings : $settings, + $this->extensions, + ); + } + + return $data ?: new \stdClass(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/AudioContent.php b/admin/vendor/mcp/sdk/src/Schema/Content/AudioContent.php new file mode 100644 index 0000000..cf7418c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/AudioContent.php @@ -0,0 +1,120 @@ + + */ +class AudioContent extends Content +{ + public function __construct( + public readonly string $data, + public readonly string $mimeType, + public readonly ?Annotations $annotations = null, + ) { + parent::__construct('audio'); + } + + /** + * @param AudioContentData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['data']) || !\is_string($data['data'])) { + throw new InvalidArgumentException('Missing or invalid "data" in AudioContent data.'); + } + if (!isset($data['mimeType']) || !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Missing or invalid "mimeType" in AudioContent data.'); + } + + return new self( + $data['data'], + $data['mimeType'], + Annotations::tryFromArray($data['annotations'] ?? null, 'AudioContent') + ); + } + + /** + * Create a new AudioContent from a file path. + * + * @param string $path Path to the audio file + * @param string|null $mimeType Optional MIME type override + * @param ?Annotations $annotations Optional annotations describing the content + * + * @throws InvalidArgumentException If the file doesn't exist + */ + public static function fromFile(string $path, ?string $mimeType = null, ?Annotations $annotations = null): self + { + if (!file_exists($path)) { + throw new InvalidArgumentException(\sprintf('Audio file not found: "%s".', $path)); + } + + $content = file_get_contents($path); + if (false === $content) { + throw new RuntimeException(\sprintf('Could not read audio file: "%s".', $path)); + } + $data = base64_encode($content); + $detectedMime = $mimeType ?? mime_content_type($path) ?: 'application/octet-stream'; + + return new self($data, $detectedMime, $annotations); + } + + /** + * Create a new AudioContent from a string. + * + * @param string $data The audio data + * @param string $mimeType MIME type of the audio + * @param ?Annotations $annotations Optional annotations describing the content + */ + public static function fromString(string $data, string $mimeType, ?Annotations $annotations = null): self + { + return new self(base64_encode($data), $mimeType, $annotations); + } + + /** + * @return array{ + * type: 'audio', + * data: string, + * mimeType: string, + * annotations?: Annotations, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'type' => 'audio', + 'data' => $this->data, + 'mimeType' => $this->mimeType, + ]; + + if (null !== $this->annotations) { + $result['annotations'] = $this->annotations; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/BlobResourceContents.php b/admin/vendor/mcp/sdk/src/Schema/Content/BlobResourceContents.php new file mode 100644 index 0000000..ee126fb --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/BlobResourceContents.php @@ -0,0 +1,99 @@ + + * } + * + * @author Kyrian Obikwelu + */ +class BlobResourceContents extends ResourceContents +{ + /** + * @param string $uri the URI of the resource or sub-resource + * @param string|null $mimeType the MIME type of the resource or sub-resource + * @param string $blob a base64-encoded string representing the binary data of the item + * @param ?array $meta Optional metadata + */ + public function __construct( + string $uri, + ?string $mimeType, + public readonly string $blob, + ?array $meta = null, + ) { + parent::__construct($uri, $mimeType, $meta); + } + + /** + * @param BlobResourceContentsData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['uri']) || !\is_string($data['uri'])) { + throw new InvalidArgumentException('Missing or invalid "uri" for BlobResourceContents.'); + } + if (!isset($data['blob']) || !\is_string($data['blob'])) { + throw new InvalidArgumentException('Missing or invalid "blob" for BlobResourceContents.'); + } + + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" for BlobResourceContents.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" for BlobResourceContents.'); + } + + return new self($data['uri'], $data['mimeType'] ?? null, $data['blob'], $data['_meta'] ?? null); + } + + /** + * @param resource $stream + * @param ?array $meta Optional metadata + * */ + public static function fromStream(string $uri, $stream, string $mimeType, ?array $meta = null): self + { + $blob = stream_get_contents($stream); + + return new self($uri, $mimeType, base64_encode($blob), $meta); + } + + /** + * @param ?array $meta Optional metadata + * */ + public static function fromSplFileInfo(string $uri, \SplFileInfo $file, ?string $explicitMimeType = null, ?array $meta = null): self + { + $mimeType = $explicitMimeType ?? mime_content_type($file->getPathname()); + $blob = file_get_contents($file->getPathname()); + + return new self($uri, $mimeType, base64_encode($blob), $meta); + } + + /** + * @return BlobResourceContentsData + */ + public function jsonSerialize(): array + { + return [ + 'blob' => $this->blob, + ...parent::jsonSerialize(), + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/Content.php b/admin/vendor/mcp/sdk/src/Schema/Content/Content.php new file mode 100644 index 0000000..f1ac394 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/Content.php @@ -0,0 +1,25 @@ + + */ +abstract class Content implements \JsonSerializable +{ + public function __construct( + public readonly string $type, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/EmbeddedResource.php b/admin/vendor/mcp/sdk/src/Schema/Content/EmbeddedResource.php new file mode 100644 index 0000000..76c4d67 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/EmbeddedResource.php @@ -0,0 +1,152 @@ + + */ +class EmbeddedResource extends Content +{ + public function __construct( + public readonly TextResourceContents|BlobResourceContents $resource, + public readonly ?Annotations $annotations = null, + ) { + parent::__construct('resource'); + } + + /** + * @param EmbeddedResourceData $data + */ + public static function fromArray(array $data): self + { + if (($data['type'] ?? null) !== 'resource') { + throw new InvalidArgumentException('Invalid type for EmbeddedResource.'); + } + if (!isset($data['resource']) || !\is_array($data['resource'])) { + throw new InvalidArgumentException('Missing or invalid "resource" field for EmbeddedResource.'); + } + + $resourceData = $data['resource']; + if (isset($resourceData['text'])) { + $resourceInstance = TextResourceContents::fromArray($resourceData); + } elseif (isset($resourceData['blob'])) { + $resourceInstance = BlobResourceContents::fromArray($resourceData); + } else { + throw new InvalidArgumentException('EmbeddedResource "resource" field must contain "text" or "blob".'); + } + + return new self( + $resourceInstance, + Annotations::tryFromArray($data['annotations'] ?? null, 'EmbeddedResource'), + ); + } + + public static function fromText(string $uri, string $text, ?string $mimeType = 'text/plain', ?Annotations $annotations = null): self + { + $textContent = new TextResourceContents($uri, $mimeType, $text); + + return new self($textContent, $annotations); + } + + public static function fromBlob(string $uri, string $base64Blob, string $mimeType, ?Annotations $annotations = null): self + { + $blobContent = new BlobResourceContents($uri, $mimeType, $base64Blob); + + return new self($blobContent, $annotations); + } + + public static function fromFile(string $uri, string $path, ?string $explicitMimeType = null, ?Annotations $annotations = null): self + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(\sprintf('File not found or not readable: "%s".', $path)); + } + $content = file_get_contents($path); + if (false === $content) { + throw new RuntimeException(\sprintf('Could not read file: "%s".', $path)); + } + + $guessedMimeType = $explicitMimeType ?? mime_content_type($path) ?: 'application/octet-stream'; + + if (self::isTextMimeTypeHeuristic($guessedMimeType) && mb_check_encoding($content, 'UTF-8')) { + $resourceContent = new TextResourceContents($uri, $guessedMimeType, $content); + } else { + $resourceContent = new BlobResourceContents($uri, $guessedMimeType, base64_encode($content)); + } + + return new self($resourceContent, $annotations); + } + + /** + * @param resource $stream + */ + public static function fromStream(string $uri, $stream, string $mimeType, ?Annotations $annotations = null): self + { + $content = stream_get_contents($stream); + if (false === $content) { + throw new RuntimeException('Could not read stream.'); + } + + return new self(new BlobResourceContents($uri, $mimeType, base64_encode($content)), $annotations); + } + + public static function fromSplFileInfo(string $uri, \SplFileInfo $file, ?string $explicitMimeType = null, ?Annotations $annotations = null): self + { + $content = file_get_contents($file->getPathname()); + if (false === $content) { + throw new RuntimeException(\sprintf('Could not read file: "%s".', $file->getPathname())); + } + + return new self(new BlobResourceContents($uri, $explicitMimeType ?? mime_content_type($file->getPathname()), base64_encode($content)), $annotations); + } + + /** + * @return array{ + * type: 'resource', + * resource: TextResourceContents|BlobResourceContents, + * annotations?: Annotations, + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'type' => $this->type, + 'resource' => $this->resource, + ]; + if (null !== $this->annotations) { + $data['annotations'] = $this->annotations; + } + + return $data; + } + + private static function isTextMimeTypeHeuristic(string $mimeType): bool + { + return str_starts_with($mimeType, 'text/') + || \in_array(strtolower($mimeType), ['application/json', 'application/xml', 'application/javascript', 'application/yaml']); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/ImageContent.php b/admin/vendor/mcp/sdk/src/Schema/Content/ImageContent.php new file mode 100644 index 0000000..9e2cbff --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/ImageContent.php @@ -0,0 +1,115 @@ + + */ +class ImageContent extends Content +{ + /** + * Create a new ImageContent instance. + * + * @param string $data Base64-encoded image data + * @param string $mimeType The MIME type of the image + * @param ?Annotations $annotations Optional annotations describing the content + */ + public function __construct( + public readonly string $data, + public readonly string $mimeType, + public readonly ?Annotations $annotations = null, + ) { + parent::__construct('image'); + } + + /** + * @param ImageContentData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['data']) || !\is_string($data['data'])) { + throw new InvalidArgumentException('Missing or invalid "data" in ImageContent data.'); + } + if (!isset($data['mimeType']) || !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Missing or invalid "mimeType" in ImageContent data.'); + } + + return new self( + $data['data'], + $data['mimeType'], + isset($data['annotations']) ? Annotations::fromArray($data['annotations']) : null + ); + } + + /** + * Create a new ImageContent from a file path. + * + * @param string $path Path to the image file + * @param string|null $mimeType Optional MIME type override + * @param ?Annotations $annotations Optional annotations describing the content + * + * @throws InvalidArgumentException If the file doesn't exist + */ + public static function fromFile(string $path, ?string $mimeType = null, ?Annotations $annotations = null): self + { + if (!file_exists($path)) { + throw new InvalidArgumentException(\sprintf('Image file not found: "%s".', $path)); + } + + $data = base64_encode(file_get_contents($path)); + $detectedMime = $mimeType ?? mime_content_type($path) ?: 'image/png'; + + return new self($data, $detectedMime, $annotations); + } + + public static function fromString(string $data, string $mimeType, ?Annotations $annotations = null): self + { + return new self(base64_encode($data), $mimeType, $annotations); + } + + /** + * Convert the content to an array. + * + * @return array{ + * type: 'image', + * data: string, + * mimeType: string, + * annotations?: Annotations, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'type' => $this->type, + 'data' => $this->data, + 'mimeType' => $this->mimeType, + ]; + + if (null !== $this->annotations) { + $result['annotations'] = $this->annotations; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/PromptMessage.php b/admin/vendor/mcp/sdk/src/Schema/Content/PromptMessage.php new file mode 100644 index 0000000..d47f5ec --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/PromptMessage.php @@ -0,0 +1,97 @@ + + */ +class PromptMessage extends Content +{ + /** + * Create a new PromptMessage instance. + * + * @param Role $role The role of the message + * @param TextContent|ImageContent|AudioContent|ResourceLink|EmbeddedResource $content The content of the message + */ + public function __construct( + public readonly Role $role, + public readonly TextContent|ImageContent|AudioContent|ResourceLink|EmbeddedResource $content, + ) { + parent::__construct('prompt'); + } + + /** + * @param PromptMessageData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['role']) || !\is_string($data['role'])) { + throw new InvalidArgumentException('Missing or invalid "role" in PromptMessage data.'); + } + if (!isset($data['content']) || !\is_array($data['content'])) { + throw new InvalidArgumentException('Missing or invalid "content" in PromptMessage data.'); + } + + $contentData = $data['content']; + $contentType = $contentData['type'] ?? null; + if (!\is_string($contentType)) { + throw new InvalidArgumentException('Missing or invalid content "type" for PromptMessage.'); + } + + $content = match ($contentType) { + 'text' => TextContent::fromArray($contentData), + 'image' => ImageContent::fromArray($contentData), + 'audio' => AudioContent::fromArray($contentData), + 'resource' => EmbeddedResource::fromArray($contentData), + 'resource_link' => ResourceLink::fromArray($contentData), + default => throw new InvalidArgumentException(\sprintf('Invalid content type "%s" for PromptMessage.', $contentType)), + }; + + if (null === $role = Role::tryFrom($data['role'])) { + throw new InvalidArgumentException(\sprintf('Invalid "role" value "%s" in PromptMessage data.', $data['role'])); + } + + return new self($role, $content); + } + + /** + * Convert the message to an array. + * + * @return array{ + * role: string, + * content: TextContent|ImageContent|AudioContent|ResourceLink|EmbeddedResource + * } + */ + public function jsonSerialize(): array + { + return [ + 'role' => $this->role->value, + 'content' => $this->content, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/ResourceContents.php b/admin/vendor/mcp/sdk/src/Schema/Content/ResourceContents.php new file mode 100644 index 0000000..ffd5599 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/ResourceContents.php @@ -0,0 +1,55 @@ + + * } + * + * @author Kyrian Obikwelu + */ +abstract class ResourceContents implements \JsonSerializable +{ + /** + * @param string $uri the URI of the resource or sub-resource + * @param string|null $mimeType the MIME type of the resource or sub-resource + * @param ?array $meta Optional metadata + */ + public function __construct( + public readonly string $uri, + public readonly ?string $mimeType = null, + public readonly ?array $meta = null, + ) { + } + + /** + * @return ResourceContentsData + */ + public function jsonSerialize(): array + { + $data = ['uri' => $this->uri]; + if (null !== $this->mimeType) { + $data['mimeType'] = $this->mimeType; + } + + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/ResourceLink.php b/admin/vendor/mcp/sdk/src/Schema/Content/ResourceLink.php new file mode 100644 index 0000000..946c9b8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/ResourceLink.php @@ -0,0 +1,155 @@ +, + * } + * + * @author Alex Rothberg + */ +class ResourceLink extends Content +{ + /** + * @param string $uri the URI of this resource + * @param string $name a short identifier for this resource + * @param ?string $title optional human-readable title for display in UI + * @param ?string $description a description of what this resource represents. This can be used by clients to improve the LLM's understanding of available resources + * @param ?string $mimeType the MIME type of this resource, if known + * @param ?Annotations $annotations optional annotations for the client + * @param ?int $size the size of the raw resource content, in bytes (before base64 encoding or any tokenization), if known + * @param ?Icon[] $icons optional icons representing the resource + * @param ?array $meta optional metadata + */ + public function __construct( + public readonly string $uri, + public readonly string $name, + public readonly ?string $title = null, + public readonly ?string $description = null, + public readonly ?string $mimeType = null, + public readonly ?Annotations $annotations = null, + public readonly ?int $size = null, + public readonly ?array $icons = null, + public readonly ?array $meta = null, + ) { + parent::__construct('resource_link'); + } + + /** + * @param ResourceLinkData $data + */ + public static function fromArray(array $data): self + { + if (($data['type'] ?? null) !== 'resource_link') { + throw new InvalidArgumentException('Invalid type for ResourceLink.'); + } + if (empty($data['uri']) || !\is_string($data['uri'])) { + throw new InvalidArgumentException('Invalid or missing "uri" in ResourceLink data.'); + } + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in ResourceLink data.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in ResourceLink data.'); + } + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in ResourceLink data.'); + } + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" in ResourceLink data.'); + } + if (isset($data['size']) && !\is_int($data['size'])) { + throw new InvalidArgumentException('Invalid "size" in ResourceLink data; expected an integer.'); + } + + return new self( + uri: $data['uri'], + name: $data['name'], + title: isset($data['title']) && \is_string($data['title']) ? $data['title'] : null, + description: $data['description'] ?? null, + mimeType: $data['mimeType'] ?? null, + annotations: Annotations::tryFromArray($data['annotations'] ?? null, 'ResourceLink'), + size: $data['size'] ?? null, + icons: isset($data['icons']) && \is_array($data['icons']) ? Icon::listFromArray($data['icons'], 'ResourceLink') : null, + meta: $data['_meta'] ?? null, + ); + } + + /** + * @return array{ + * type: 'resource_link', + * uri: string, + * name: string, + * title?: string, + * description?: string, + * mimeType?: string, + * annotations?: Annotations, + * size?: int, + * icons?: Icon[], + * _meta?: array, + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'type' => $this->type, + 'uri' => $this->uri, + 'name' => $this->name, + ]; + if (null !== $this->title) { + $data['title'] = $this->title; + } + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->mimeType) { + $data['mimeType'] = $this->mimeType; + } + if (null !== $this->annotations) { + $data['annotations'] = $this->annotations; + } + if (null !== $this->size) { + $data['size'] = $this->size; + } + if (null !== $this->icons) { + $data['icons'] = $this->icons; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/SamplingMessage.php b/admin/vendor/mcp/sdk/src/Schema/Content/SamplingMessage.php new file mode 100644 index 0000000..17343d0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/SamplingMessage.php @@ -0,0 +1,162 @@ +|list>, + * _meta?: array, + * } + * + * @author Kyrian Obikwelu + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +class SamplingMessage extends Content +{ + /** + * @var SamplingContent|list + */ + public readonly TextContent|ImageContent|AudioContent|ToolUseContent|ToolResultContent|array $content; + + /** + * @param SamplingContent|array $content keys are discarded, the property always holds a list + * @param ?array $meta + */ + public function __construct( + public readonly Role $role, + TextContent|ImageContent|AudioContent|ToolUseContent|ToolResultContent|array $content, + public readonly ?array $meta = null, + ) { + if (\is_array($content)) { + if ([] === $content) { + throw new InvalidArgumentException('Sampling message content must not be empty.'); + } + + foreach ($content as $item) { + if (!$item instanceof TextContent && !$item instanceof ImageContent && !$item instanceof AudioContent && !$item instanceof ToolUseContent && !$item instanceof ToolResultContent) { + throw new InvalidArgumentException('Sampling message content contains an unsupported content block.'); + } + } + + // array_filter() and friends preserve keys, and a keyed array serializes + // as a JSON object rather than the array the schema requires. + $content = array_values($content); + } + + $this->content = $content; + + parent::__construct('sampling'); + } + + /** + * @return list + */ + public function getContentBlocks(): array + { + return \is_array($this->content) ? $this->content : [$this->content]; + } + + /** + * @param SamplingMessageData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['role']) || !\is_string($data['role'])) { + throw new InvalidArgumentException('Missing or invalid "role" in SamplingMessage data.'); + } + if (!isset($data['content']) || !\is_array($data['content']) || [] === $data['content']) { + throw new InvalidArgumentException('Missing or invalid "content" in SamplingMessage data.'); + } + + if (null === $role = Role::tryFrom($data['role'])) { + throw new InvalidArgumentException(\sprintf('Invalid "role" value "%s" in SamplingMessage data.', $data['role'])); + } + + $contentData = $data['content']; + $contentType = $contentData['type'] ?? null; + if (null !== $contentType && !\is_string($contentType)) { + throw new InvalidArgumentException('Missing or invalid content "type" for SamplingMessage.'); + } + + $isSingleContent = null !== $contentType; + $contentItems = $isSingleContent ? [$contentData] : $contentData; + $content = []; + + foreach ($contentItems as $item) { + if (!\is_array($item)) { + throw new InvalidArgumentException('Invalid content block in SamplingMessage data.'); + } + $content[] = self::hydrateContent($item); + } + + return new self( + $role, + $isSingleContent ? $content[0] : $content, + isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null, + ); + } + + /** + * @return array{ + * role: string, + * content: SamplingContent|list, + * _meta?: array, + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'role' => $this->role->value, + 'content' => $this->content, + ]; + + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } + + /** + * @param array $contentData + * + * @return SamplingContent + */ + private static function hydrateContent(array $contentData): TextContent|ImageContent|AudioContent|ToolUseContent|ToolResultContent + { + $contentType = $contentData['type'] ?? null; + + return match ($contentType) { + 'text' => TextContent::fromArray($contentData), + 'image' => ImageContent::fromArray($contentData), + 'audio' => AudioContent::fromArray($contentData), + 'tool_use' => ToolUseContent::fromArray($contentData), + 'tool_result' => ToolResultContent::fromArray($contentData), + default => throw new InvalidArgumentException(\sprintf('Invalid content type "%s" for SamplingMessage.', $contentType)), + }; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/TextContent.php b/admin/vendor/mcp/sdk/src/Schema/Content/TextContent.php new file mode 100644 index 0000000..b743af5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/TextContent.php @@ -0,0 +1,96 @@ + + */ +class TextContent extends Content +{ + /** + * Create a new TextContent instance from any value. + * + * @param mixed $text The value to convert to text + * @param ?Annotations $annotations Optional annotations describing the content + */ + public function __construct( + public mixed $text, + public readonly ?Annotations $annotations = null, + ) { + $this->text = (\is_array($text) || \is_object($text)) + ? json_encode($text, \JSON_PRETTY_PRINT) : (string) $text; + + parent::__construct('text'); + } + + /** + * @param TextContentData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['text']) || !\is_string($data['text'])) { + throw new InvalidArgumentException('Missing or invalid "text" in TextContent data.'); + } + + return new self( + $data['text'], + Annotations::tryFromArray($data['annotations'] ?? null, 'TextContent') + ); + } + + /** + * Create a new TextContent with markdown formatted code. + * + * @param string $code The code to format + * @param string $language The language for syntax highlighting + */ + public static function code(string $code, string $language = '', ?Annotations $annotations = null): self + { + return new self("```{$language}\n{$code}\n```", $annotations); + } + + /** + * Convert the content to an array. + * + * @return array{ + * type: 'text', + * text: string, + * annotations?: Annotations, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'type' => 'text', + 'text' => $this->text, + ]; + + if (null !== $this->annotations) { + $result['annotations'] = $this->annotations; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/TextResourceContents.php b/admin/vendor/mcp/sdk/src/Schema/Content/TextResourceContents.php new file mode 100644 index 0000000..9beff81 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/TextResourceContents.php @@ -0,0 +1,77 @@ + + * } + * + * @author Kyrian Obikwelu + */ +class TextResourceContents extends ResourceContents +{ + /** + * @param string $uri the URI of the resource or sub-resource + * @param string|null $mimeType the MIME type of the resource or sub-resource + * @param string $text The text of the item. This must only be set if the item can actually be represented as text (not binary data). + * @param ?array $meta Optional metadata + */ + public function __construct( + string $uri, + ?string $mimeType, + public readonly string $text, + ?array $meta = null, + ) { + parent::__construct($uri, $mimeType, $meta); + } + + /** + * @param TextResourceContentsData $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['uri']) || !\is_string($data['uri'])) { + throw new InvalidArgumentException('Missing or invalid "uri" for TextResourceContents.'); + } + if (!isset($data['text']) || !\is_string($data['text'])) { + throw new InvalidArgumentException('Missing or invalid "text" for TextResourceContents.'); + } + + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" for TextResourceContents.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" for TextResourceContents.'); + } + + return new self($data['uri'], $data['mimeType'] ?? null, $data['text'], $data['_meta'] ?? null); + } + + /** + * @return TextResourceContentsData + */ + public function jsonSerialize(): array + { + return [ + 'text' => $this->text, + ...parent::jsonSerialize(), + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/ToolResultContent.php b/admin/vendor/mcp/sdk/src/Schema/Content/ToolResultContent.php new file mode 100644 index 0000000..7f2b11c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/ToolResultContent.php @@ -0,0 +1,121 @@ + + */ + public readonly array $content; + + /** + * @param array $content keys are discarded, the property always holds a list + * @param ?array $structuredContent + * @param ?array $meta + */ + public function __construct( + public readonly string $toolUseId, + array $content, + public readonly ?array $structuredContent = null, + public readonly bool $isError = false, + public readonly ?array $meta = null, + ) { + foreach ($content as $item) { + if (!$item instanceof TextContent && !$item instanceof ImageContent && !$item instanceof AudioContent && !$item instanceof ResourceLink && !$item instanceof EmbeddedResource) { + throw new InvalidArgumentException('Tool result content must contain standard content blocks.'); + } + } + + // array_filter() and friends preserve keys, and a keyed array serializes + // as a JSON object rather than the array the schema requires. + $this->content = array_values($content); + + parent::__construct('tool_result'); + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['toolUseId']) || !\is_string($data['toolUseId'])) { + throw new InvalidArgumentException('Missing or invalid "toolUseId" in ToolResultContent data.'); + } + if (!isset($data['content']) || !\is_array($data['content'])) { + throw new InvalidArgumentException('Missing or invalid "content" in ToolResultContent data.'); + } + + $content = []; + foreach ($data['content'] as $item) { + if (!\is_array($item)) { + throw new InvalidArgumentException('Invalid content block in ToolResultContent data.'); + } + + $content[] = match ($item['type'] ?? null) { + 'text' => TextContent::fromArray($item), + 'image' => ImageContent::fromArray($item), + 'audio' => AudioContent::fromArray($item), + 'resource_link' => ResourceLink::fromArray($item), + 'resource' => EmbeddedResource::fromArray($item), + default => throw new InvalidArgumentException(\sprintf('Unsupported tool result content type "%s".', $item['type'] ?? null)), + }; + } + + return new self( + $data['toolUseId'], + $content, + isset($data['structuredContent']) && \is_array($data['structuredContent']) ? $data['structuredContent'] : null, + isset($data['isError']) && true === $data['isError'], + isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'type' => $this->type, + 'toolUseId' => $this->toolUseId, + 'content' => $this->content, + ]; + + // Optional in the schema with a default of false, so only sent when it is true. + if ($this->isError) { + $data['isError'] = true; + } + + if (null !== $this->structuredContent) { + $data['structuredContent'] = $this->structuredContent; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Content/ToolUseContent.php b/admin/vendor/mcp/sdk/src/Schema/Content/ToolUseContent.php new file mode 100644 index 0000000..8952e8c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Content/ToolUseContent.php @@ -0,0 +1,85 @@ + $input + * @param ?array $meta + */ + public function __construct( + public readonly string $id, + public readonly string $name, + public readonly array $input, + public readonly ?array $meta = null, + ) { + if ([] !== $input && array_is_list($input)) { + throw new InvalidArgumentException('ToolUseContent "input" must be a map of argument names, not a list.'); + } + + parent::__construct('tool_use'); + } + + /** + * @param array{id?: mixed, name?: mixed, input?: mixed, _meta?: mixed} $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['id']) || !\is_string($data['id'])) { + throw new InvalidArgumentException('Missing or invalid "id" in ToolUseContent data.'); + } + if (!isset($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Missing or invalid "name" in ToolUseContent data.'); + } + if (!isset($data['input']) || !\is_array($data['input'])) { + throw new InvalidArgumentException('Missing or invalid "input" in ToolUseContent data.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in ToolUseContent data.'); + } + + return new self( + $data['id'], + $data['name'], + $data['input'], + $data['_meta'] ?? null, + ); + } + + /** + * @return array{type: 'tool_use', id: string, name: string, input: array|\stdClass, _meta?: array} + */ + public function jsonSerialize(): array + { + $data = [ + 'type' => $this->type, + 'id' => $this->id, + 'name' => $this->name, + 'input' => $this->input ?: new \stdClass(), + ]; + + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/AbstractSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/AbstractSchemaDefinition.php new file mode 100644 index 0000000..a05135f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/AbstractSchemaDefinition.php @@ -0,0 +1,67 @@ + + */ +abstract class AbstractSchemaDefinition implements \JsonSerializable +{ + public function __construct( + public readonly ?string $title = null, + public readonly ?string $description = null, + ) { + } + + /** + * Reject a title that is present but not a string. + * + * @param array $data + * + * @throws InvalidArgumentException + */ + protected static function validateTitle(array $data, string $schemaType): void + { + if (\array_key_exists('title', $data) && null !== $data['title'] && !\is_string($data['title'])) { + throw new InvalidArgumentException(\sprintf('Invalid "title" for %s schema definition.', $schemaType)); + } + } + + /** + * Build the base JSON structure with type, optional title and description. + * + * @return array + */ + protected function buildBaseJson(string $type): array + { + $data = ['type' => $type]; + + if (null !== $this->title) { + $data['title'] = $this->title; + } + + if (null !== $this->description) { + $data['description'] = $this->description; + } + + return $data; + } + + /** + * @return array + */ + abstract public function jsonSerialize(): array; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/BooleanSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/BooleanSchemaDefinition.php new file mode 100644 index 0000000..3976685 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/BooleanSchemaDefinition.php @@ -0,0 +1,70 @@ + + */ +final class BooleanSchemaDefinition extends AbstractSchemaDefinition +{ + /** + * @param ?string $title Optional human-readable title for the field + * @param string|null $description Optional description/help text + * @param bool|null $default Optional default value + */ + public function __construct( + ?string $title = null, + ?string $description = null, + public readonly ?bool $default = null, + ) { + parent::__construct($title, $description); + } + + /** + * @param array{ + * title?: string, + * description?: string, + * default?: bool, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'boolean'); + + return new self( + title: $data['title'] ?? null, + description: $data['description'] ?? null, + default: isset($data['default']) ? (bool) $data['default'] : null, + ); + } + + /** + * @return array{ + * type: string, + * title?: string, + * description?: string, + * default?: bool, + * } + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson('boolean'); + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/ElicitationSchema.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/ElicitationSchema.php new file mode 100644 index 0000000..36d130a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/ElicitationSchema.php @@ -0,0 +1,160 @@ + + */ +final class ElicitationSchema implements \JsonSerializable +{ + /** + * @param array $properties Property definitions keyed by name + * @param string[] $required Array of required property names + */ + public function __construct( + public readonly array $properties, + public readonly array $required = [], + ) { + if ([] === $properties) { + throw new InvalidArgumentException('properties array must not be empty.'); + } + + foreach ($required as $name) { + if (!\is_string($name)) { + throw new InvalidArgumentException('Each entry in "required" must be a string.'); + } + if (!\array_key_exists($name, $properties)) { + throw new InvalidArgumentException(\sprintf('Required property "%s" is not defined in properties.', $name)); + } + } + } + + /** + * Create an ElicitationSchema from array data. + * + * @param array{ + * type?: string, + * properties: array, + * required?: string[], + * } $data + */ + public static function fromArray(array $data): self + { + if (isset($data['type']) && 'object' !== $data['type']) { + throw new InvalidArgumentException('ElicitationSchema type must be "object".'); + } + + if (!isset($data['properties']) || !\is_array($data['properties'])) { + throw new InvalidArgumentException('Missing or invalid "properties" for elicitation schema.'); + } + + $properties = []; + foreach ($data['properties'] as $name => $propertyData) { + if (!\is_array($propertyData)) { + throw new InvalidArgumentException(\sprintf('Property "%s" must be an array.', $name)); + } + $properties[$name] = self::createSchemaDefinition($propertyData); + } + + if (isset($data['required']) && !\is_array($data['required'])) { + throw new InvalidArgumentException('Invalid "required" for elicitation schema; expected an array.'); + } + + return new self( + properties: $properties, + required: $data['required'] ?? [], + ); + } + + /** + * Create a schema definition from array data. + * + * @param array $data + */ + private static function createSchemaDefinition(array $data): AbstractSchemaDefinition + { + if (!isset($data['type']) || !\is_string($data['type'])) { + throw new InvalidArgumentException('Missing or invalid "type" for schema definition.'); + } + + return match ($data['type']) { + 'string' => self::resolveStringType($data), + 'integer', 'number' => NumberSchemaDefinition::fromArray($data), + 'boolean' => BooleanSchemaDefinition::fromArray($data), + 'array' => self::resolveArrayType($data), + default => throw new InvalidArgumentException(\sprintf('Unsupported type "%s". Supported types are: string, integer, number, boolean, array.', $data['type'])), + }; + } + + /** + * @param array $data + */ + private static function resolveStringType(array $data): AbstractSchemaDefinition + { + if (isset($data['oneOf'])) { + return TitledEnumSchemaDefinition::fromArray($data); + } + + if (isset($data['enum'])) { + return EnumSchemaDefinition::fromArray($data); + } + + return StringSchemaDefinition::fromArray($data); + } + + /** + * @param array $data + */ + private static function resolveArrayType(array $data): AbstractSchemaDefinition + { + if (isset($data['items']['anyOf'])) { + return TitledMultiSelectEnumSchemaDefinition::fromArray($data); + } + + if (isset($data['items']['enum'])) { + return MultiSelectEnumSchemaDefinition::fromArray($data); + } + + throw new InvalidArgumentException('Array type must have "items" with either "enum" or "anyOf".'); + } + + /** + * @return array{ + * type: string, + * properties: array, + * required?: string[], + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'type' => 'object', + 'properties' => [], + ]; + + foreach ($this->properties as $name => $property) { + $data['properties'][$name] = $property->jsonSerialize(); + } + + if ([] !== $this->required) { + $data['required'] = $this->required; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/EnumSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/EnumSchemaDefinition.php new file mode 100644 index 0000000..1eaef8b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/EnumSchemaDefinition.php @@ -0,0 +1,120 @@ + + */ +final class EnumSchemaDefinition extends AbstractSchemaDefinition +{ + /** + * @param ?string $title Optional human-readable title for the field + * @param string[] $enum Array of allowed string values + * @param string|null $description Optional description/help text + * @param string|null $default Optional default value (must be in enum) + * @param string[]|null $enumNames Optional human-readable labels for each enum value + */ + public function __construct( + ?string $title, + public readonly array $enum, + ?string $description = null, + public readonly ?string $default = null, + public readonly ?array $enumNames = null, + ) { + parent::__construct($title, $description); + + if ([] === $enum) { + throw new InvalidArgumentException('enum array must not be empty.'); + } + + foreach ($enum as $value) { + if (!\is_string($value)) { + throw new InvalidArgumentException('All enum values must be strings.'); + } + } + + if (null !== $enumNames && \count($enumNames) !== \count($enum)) { + throw new InvalidArgumentException('enumNames length must match enum length.'); + } + + if (null !== $default && !\in_array($default, $enum, true)) { + throw new InvalidArgumentException(\sprintf('Default value "%s" is not in the enum array.', $default)); + } + } + + /** + * @param array{ + * title?: string, + * enum: string[], + * description?: string, + * default?: string, + * enumNames?: string[], + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'enum'); + + if (!isset($data['enum']) || !\is_array($data['enum'])) { + throw new InvalidArgumentException('Missing or invalid "enum" for enum schema definition.'); + } + + return new self( + title: $data['title'] ?? null, + enum: $data['enum'], + description: $data['description'] ?? null, + default: $data['default'] ?? null, + enumNames: $data['enumNames'] ?? null, + ); + } + + /** + * @return array{ + * type: string, + * title?: string, + * enum: string[], + * description?: string, + * default?: string, + * enumNames?: string[], + * } + */ + public function jsonSerialize(): array + { + $data = ['type' => 'string']; + + if (null !== $this->title) { + $data['title'] = $this->title; + } + + $data['enum'] = $this->enum; + + if (null !== $this->description) { + $data['description'] = $this->description; + } + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + if (null !== $this->enumNames) { + $data['enumNames'] = $this->enumNames; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/MultiSelectEnumSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/MultiSelectEnumSchemaDefinition.php new file mode 100644 index 0000000..ce2ab08 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/MultiSelectEnumSchemaDefinition.php @@ -0,0 +1,127 @@ + $maxItems) { + throw new InvalidArgumentException('minItems cannot be greater than maxItems.'); + } + + if (null !== $default) { + foreach ($default as $value) { + if (!\in_array($value, $enum, true)) { + throw new InvalidArgumentException(\sprintf('Default value "%s" is not in the enum array.', $value)); + } + } + } + } + + /** + * @param array{ + * title?: string, + * items: array{type: string, enum: string[]}, + * description?: string, + * default?: string[], + * minItems?: int, + * maxItems?: int, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'multi-select enum'); + + if (!isset($data['items']['enum']) || !\is_array($data['items']['enum'])) { + throw new InvalidArgumentException('Missing or invalid "items.enum" for multi-select enum schema definition.'); + } + + return new self( + title: $data['title'] ?? null, + enum: $data['items']['enum'], + description: $data['description'] ?? null, + default: $data['default'] ?? null, + minItems: isset($data['minItems']) ? (int) $data['minItems'] : null, + maxItems: isset($data['maxItems']) ? (int) $data['maxItems'] : null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson('array'); + $data['items'] = [ + 'type' => 'string', + 'enum' => $this->enum, + ]; + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + if (null !== $this->minItems) { + $data['minItems'] = $this->minItems; + } + + if (null !== $this->maxItems) { + $data['maxItems'] = $this->maxItems; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/NumberSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/NumberSchemaDefinition.php new file mode 100644 index 0000000..d2bf540 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/NumberSchemaDefinition.php @@ -0,0 +1,115 @@ + + */ +final class NumberSchemaDefinition extends AbstractSchemaDefinition +{ + /** + * @param ?string $title Optional human-readable title for the field + * @param bool $integerOnly Whether to restrict to integer values only + * @param string|null $description Optional description/help text + * @param int|float|null $default Optional default value + * @param int|float|null $minimum Optional minimum value (inclusive) + * @param int|float|null $maximum Optional maximum value (inclusive) + */ + public function __construct( + ?string $title = null, + public readonly bool $integerOnly = false, + ?string $description = null, + public readonly int|float|null $default = null, + public readonly int|float|null $minimum = null, + public readonly int|float|null $maximum = null, + ) { + parent::__construct($title, $description); + + if (null !== $minimum && null !== $maximum && $minimum > $maximum) { + throw new InvalidArgumentException('minimum cannot be greater than maximum.'); + } + + if (null !== $default && null !== $minimum && $default < $minimum) { + throw new InvalidArgumentException('default value cannot be less than minimum.'); + } + + if (null !== $default && null !== $maximum && $default > $maximum) { + throw new InvalidArgumentException('default value cannot be greater than maximum.'); + } + + if ($integerOnly && null !== $default && $default !== (int) $default) { + throw new InvalidArgumentException('default value must be an integer when integerOnly is true.'); + } + } + + /** + * @param array{ + * type: string, + * title?: string, + * description?: string, + * default?: int|float, + * minimum?: int|float, + * maximum?: int|float, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'number'); + + $type = $data['type'] ?? 'number'; + $integerOnly = 'integer' === $type; + + return new self( + title: $data['title'] ?? null, + integerOnly: $integerOnly, + description: $data['description'] ?? null, + default: $data['default'] ?? null, + minimum: $data['minimum'] ?? null, + maximum: $data['maximum'] ?? null, + ); + } + + /** + * @return array{ + * type: string, + * title?: string, + * description?: string, + * default?: int|float, + * minimum?: int|float, + * maximum?: int|float, + * } + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson($this->integerOnly ? 'integer' : 'number'); + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + if (null !== $this->minimum) { + $data['minimum'] = $this->minimum; + } + + if (null !== $this->maximum) { + $data['maximum'] = $this->maximum; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/StringSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/StringSchemaDefinition.php new file mode 100644 index 0000000..36c5e44 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/StringSchemaDefinition.php @@ -0,0 +1,119 @@ + + */ +final class StringSchemaDefinition extends AbstractSchemaDefinition +{ + private const VALID_FORMATS = ['date', 'date-time', 'email', 'uri']; + + /** + * @param ?string $title Optional human-readable title for the field + * @param string|null $description Optional description/help text + * @param string|null $default Optional default value + * @param string|null $format Optional format constraint (date, date-time, email, uri) + * @param int|null $minLength Optional minimum string length + * @param int|null $maxLength Optional maximum string length + */ + public function __construct( + ?string $title = null, + ?string $description = null, + public readonly ?string $default = null, + public readonly ?string $format = null, + public readonly ?int $minLength = null, + public readonly ?int $maxLength = null, + ) { + parent::__construct($title, $description); + + if (null !== $format && !\in_array($format, self::VALID_FORMATS, true)) { + throw new InvalidArgumentException(\sprintf('Invalid format "%s". Valid formats are: %s.', $format, implode(', ', self::VALID_FORMATS))); + } + + if (null !== $minLength && $minLength < 0) { + throw new InvalidArgumentException('minLength must be non-negative.'); + } + + if (null !== $maxLength && $maxLength < 0) { + throw new InvalidArgumentException('maxLength must be non-negative.'); + } + + if (null !== $minLength && null !== $maxLength && $minLength > $maxLength) { + throw new InvalidArgumentException('minLength cannot be greater than maxLength.'); + } + } + + /** + * @param array{ + * title?: string, + * description?: string, + * default?: string, + * format?: string, + * minLength?: int, + * maxLength?: int, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'string'); + + return new self( + title: $data['title'] ?? null, + description: $data['description'] ?? null, + default: $data['default'] ?? null, + format: $data['format'] ?? null, + minLength: isset($data['minLength']) ? (int) $data['minLength'] : null, + maxLength: isset($data['maxLength']) ? (int) $data['maxLength'] : null, + ); + } + + /** + * @return array{ + * type: string, + * title?: string, + * description?: string, + * default?: string, + * format?: string, + * minLength?: int, + * maxLength?: int, + * } + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson('string'); + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + if (null !== $this->format) { + $data['format'] = $this->format; + } + + if (null !== $this->minLength) { + $data['minLength'] = $this->minLength; + } + + if (null !== $this->maxLength) { + $data['maxLength'] = $this->maxLength; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledEnumSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledEnumSchemaDefinition.php new file mode 100644 index 0000000..f6c47d4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledEnumSchemaDefinition.php @@ -0,0 +1,98 @@ + $oneOf Array of const/title pairs + * @param string|null $description Optional description/help text + * @param string|null $default Optional default value (must match a const) + */ + public function __construct( + ?string $title, + public readonly array $oneOf, + ?string $description = null, + public readonly ?string $default = null, + ) { + parent::__construct($title, $description); + + if ([] === $oneOf) { + throw new InvalidArgumentException('oneOf array must not be empty.'); + } + + $consts = []; + foreach ($oneOf as $item) { + if (!isset($item['const']) || !\is_string($item['const'])) { + throw new InvalidArgumentException('Each oneOf item must have a string "const" property.'); + } + if (!isset($item['title']) || !\is_string($item['title'])) { + throw new InvalidArgumentException('Each oneOf item must have a string "title" property.'); + } + $consts[] = $item['const']; + } + + if (null !== $default && !\in_array($default, $consts, true)) { + throw new InvalidArgumentException(\sprintf('Default value "%s" is not in the oneOf const values.', $default)); + } + } + + /** + * @param array{ + * title?: string, + * oneOf: list, + * description?: string, + * default?: string, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'titled enum'); + + if (!isset($data['oneOf']) || !\is_array($data['oneOf'])) { + throw new InvalidArgumentException('Missing or invalid "oneOf" for titled enum schema definition.'); + } + + return new self( + title: $data['title'] ?? null, + oneOf: $data['oneOf'], + description: $data['description'] ?? null, + default: $data['default'] ?? null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson('string'); + $data['oneOf'] = $this->oneOf; + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledMultiSelectEnumSchemaDefinition.php b/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledMultiSelectEnumSchemaDefinition.php new file mode 100644 index 0000000..ee49a05 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Elicitation/TitledMultiSelectEnumSchemaDefinition.php @@ -0,0 +1,131 @@ + $anyOf Array of const/title pairs + * @param string|null $description Optional description/help text + * @param string[]|null $default Optional default selected values (must be subset of anyOf consts) + * @param int|null $minItems Optional minimum number of selections + * @param int|null $maxItems Optional maximum number of selections + */ + public function __construct( + ?string $title, + public readonly array $anyOf, + ?string $description = null, + public readonly ?array $default = null, + public readonly ?int $minItems = null, + public readonly ?int $maxItems = null, + ) { + parent::__construct($title, $description); + + if ([] === $anyOf) { + throw new InvalidArgumentException('anyOf array must not be empty.'); + } + + $consts = []; + foreach ($anyOf as $item) { + if (!isset($item['const']) || !\is_string($item['const'])) { + throw new InvalidArgumentException('Each anyOf item must have a string "const" property.'); + } + if (!isset($item['title']) || !\is_string($item['title'])) { + throw new InvalidArgumentException('Each anyOf item must have a string "title" property.'); + } + $consts[] = $item['const']; + } + + if (null !== $minItems && $minItems < 0) { + throw new InvalidArgumentException('minItems must be non-negative.'); + } + + if (null !== $maxItems && $maxItems < 0) { + throw new InvalidArgumentException('maxItems must be non-negative.'); + } + + if (null !== $minItems && null !== $maxItems && $minItems > $maxItems) { + throw new InvalidArgumentException('minItems cannot be greater than maxItems.'); + } + + if (null !== $default) { + foreach ($default as $value) { + if (!\in_array($value, $consts, true)) { + throw new InvalidArgumentException(\sprintf('Default value "%s" is not in the anyOf const values.', $value)); + } + } + } + } + + /** + * @param array{ + * title?: string, + * items: array{anyOf: list}, + * description?: string, + * default?: string[], + * minItems?: int, + * maxItems?: int, + * } $data + */ + public static function fromArray(array $data): self + { + self::validateTitle($data, 'titled multi-select enum'); + + if (!isset($data['items']['anyOf']) || !\is_array($data['items']['anyOf'])) { + throw new InvalidArgumentException('Missing or invalid "items.anyOf" for titled multi-select enum schema definition.'); + } + + return new self( + title: $data['title'] ?? null, + anyOf: $data['items']['anyOf'], + description: $data['description'] ?? null, + default: $data['default'] ?? null, + minItems: isset($data['minItems']) ? (int) $data['minItems'] : null, + maxItems: isset($data['maxItems']) ? (int) $data['maxItems'] : null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = $this->buildBaseJson('array'); + $data['items'] = [ + 'anyOf' => $this->anyOf, + ]; + + if (null !== $this->default) { + $data['default'] = $this->default; + } + + if (null !== $this->minItems) { + $data['minItems'] = $this->minItems; + } + + if (null !== $this->maxItems) { + $data['maxItems'] = $this->maxItems; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/CacheScope.php b/admin/vendor/mcp/sdk/src/Schema/Enum/CacheScope.php new file mode 100644 index 0000000..2c71cf8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/CacheScope.php @@ -0,0 +1,30 @@ + + */ +enum CacheScope: string +{ + /** Contains no caller-specific data; any cache may share it. */ + case Public = 'public'; + + /** Reusable only within the same authorization context. */ + case Private = 'private'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitAction.php b/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitAction.php new file mode 100644 index 0000000..373e618 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitAction.php @@ -0,0 +1,24 @@ + + */ +enum ElicitAction: string +{ + case Accept = 'accept'; + case Decline = 'decline'; + case Cancel = 'cancel'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitationMode.php b/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitationMode.php new file mode 100644 index 0000000..adb0493 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/ElicitationMode.php @@ -0,0 +1,31 @@ + + */ +enum ElicitationMode: string +{ + /** + * Present a form built from the requested schema, and return the filled values. + */ + case Form = 'form'; + + /** + * Send the user to a URL to complete the interaction out of band. The result + * carries no content — only whether the user accepted, declined, or cancelled. + */ + case Url = 'url'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/IconTheme.php b/admin/vendor/mcp/sdk/src/Schema/Enum/IconTheme.php new file mode 100644 index 0000000..abfac9e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/IconTheme.php @@ -0,0 +1,25 @@ + + */ +enum IconTheme: string +{ + case Light = 'light'; + case Dark = 'dark'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/LoggingLevel.php b/admin/vendor/mcp/sdk/src/Schema/Enum/LoggingLevel.php new file mode 100644 index 0000000..d6031f9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/LoggingLevel.php @@ -0,0 +1,62 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +enum LoggingLevel: string +{ + case Debug = 'debug'; + case Info = 'info'; + case Notice = 'notice'; + case Warning = 'warning'; + case Error = 'error'; + case Critical = 'critical'; + case Alert = 'alert'; + case Emergency = 'emergency'; + + /** + * RFC 5424 ordering, inverted so a larger number is more severe — which is + * the direction a minimum-level comparison reads in. + */ + public function severity(): int + { + return match ($this) { + self::Debug => 0, + self::Info => 1, + self::Notice => 2, + self::Warning => 3, + self::Error => 4, + self::Critical => 5, + self::Alert => 6, + self::Emergency => 7, + }; + } + + /** + * Whether a message at this level should be emitted when $minimum was + * requested. + */ + public function isAtLeast(self $minimum): bool + { + return $this->severity() >= $minimum->severity(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/ProtocolVersion.php b/admin/vendor/mcp/sdk/src/Schema/Enum/ProtocolVersion.php new file mode 100644 index 0000000..03ebddf --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/ProtocolVersion.php @@ -0,0 +1,154 @@ + + */ +enum ProtocolVersion: string +{ + // Declaration order is also the era boundary: everything declared from + // FIRST_MODERN_VERSION onwards is modern. A new handshake-era revision has + // to be inserted above that case — appending it here would silently drop it + // out of the handshake negotiation. + case V2024_11_05 = '2024-11-05'; + case V2025_03_26 = '2025-03-26'; + case V2025_06_18 = '2025-06-18'; + case V2025_11_25 = '2025-11-25'; + case V2026_07_28 = '2026-07-28'; + + /** + * First revision of the modern era, in which the `initialize` handshake was + * replaced by per-request metadata. + */ + public const FIRST_MODERN_VERSION = self::V2026_07_28; + + /** + * Version a server assumes when a client omits the `MCP-Protocol-Version` + * header on the Streamable HTTP transport. + * + * This is the revision that introduced both Streamable HTTP and the header + * itself, so a request without the header cannot be newer than this. + */ + public const DEFAULT_HEADER_VERSION = self::V2025_03_26; + + /** + * Newest revision reachable through the `initialize` handshake. + * + * This is what a server counter-offers when it cannot honour the version a + * client asked for, and what a handshake-era client offers by default. + */ + public static function latestHandshake(): self + { + $versions = self::handshakeVersions(); + + return $versions[\count($versions) - 1]; + } + + /** + * Revisions reachable through the `initialize` handshake, oldest to newest. + * + * @return non-empty-list + */ + public static function handshakeVersions(): array + { + return array_values(array_filter(self::cases(), static fn (self $v): bool => !$v->isModern())); + } + + /** + * Revisions using the per-request metadata envelope, oldest to newest. + * + * @return non-empty-list + */ + public static function modernVersions(): array + { + return array_values(array_filter(self::cases(), static fn (self $v): bool => $v->isModern())); + } + + /** + * Whether this revision belongs to the modern, per-request-metadata era. + */ + public function isModern(): bool + { + return $this->isAtLeast(self::FIRST_MODERN_VERSION); + } + + /** + * Whether this revision restricts `structuredContent` to a JSON object. + * + * SEP-2106, part of {@see self::V2026_07_28}, widened `outputSchema` to any + * JSON Schema 2020-12 and `structuredContent` to any JSON value conforming to + * it. Up to `2025-11-25` both are restricted to an object. + * + * @see https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content + */ + public function requiresObjectStructuredContent(): bool + { + return !$this->isAtLeast(self::V2026_07_28); + } + + /** + * Whether this revision answers a missing resource with `-32602`. + * + * SEP-2164, part of {@see self::V2026_07_28}, retired the bespoke `-32002` + * in favour of the JSON-RPC code that already meant this, and reserved + * `-32002` so it is never reused. Earlier revisions still expect it, and + * clients are told to keep accepting it from them. + * + * @see https://modelcontextprotocol.io/specification/2026-07-28/basic/index#error-codes + */ + public function usesInvalidParamsForResourceNotFound(): bool + { + return $this->isAtLeast(self::V2026_07_28); + } + + /** + * Whether this revision is at least as new as $minimum. + */ + public function isAtLeast(self $minimum): bool + { + return $this->position() >= $minimum->position(); + } + + /** + * Index of this revision in the chronological declaration order. + */ + private function position(): int + { + foreach (self::cases() as $index => $case) { + if ($case === $this) { + return $index; + } + } + + throw new LogicException(\sprintf('Protocol version "%s" is not a declared case.', $this->value)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/ResultType.php b/admin/vendor/mcp/sdk/src/Schema/Enum/ResultType.php new file mode 100644 index 0000000..8319bc4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/ResultType.php @@ -0,0 +1,30 @@ + + */ +enum ResultType: string +{ + /** The request finished; the result holds the final content. */ + case Complete = 'complete'; + + /** The request needs more input before it can finish (MRTR). */ + case InputRequired = 'input_required'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/Role.php b/admin/vendor/mcp/sdk/src/Schema/Enum/Role.php new file mode 100644 index 0000000..0b1d97a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/Role.php @@ -0,0 +1,23 @@ + + */ +enum Role: string +{ + case User = 'user'; + case Assistant = 'assistant'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Enum/SamplingContext.php b/admin/vendor/mcp/sdk/src/Schema/Enum/SamplingContext.php new file mode 100644 index 0000000..8ffe6ec --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Enum/SamplingContext.php @@ -0,0 +1,28 @@ + + */ +abstract class AbstractExtension implements ExtensionInterface +{ + public function getMessages(): array + { + return []; + } + + public function getRequestHandlers(): iterable + { + return []; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/McpApps.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/McpApps.php new file mode 100644 index 0000000..dcf3c9e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/McpApps.php @@ -0,0 +1,60 @@ + + */ +final class McpApps extends AbstractExtension +{ + public const EXTENSION_ID = 'io.modelcontextprotocol/ui'; + public const MIME_TYPE = 'text/html;profile=mcp-app'; + public const URI_SCHEME = 'ui'; + + public function getId(): ExtensionIdentifier + { + return new ExtensionIdentifier(self::EXTENSION_ID); + } + + /** + * @return array{mimeTypes: string[]} + */ + public function getCapabilities(): array + { + return ['mimeTypes' => [self::MIME_TYPE]]; + } + + /** + * The marker value for the `_meta.ui` field on a UI resource *descriptor* + * (its `resources/list` entry), flagging the resource as an MCP App. + * + * The structured CSP/permissions metadata instead belongs on the resource + * *content* (the `resources/read` payload) via {@see UiResourceContentMeta}. + */ + public static function resourceMarker(): \stdClass + { + return new \stdClass(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/ToolVisibility.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/ToolVisibility.php new file mode 100644 index 0000000..bdb12ef --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/ToolVisibility.php @@ -0,0 +1,26 @@ + + */ +enum ToolVisibility: string +{ + /** Visible to and callable by the LLM agent. */ + case Model = 'model'; + + /** Callable by the MCP App (HTML view) only, hidden from the model's tools/list. */ + case App = 'app'; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceContentMeta.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceContentMeta.php new file mode 100644 index 0000000..666a7e9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceContentMeta.php @@ -0,0 +1,96 @@ + + */ +final class UiResourceContentMeta implements \JsonSerializable +{ + public function __construct( + public readonly ?UiResourceCsp $csp = null, + public readonly ?UiResourcePermissions $permissions = null, + public readonly ?string $domain = null, + public readonly ?bool $prefersBorder = null, + ) { + } + + /** + * @param UiResourceContentMetaData $data + */ + public static function fromArray(array $data): self + { + if (isset($data['csp']) && !\is_array($data['csp'])) { + throw new InvalidArgumentException('Invalid "csp" in UiResourceContentMeta data; expected an array.'); + } + if (isset($data['permissions']) && !\is_array($data['permissions'])) { + throw new InvalidArgumentException('Invalid "permissions" in UiResourceContentMeta data; expected an array.'); + } + if (isset($data['domain']) && !\is_string($data['domain'])) { + throw new InvalidArgumentException('Invalid "domain" in UiResourceContentMeta data.'); + } + if (isset($data['prefersBorder']) && !\is_bool($data['prefersBorder'])) { + throw new InvalidArgumentException('Invalid "prefersBorder" in UiResourceContentMeta data.'); + } + + return new self( + csp: isset($data['csp']) ? UiResourceCsp::fromArray($data['csp']) : null, + permissions: isset($data['permissions']) ? UiResourcePermissions::fromArray($data['permissions']) : null, + domain: $data['domain'] ?? null, + prefersBorder: $data['prefersBorder'] ?? null, + ); + } + + /** + * @return array{ + * csp?: UiResourceCsp, + * permissions?: UiResourcePermissions, + * domain?: string, + * prefersBorder?: bool + * } + */ + public function jsonSerialize(): array + { + $data = []; + + if (null !== $this->csp) { + $data['csp'] = $this->csp; + } + if (null !== $this->permissions) { + $data['permissions'] = $this->permissions; + } + if (null !== $this->domain) { + $data['domain'] = $this->domain; + } + if (null !== $this->prefersBorder) { + $data['prefersBorder'] = $this->prefersBorder; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceCsp.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceCsp.php new file mode 100644 index 0000000..e3550a6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourceCsp.php @@ -0,0 +1,90 @@ + + */ +final class UiResourceCsp implements \JsonSerializable +{ + /** + * @param ?string[] $connectDomains domains allowed for network requests (fetch, XHR, WebSocket) + * @param ?string[] $resourceDomains domains allowed for static resources (images, scripts, styles) + * @param ?string[] $frameDomains domains allowed for nested iframes + * @param ?string[] $baseUriDomains domains allowed for base URI origins + */ + public function __construct( + public readonly ?array $connectDomains = null, + public readonly ?array $resourceDomains = null, + public readonly ?array $frameDomains = null, + public readonly ?array $baseUriDomains = null, + ) { + } + + /** + * @param UiResourceCspData $data + */ + public static function fromArray(array $data): self + { + foreach (['connectDomains', 'resourceDomains', 'frameDomains', 'baseUriDomains'] as $key) { + if (isset($data[$key]) && !\is_array($data[$key])) { + throw new InvalidArgumentException(\sprintf('Invalid "%s" in UiResourceCsp data; expected an array.', $key)); + } + } + + return new self( + connectDomains: $data['connectDomains'] ?? null, + resourceDomains: $data['resourceDomains'] ?? null, + frameDomains: $data['frameDomains'] ?? null, + baseUriDomains: $data['baseUriDomains'] ?? null, + ); + } + + /** + * @return UiResourceCspData + */ + public function jsonSerialize(): array + { + $data = []; + + // The MCP Apps spec (2026-01-26) defines "empty or omitted" identically + // for every CSP allow-list, so empty arrays are dropped, not emitted as `[]`. + if ($this->connectDomains) { + $data['connectDomains'] = $this->connectDomains; + } + if ($this->resourceDomains) { + $data['resourceDomains'] = $this->resourceDomains; + } + if ($this->frameDomains) { + $data['frameDomains'] = $this->frameDomains; + } + if ($this->baseUriDomains) { + $data['baseUriDomains'] = $this->baseUriDomains; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourcePermissions.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourcePermissions.php new file mode 100644 index 0000000..bed14ba --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiResourcePermissions.php @@ -0,0 +1,76 @@ +, + * microphone?: \stdClass|array, + * geolocation?: \stdClass|array, + * clipboardWrite?: \stdClass|array + * } + * + * @author Christopher Hertel + */ +final class UiResourcePermissions implements \JsonSerializable +{ + public function __construct( + public readonly bool $camera = false, + public readonly bool $microphone = false, + public readonly bool $geolocation = false, + public readonly bool $clipboardWrite = false, + ) { + } + + /** + * @param UiResourcePermissionsData $data + */ + public static function fromArray(array $data): self + { + // A permission is requested when its key is present with the spec's `{}` + // marker; isset() accepts that (array/object forms) and rejects a stray null. + return new self( + camera: isset($data['camera']), + microphone: isset($data['microphone']), + geolocation: isset($data['geolocation']), + clipboardWrite: isset($data['clipboardWrite']), + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = []; + + if ($this->camera) { + $data['camera'] = new \stdClass(); + } + if ($this->microphone) { + $data['microphone'] = new \stdClass(); + } + if ($this->geolocation) { + $data['geolocation'] = new \stdClass(); + } + if ($this->clipboardWrite) { + $data['clipboardWrite'] = new \stdClass(); + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiToolMeta.php b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiToolMeta.php new file mode 100644 index 0000000..235df2a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/Apps/UiToolMeta.php @@ -0,0 +1,82 @@ + + */ +final class UiToolMeta implements \JsonSerializable +{ + /** + * @param ?string $resourceUri the ui:// URI of the linked UI resource + * @param ?list $visibility who can see/call this tool; when omitted the host + * defaults to both {@see ToolVisibility::Model} and {@see ToolVisibility::App} + */ + public function __construct( + public readonly ?string $resourceUri = null, + public readonly ?array $visibility = null, + ) { + } + + /** + * @param UiToolMetaData $data + */ + public static function fromArray(array $data): self + { + if (isset($data['resourceUri']) && !\is_string($data['resourceUri'])) { + throw new InvalidArgumentException('Invalid "resourceUri" in UiToolMeta data.'); + } + if (isset($data['visibility']) && !\is_array($data['visibility'])) { + throw new InvalidArgumentException('Invalid "visibility" in UiToolMeta data; expected an array.'); + } + + return new self( + resourceUri: $data['resourceUri'] ?? null, + visibility: isset($data['visibility']) ? array_map( + static function (mixed $entry): ToolVisibility { + if (!\is_string($entry) || null === $case = ToolVisibility::tryFrom($entry)) { + throw new InvalidArgumentException('Each entry in "visibility" of UiToolMeta data must be a valid tool visibility.'); + } + + return $case; + }, + $data['visibility'], + ) : null, + ); + } + + /** + * @return UiToolMetaData + */ + public function jsonSerialize(): array + { + $data = []; + + if (null !== $this->resourceUri) { + $data['resourceUri'] = $this->resourceUri; + } + if (null !== $this->visibility) { + $data['visibility'] = array_map(static fn (ToolVisibility $v): string => $v->value, $this->visibility); + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionIdentifier.php b/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionIdentifier.php new file mode 100644 index 0000000..2d714d6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionIdentifier.php @@ -0,0 +1,98 @@ + + */ +final class ExtensionIdentifier implements \Stringable +{ + /** Second labels only the specification may use. */ + public const RESERVED_LABELS = ['modelcontextprotocol', 'mcp']; + + /** Prefixes the specification itself allocates. */ + public const OFFICIAL_PREFIX = 'io.modelcontextprotocol/'; + + /** A label: starts with a letter, ends alphanumeric, hyphens inside. */ + private const LABEL = '[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?'; + + /** A name: alphanumeric at both ends, `-`, `_` and `.` inside. */ + private const NAME = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?'; + + /** + * @throws InvalidArgumentException if $identifier is not a valid `_meta` prefix + */ + public function __construct( + private readonly string $identifier, + ) { + if (null !== $reason = self::check($identifier)) { + throw new InvalidArgumentException($reason); + } + } + + /** + * Whether this identifier claims a prefix the specification reserves. + * + * Not an error on its own — the official extensions legitimately use it — + * but a third party doing so is misrepresenting itself, so callers that are + * not the SDK should refuse. + */ + public function isReserved(): bool + { + $labels = explode('.', strstr($this->identifier, '/', true) ?: ''); + + return \in_array($labels[1] ?? '', self::RESERVED_LABELS, true); + } + + public function __toString(): string + { + return $this->identifier; + } + + /** + * @return string|null the reason $identifier is invalid, or null when it is well-formed + */ + private static function check(string $identifier): ?string + { + $slash = strpos($identifier, '/'); + + if (false === $slash) { + return \sprintf('"%s" has no prefix; an extension identifier must be prefixed, e.g. "com.example/my-extension".', $identifier); + } + + $prefix = substr($identifier, 0, $slash); + $name = substr($identifier, $slash + 1); + + if (1 !== preg_match('/^'.self::LABEL.'(?:\.'.self::LABEL.')*$/', $prefix)) { + return \sprintf('"%s" is not a valid prefix: labels must start with a letter, end alphanumeric, and be separated by dots.', $prefix); + } + + if ('' === $name || 1 !== preg_match('/^'.self::NAME.'$/', $name)) { + return \sprintf('"%s" is not a valid extension name: it must start and end alphanumeric.', $name); + } + + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionInterface.php b/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionInterface.php new file mode 100644 index 0000000..a663525 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Extension/ExtensionInterface.php @@ -0,0 +1,70 @@ +]`. The same + * extension object can be enabled on a server (initialize response) and on a client + * (initialize request); the side that enables it decides which. + * + * An extension that only announces a capability, without adding RPC methods of its + * own, can extend {@see AbstractExtension} and skip {@see self::getMessages()} and + * {@see self::getRequestHandlers()} entirely. + * + * @author Christopher Hertel + */ +interface ExtensionInterface +{ + /** + * The reverse-DNS identifier used as the key under `capabilities.extensions`. + */ + public function getId(): ExtensionIdentifier; + + /** + * The capability payload announced for this extension. + * + * The returned array is cast to an object and embedded under + * `capabilities.extensions[]`, so every value must be JSON-serializable + * (scalars, arrays, or `JsonSerializable` objects). + * + * @return array + */ + public function getCapabilities(): array; + + /** + * Every message class this extension defines. + * + * These are registered with the {@see \Mcp\JsonRpc\MessageFactory}, without + * which an extension's method cannot be decoded off the wire at all, and + * their method names are what let a server distinguish an extension it does + * not serve from a method that does not exist. An extension with no methods + * of its own returns an empty array. + * + * @return list|class-string> + */ + public function getMessages(): array; + + /** + * The handlers serving those methods. + * + * @return iterable> + */ + public function getRequestHandlers(): iterable; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Icon.php b/admin/vendor/mcp/sdk/src/Schema/Icon.php new file mode 100644 index 0000000..2751343 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Icon.php @@ -0,0 +1,135 @@ + + */ +class Icon implements \JsonSerializable +{ + /** + * @param string $src a standard URI pointing to an icon resource + * @param ?string $mimeType optional override if the server's MIME type is missing or generic + * @param ?string[] $sizes optional array of strings that specify sizes at which the icon can be used. + * Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for + * scalable formats like SVG. + * @param ?IconTheme $theme Optional background this icon is designed for. When omitted, the icon is + * assumed to work against any background. + */ + public function __construct( + public readonly string $src, + public readonly ?string $mimeType = null, + public readonly ?array $sizes = null, + public readonly ?IconTheme $theme = null, + ) { + if (empty($src)) { + throw new InvalidArgumentException('Icon "src" must be a non-empty string.'); + } + if (!preg_match('#^(https?://|data:)#', $src)) { + throw new InvalidArgumentException('Icon "src" must be a valid URL or data URI.'); + } + + if (null !== $sizes) { + foreach ($sizes as $size) { + if (!\is_string($size)) { + throw new InvalidArgumentException('Each size in "sizes" must be a string.'); + } + if (!preg_match('/^(any|\d+x\d+)$/', $size)) { + throw new InvalidArgumentException(\sprintf('Invalid size format "%s" in "sizes". Expected "WxH" or "any".', $size)); + } + } + } + } + + /** + * @param IconData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['src']) || !\is_string($data['src'])) { + throw new InvalidArgumentException('Invalid or missing "src" in Icon data.'); + } + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" in Icon data.'); + } + if (isset($data['sizes']) && !\is_array($data['sizes'])) { + throw new InvalidArgumentException('Invalid "sizes" in Icon data.'); + } + + $theme = null; + if (isset($data['theme'])) { + if (!\is_string($data['theme']) || null === $theme = IconTheme::tryFrom($data['theme'])) { + throw new InvalidArgumentException('Invalid "theme" in Icon data.'); + } + } + + return new self($data['src'], $data['mimeType'] ?? null, $data['sizes'] ?? null, $theme); + } + + /** + * Hydrates an "icons" list, rejecting entries that are not objects. + * + * @param array $icons + * @param string $context the surrounding schema type, used for the error message + * + * @return self[] + */ + public static function listFromArray(array $icons, string $context): array + { + return array_map( + static function (mixed $icon) use ($context): self { + if (!\is_array($icon)) { + throw new InvalidArgumentException(\sprintf('Each entry in "icons" of %s data must be an array.', $context)); + } + + return self::fromArray($icon); + }, + $icons, + ); + } + + /** + * @return IconData + */ + public function jsonSerialize(): array + { + $data = [ + 'src' => $this->src, + ]; + + if (null !== $this->mimeType) { + $data['mimeType'] = $this->mimeType; + } + + if (null !== $this->sizes) { + $data['sizes'] = $this->sizes; + } + + if (null !== $this->theme) { + $data['theme'] = $this->theme->value; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Implementation.php b/admin/vendor/mcp/sdk/src/Schema/Implementation.php new file mode 100644 index 0000000..b48be54 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Implementation.php @@ -0,0 +1,121 @@ + + */ +class Implementation implements \JsonSerializable +{ + /** + * @param ?Icon[] $icons + * @param ?string $title Display name for UI and end-user contexts. Falls back to $name when absent. + */ + public function __construct( + public readonly string $name = 'app', + public readonly string $version = 'dev', + public readonly ?string $description = null, + public readonly ?array $icons = null, + public readonly ?string $websiteUrl = null, + public readonly ?string $title = null, + ) { + } + + /** + * @param array{ + * name: string, + * version: string, + * description?: string, + * icons?: IconData[], + * websiteUrl?: string, + * title?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['name']) || !\is_string($data['name']) || '' === $data['name']) { + throw new InvalidArgumentException('Invalid or missing "name" in Implementation data.'); + } + if (!isset($data['version']) || !\is_string($data['version']) || '' === $data['version']) { + throw new InvalidArgumentException('Invalid or missing "version" in Implementation data.'); + } + + if (isset($data['icons'])) { + if (!\is_array($data['icons'])) { + throw new InvalidArgumentException('Invalid "icons" in Implementation data; expected an array.'); + } + + $data['icons'] = Icon::listFromArray($data['icons'], 'Implementation'); + } + + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in Implementation data.'); + } + if (isset($data['websiteUrl']) && !\is_string($data['websiteUrl'])) { + throw new InvalidArgumentException('Invalid "websiteUrl" in Implementation data.'); + } + if (isset($data['title']) && !\is_string($data['title'])) { + throw new InvalidArgumentException('Invalid "title" in Implementation data.'); + } + + return new self( + $data['name'], + $data['version'], + $data['description'] ?? null, + $data['icons'] ?? null, + $data['websiteUrl'] ?? null, + $data['title'] ?? null, + ); + } + + /** + * @return array{ + * name: string, + * version: string, + * description?: string, + * icons?: Icon[], + * websiteUrl?: string, + * title?: string, + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'name' => $this->name, + 'version' => $this->version, + ]; + + if (null !== $this->description) { + $data['description'] = $this->description; + } + + if (null !== $this->icons) { + $data['icons'] = $this->icons; + } + + if (null !== $this->websiteUrl) { + $data['websiteUrl'] = $this->websiteUrl; + } + + if (null !== $this->title) { + $data['title'] = $this->title; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Error.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Error.php new file mode 100644 index 0000000..7749fa8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Error.php @@ -0,0 +1,212 @@ + + */ +class Error implements MessageInterface +{ + public const PARSE_ERROR = -32700; + public const INVALID_REQUEST = -32600; + public const METHOD_NOT_FOUND = -32601; + public const INVALID_PARAMS = -32602; + public const INTERNAL_ERROR = -32603; + public const SERVER_ERROR = -32000; + public const RESOURCE_NOT_FOUND = -32002; + + /** + * Values in the HTTP headers contradict the request body, or a required + * header is missing or malformed. Answered with `400 Bad Request`. + */ + public const HEADER_MISMATCH = -32020; + + /** + * Handling the request needs a client capability the client never declared. + * Answered with `400 Bad Request`. + */ + public const MISSING_REQUIRED_CLIENT_CAPABILITY = -32021; + + /** + * The request's protocol version is unknown to this server, or is a version + * it has chosen not to implement. Answered with `400 Bad Request`. + */ + public const UNSUPPORTED_PROTOCOL_VERSION = -32022; + + /** + * @param string|int|null $id The id of the request this answers. `null` only when it could not be + * read — a malformed body, or a notification that was refused — in which + * case the member is omitted rather than sent as an id nobody issued. + * @param int $code the error type that occurred + * @param string $message a short description of the error + * @param mixed|null $data additional information about the error + */ + public function __construct( + public readonly string|int|null $id, + public readonly int $code, + public readonly string $message, + public readonly mixed $data = null, + ) { + } + + /** + * @param ErrorData $data + */ + final public static function fromArray(array $data): self + { + if (!isset($data['jsonrpc']) || MessageInterface::JSONRPC_VERSION !== $data['jsonrpc']) { + throw new InvalidArgumentException('Invalid or missing "jsonrpc" in Error data.'); + } + // An error response carrying no id is well-formed: it is what a + // receiver sends when the id could not be read off the request. + if (isset($data['id']) && !\is_string($data['id']) && !\is_int($data['id'])) { + throw new InvalidArgumentException('Invalid "id" type in Error data.'); + } + if (!isset($data['error']) || !\is_array($data['error'])) { + throw new InvalidArgumentException('Invalid or missing "error" field in Error data.'); + } + if (!isset($data['error']['code']) || !\is_int($data['error']['code'])) { + throw new InvalidArgumentException('Invalid or missing "code" in Error data.'); + } + if (!isset($data['error']['message']) || !\is_string($data['error']['message'])) { + throw new InvalidArgumentException('Invalid or missing "message" in Error data.'); + } + + return new self($data['id'] ?? null, $data['error']['code'], $data['error']['message'], $data['error']['data'] ?? null); + } + + final public static function forParseError(string $message, string|int|null $id = null): self + { + return new self($id, self::PARSE_ERROR, $message); + } + + final public static function forInvalidRequest(string $message, string|int|null $id = null): self + { + return new self($id, self::INVALID_REQUEST, $message); + } + + final public static function forMethodNotFound(string $message, string|int|null $id = null): self + { + return new self($id, self::METHOD_NOT_FOUND, $message); + } + + final public static function forInvalidParams(string $message, string|int|null $id = null, mixed $data = null): self + { + return new self($id, self::INVALID_PARAMS, $message, $data); + } + + final public static function forInternalError(string $message, string|int|null $id = null): self + { + return new self($id, self::INTERNAL_ERROR, $message); + } + + final public static function forServerError(string $message, string|int|null $id = null): self + { + return new self($id, self::SERVER_ERROR, $message); + } + + final public static function forResourceNotFound(string $message, string|int|null $id = null): self + { + return new self($id, self::RESOURCE_NOT_FOUND, $message); + } + + final public static function forHeaderMismatch(string $message, string|int|null $id = null): self + { + return new self($id, self::HEADER_MISMATCH, $message); + } + + /** + * @param ClientCapabilities $requiredCapabilities the capabilities the server needs to process the request + */ + final public static function forMissingRequiredClientCapability( + string $message, + ClientCapabilities $requiredCapabilities, + string|int|null $id = null, + ): self { + return new self($id, self::MISSING_REQUIRED_CLIENT_CAPABILITY, $message, [ + 'requiredCapabilities' => $requiredCapabilities, + ]); + } + + /** + * The client is expected to pick a mutually supported version out of + * $supported and retry, so the list travels with the error rather than + * only in the message. + * + * @param list $supported versions this server does support + */ + final public static function forUnsupportedProtocolVersion( + string $requested, + array $supported, + string|int|null $id = null, + ): self { + return new self($id, self::UNSUPPORTED_PROTOCOL_VERSION, 'Unsupported protocol version', [ + 'requested' => $requested, + 'supported' => array_values(array_map(static fn (ProtocolVersion $v): string => $v->value, $supported)), + ]); + } + + public function getId(): string|int|null + { + return $this->id; + } + + /** + * @return array{ + * jsonrpc: string, + * id?: string|int, + * error: array{ + * code: int, + * message: string, + * data?: mixed, + * }, + * } + */ + public function jsonSerialize(): array + { + $error = [ + 'code' => $this->code, + 'message' => $this->message, + ]; + + if (null !== $this->data) { + $error['data'] = $this->data; + } + + $data = ['jsonrpc' => MessageInterface::JSONRPC_VERSION]; + + // Omitted, not empty: `"id": ""` claims the sender issued a request + // with an empty-string id, which is a different statement from "the + // id could not be read". + if (null !== $this->id) { + $data['id'] = $this->id; + } + + $data['error'] = $error; + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/HasMethodInterface.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/HasMethodInterface.php new file mode 100644 index 0000000..cbaca8e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/HasMethodInterface.php @@ -0,0 +1,27 @@ + + */ +interface HasMethodInterface +{ + public static function getMethod(): string; + + /** + * @param array $data + */ + public static function fromArray(array $data): self; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/MessageInterface.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/MessageInterface.php new file mode 100644 index 0000000..9427eaa --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/MessageInterface.php @@ -0,0 +1,25 @@ + + */ +interface MessageInterface extends \JsonSerializable +{ + public const JSONRPC_VERSION = '2.0'; + public const PROTOCOL_VERSION = ProtocolVersion::V2025_11_25; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Notification.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Notification.php new file mode 100644 index 0000000..bfd37f1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Notification.php @@ -0,0 +1,88 @@ +|null + * } + * + * @author Kyrian Obikwelu + */ +abstract class Notification implements HasMethodInterface, MessageInterface +{ + /** + * @var array|null + */ + protected ?array $meta = null; + + abstract public static function getMethod(): string; + + /** + * @param NotificationData $data + */ + public static function fromArray(array $data): self + { + if (isset($data['id'])) { + throw new InvalidArgumentException('Notification MUST NOT contain an "id" field.'); + } + if (!isset($data['method']) || !\is_string($data['method'])) { + throw new InvalidArgumentException('Invalid or missing "method" for Notification.'); + } + $params = $data['params'] ?? null; + if (null !== $params && !\is_array($params)) { + throw new InvalidArgumentException('"params" for Notification must be an array/object or null.'); + } + + $notification = static::fromParams($params); + + if (isset($data['params']['_meta'])) { + $notification->meta = $data['params']['_meta']; + } + + return $notification; + } + + /** + * @param array|null $params + */ + abstract protected static function fromParams(?array $params): self; + + /** + * @return NotificationData + */ + public function jsonSerialize(): array + { + $array = [ + 'jsonrpc' => MessageInterface::JSONRPC_VERSION, + 'method' => static::getMethod(), + ]; + if (null !== $params = $this->getParams()) { + $array['params'] = $params; + } + + if (null !== $this->meta && !isset($params['meta'])) { + $array['params']['_meta'] = $this->meta; + } + + return $array; + } + + /** + * @return array|null + */ + abstract protected function getParams(): ?array; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Request.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Request.php new file mode 100644 index 0000000..cb8ed83 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Request.php @@ -0,0 +1,136 @@ +, + * } + * + * @author Kyrian Obikwelu + */ +abstract class Request implements HasMethodInterface, MessageInterface +{ + protected string|int $id; + /** + * @var array|null + */ + protected ?array $meta = null; + + abstract public static function getMethod(): string; + + /** + * @param RequestData $data + */ + public static function fromArray(array $data): static + { + if (($data['jsonrpc'] ?? null) !== MessageInterface::JSONRPC_VERSION) { + throw new InvalidArgumentException('Invalid or missing "jsonrpc" version for Request.'); + } + if (!isset($data['id']) || !\is_string($data['id']) && !\is_int($data['id'])) { + throw new InvalidArgumentException('Invalid or missing "id" for Request.'); + } + if (!isset($data['method']) || !\is_string($data['method'])) { + throw new InvalidArgumentException('Invalid or missing "method" for Request.'); + } + $params = $data['params'] ?? null; + if ($params instanceof \stdClass) { + $params = (array) $params; + } + if (null !== $params && !\is_array($params)) { + throw new InvalidArgumentException('"params" for Request must be an array/object or null.'); + } + + $request = static::fromParams($params); + $request->id = $data['id']; + + if (isset($data['params']['_meta'])) { + $meta = $data['params']['_meta']; + if ($meta instanceof \stdClass) { + $meta = (array) $meta; + } + if (\is_array($meta)) { + $request->meta = $meta; + } + } + + return $request; + } + + /** + * @param array|null $params + */ + abstract protected static function fromParams(?array $params): static; + + public function getId(): string|int + { + return $this->id; + } + + /** + * @return array|null + */ + public function getMeta(): ?array + { + return $this->meta; + } + + public function withId(string|int $id): static + { + $clone = clone $this; + $clone->id = $id; + + return $clone; + } + + /** + * @param array|null $meta + */ + public function withMeta(?array $meta): static + { + $clone = clone $this; + $clone->meta = $meta; + + return $clone; + } + + /** + * @return RequestData + */ + public function jsonSerialize(): array + { + $array = [ + 'jsonrpc' => MessageInterface::JSONRPC_VERSION, + 'id' => $this->id, + 'method' => static::getMethod(), + ]; + if (null !== $params = $this->getParams()) { + $array['params'] = $params; + } + + if (null !== $this->meta && !isset($params['meta'])) { + $array['params']['_meta'] = $this->meta; + } + + return $array; + } + + /** + * @return array|null + */ + abstract protected function getParams(): ?array; +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Response.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Response.php new file mode 100644 index 0000000..4c64881 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/Response.php @@ -0,0 +1,90 @@ +, + * } + * + * @author Kyrian Obikwelu + */ +class Response implements MessageInterface +{ + /** + * @param string|int $id this MUST be the same as the value of the id member in the Request Object + * @param TResult $result the value of this member is determined by the method invoked on the Server + */ + public function __construct( + public readonly string|int $id, + /** @var TResult */ + public readonly mixed $result, + ) { + } + + public function getId(): string|int + { + return $this->id; + } + + /** + * @param ResponseData $data + * + * @return self> + */ + public static function fromArray(array $data): self + { + if (($data['jsonrpc'] ?? null) !== MessageInterface::JSONRPC_VERSION) { + throw new InvalidArgumentException('Invalid or missing "jsonrpc" version for Response.'); + } + if (!isset($data['id'])) { + throw new InvalidArgumentException('Missing "id" for Response.'); + } + if (!\is_string($data['id']) && !\is_int($data['id'])) { + throw new InvalidArgumentException('Invalid "id" type for Response.'); + } + if (!isset($data['result'])) { + throw new InvalidArgumentException('Response must contain "result" field.'); + } + if (!\is_array($data['result'])) { + throw new InvalidArgumentException('Response "result" must be an array.'); + } + + return new self($data['id'], $data['result']); + } + + /** + * @return array{ + * jsonrpc: string, + * id: string|int, + * result: mixed, + * } + */ + public function jsonSerialize(): array + { + return [ + 'jsonrpc' => MessageInterface::JSONRPC_VERSION, + 'id' => $this->id, + 'result' => $this->result, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/JsonRpc/ResultInterface.php b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/ResultInterface.php new file mode 100644 index 0000000..180dfba --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/JsonRpc/ResultInterface.php @@ -0,0 +1,21 @@ + + */ +interface ResultInterface extends \JsonSerializable +{ +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ModelHint.php b/admin/vendor/mcp/sdk/src/Schema/ModelHint.php new file mode 100644 index 0000000..5bdd88b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ModelHint.php @@ -0,0 +1,53 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +class ModelHint implements \JsonSerializable +{ + /** + * @param string|null $name A hint for a model name. + * + * The client SHOULD treat this as a substring of a model name; for example: + * - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + * - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + * - `claude` should match any Claude model + * + * The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + * - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + */ + public function __construct( + public readonly ?string $name = null, + ) { + } + + /** + * @return array{name: string}|array{} + */ + public function jsonSerialize(): array + { + if (null === $this->name) { + return []; + } + + return ['name' => $this->name]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ModelPreferences.php b/admin/vendor/mcp/sdk/src/Schema/ModelPreferences.php new file mode 100644 index 0000000..e668429 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ModelPreferences.php @@ -0,0 +1,119 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +class ModelPreferences implements \JsonSerializable +{ + /** + * @param ModelHint[]|null $hints Optional hints about the model to use. + * + * If multiple hints are specified, the client MUST evaluate them in order (such that the first match is taken). + * + * The client SHOULD prioritize these hints over the numeric priorities, but MAY still use the priorities to select from ambiguous matches. + * @param float|null $costPriority How much to prioritize cost when selecting a model. A value of 0 means cost is not important, while + * a value of 1 means cost is the most important factor. Minimum value is 0, maximum value is 1. + * @param float|null $speedPriority How much to prioritize sampling speed (latency) when selecting a model. A value of 0 means + * speed is not important, while a value of 1 means speed is the most important factor. Minimum value is 0, maximum value is 1. + * @param float|null $intelligencePriority How much to prioritize intelligence and capabilities when selecting a model. A value of 0 + * means intelligence is not important, while a value of 1 means intelligence is the most important factor. + */ + public function __construct( + public readonly ?array $hints = null, + public readonly ?float $costPriority = null, + public readonly ?float $speedPriority = null, + public readonly ?float $intelligencePriority = null, + ) { + } + + /** + * @param ModelPreferencesData $preferences + */ + public static function fromArray(array $preferences): self + { + if (isset($preferences['hints']) && !\is_array($preferences['hints'])) { + throw new InvalidArgumentException('Invalid "hints" in ModelPreferences data.'); + } + + return new self( + $preferences['hints'] ?? null, + self::priority($preferences, 'costPriority'), + self::priority($preferences, 'speedPriority'), + self::priority($preferences, 'intelligencePriority'), + ); + } + + /** + * @param array $preferences + */ + private static function priority(array $preferences, string $key): ?float + { + if (!isset($preferences[$key])) { + return null; + } + + // JSON numbers decode to int when they have no fractional part. + if (!\is_float($preferences[$key]) && !\is_int($preferences[$key])) { + throw new InvalidArgumentException(\sprintf('Invalid "%s" in ModelPreferences data; expected a number.', $key)); + } + + return (float) $preferences[$key]; + } + + /** + * @return ModelPreferencesData + */ + public function jsonSerialize(): array + { + $result = []; + if (null !== $this->hints) { + $result['hints'] = $this->hints; + } + if (null !== $this->costPriority) { + $result['costPriority'] = $this->costPriority; + } + if (null !== $this->speedPriority) { + $result['speedPriority'] = $this->speedPriority; + } + if (null !== $this->intelligencePriority) { + $result['intelligencePriority'] = $this->intelligencePriority; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/CancelledNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/CancelledNotification.php new file mode 100644 index 0000000..4ae3257 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/CancelledNotification.php @@ -0,0 +1,68 @@ + + */ +class CancelledNotification extends Notification +{ + /** + * @param string|int $requestId The ID of the request that is being cancelled. This MUST correspond to the ID of a request previously issued in the same direction. + * @param ?string $reason An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + */ + public function __construct( + public readonly string|int $requestId, + public readonly ?string $reason = null, + ) { + } + + public static function getMethod(): string + { + return 'notifications/cancelled'; + } + + protected static function fromParams(?array $params): Notification + { + if (null === $params || !isset($params['requestId']) || (!\is_string($params['requestId']) && !\is_int($params['requestId']))) { + throw new InvalidArgumentException('Invalid or missing "requestId" parameter for "notifications/cancelled" notification.'); + } + + if (isset($params['reason']) && !\is_string($params['reason'])) { + throw new InvalidArgumentException('Invalid "reason" parameter for "notifications/cancelled" notification.'); + } + + return new self($params['requestId'], $params['reason'] ?? null); + } + + protected function getParams(): ?array + { + $params = ['requestId' => $this->requestId]; + + if (null !== $this->reason) { + $params['reason'] = $this->reason; + } + + return $params; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/InitializedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/InitializedNotification.php new file mode 100644 index 0000000..8e73327 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/InitializedNotification.php @@ -0,0 +1,37 @@ + + */ +class InitializedNotification extends Notification +{ + public static function getMethod(): string + { + return 'notifications/initialized'; + } + + public static function fromParams(?array $params): self + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/LoggingMessageNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/LoggingMessageNotification.php new file mode 100644 index 0000000..17cbd70 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/LoggingMessageNotification.php @@ -0,0 +1,79 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +class LoggingMessageNotification extends Notification +{ + /** + * @param LoggingLevel $level the severity of this log message + * @param mixed $data The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + * @param ?string $logger an optional name of the logger issuing this message + */ + public function __construct( + public readonly LoggingLevel $level, + public readonly mixed $data, + public readonly ?string $logger = null, + ) { + } + + public static function getMethod(): string + { + return 'notifications/message'; + } + + protected static function fromParams(?array $params): Notification + { + if (!isset($params['level']) || !\is_string($params['level'])) { + throw new InvalidArgumentException('Missing or invalid "level" parameter for "notifications/message" notification.'); + } + + if (!isset($params['data'])) { + throw new InvalidArgumentException('Missing "data" parameter for "notifications/message" notification.'); + } + + if (null === $level = LoggingLevel::tryFrom($params['level'])) { + throw new InvalidArgumentException(\sprintf('Invalid "level" parameter "%s" for "notifications/message" notification.', $params['level'])); + } + + if (isset($params['logger']) && !\is_string($params['logger'])) { + throw new InvalidArgumentException('Invalid "logger" parameter for "notifications/message" notification.'); + } + + $data = \is_string($params['data']) ? $params['data'] : json_encode($params['data']); + + return new self($level, $data, $params['logger'] ?? null); + } + + protected function getParams(): ?array + { + $params = [ + 'level' => $this->level->value, + 'data' => $this->data, + ]; + + if (null !== $this->logger) { + $params['logger'] = $this->logger; + } + + return $params; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/ProgressNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/ProgressNotification.php new file mode 100644 index 0000000..15fc50a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/ProgressNotification.php @@ -0,0 +1,89 @@ + + */ +class ProgressNotification extends Notification +{ + /** + * @param string|int $progressToken the progress token which was given in the initial request, used to + * associate this notification with the request that is proceeding + * @param float $progress The progress thus far. This should increase every time progress is + * made, even if the total is unknown. + * @param ?float $total total number of items to process (or total progress required), if known + * @param ?string $message an optional message describing the current progress + */ + public function __construct( + public readonly string|int $progressToken, + public readonly float $progress, + public readonly ?float $total = null, + public readonly ?string $message = null, + ) { + } + + public static function getMethod(): string + { + return 'notifications/progress'; + } + + protected static function fromParams(?array $params): Notification + { + // JSON numbers decode to int when they have no fractional part. + if (!isset($params['progressToken']) || !\is_string($params['progressToken']) && !\is_int($params['progressToken'])) { + throw new InvalidArgumentException('Missing or invalid "progressToken" parameter for "notifications/progress" notification.'); + } + + if (!isset($params['progress']) || !\is_float($params['progress']) && !\is_int($params['progress'])) { + throw new InvalidArgumentException('Missing or invalid "progress" parameter for "notifications/progress" notification.'); + } + + if (isset($params['total']) && !\is_float($params['total']) && !\is_int($params['total'])) { + throw new InvalidArgumentException('Invalid "total" parameter for "notifications/progress" notification.'); + } + + if (isset($params['message']) && !\is_string($params['message'])) { + throw new InvalidArgumentException('Invalid "message" parameter for "notifications/progress" notification.'); + } + + return new self( + $params['progressToken'], + (float) $params['progress'], + isset($params['total']) ? (float) $params['total'] : null, + $params['message'] ?? null, + ); + } + + protected function getParams(): ?array + { + $params = [ + 'progressToken' => $this->progressToken, + 'progress' => $this->progress, + ]; + + if (null !== $this->total) { + $params['total'] = $this->total; + } + + if (null !== $this->message) { + $params['message'] = $this->message; + } + + return $params; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/PromptListChangedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/PromptListChangedNotification.php new file mode 100644 index 0000000..1557e1e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/PromptListChangedNotification.php @@ -0,0 +1,37 @@ + + */ +class PromptListChangedNotification extends Notification +{ + public static function getMethod(): string + { + return 'notifications/prompts/list_changed'; + } + + protected static function fromParams(?array $params): Notification + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceListChangedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceListChangedNotification.php new file mode 100644 index 0000000..9a15e12 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceListChangedNotification.php @@ -0,0 +1,37 @@ + + */ +class ResourceListChangedNotification extends Notification +{ + public static function getMethod(): string + { + return 'notifications/resources/list_changed'; + } + + protected static function fromParams(?array $params): Notification + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceUpdatedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceUpdatedNotification.php new file mode 100644 index 0000000..946d500 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/ResourceUpdatedNotification.php @@ -0,0 +1,47 @@ + + */ +class ResourceUpdatedNotification extends Notification +{ + public function __construct( + public readonly string $uri, + ) { + } + + public static function getMethod(): string + { + return 'notifications/resources/updated'; + } + + protected static function fromParams(?array $params): Notification + { + if (null === $params || !isset($params['uri']) || !\is_string($params['uri'])) { + throw new InvalidArgumentException('Invalid or missing "uri" parameter for notifications/resources/updated notification.'); + } + + return new self($params['uri']); + } + + protected function getParams(): ?array + { + return [ + 'uri' => $this->uri, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/RootsListChangedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/RootsListChangedNotification.php new file mode 100644 index 0000000..89a0e47 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/RootsListChangedNotification.php @@ -0,0 +1,43 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +class RootsListChangedNotification extends Notification +{ + public static function getMethod(): string + { + return 'notifications/roots/list_changed'; + } + + protected static function fromParams(?array $params): Notification + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Notification/ToolListChangedNotification.php b/admin/vendor/mcp/sdk/src/Schema/Notification/ToolListChangedNotification.php new file mode 100644 index 0000000..e081df2 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Notification/ToolListChangedNotification.php @@ -0,0 +1,37 @@ + + */ +class ToolListChangedNotification extends Notification +{ + public static function getMethod(): string + { + return 'notifications/tools/list_changed'; + } + + protected static function fromParams(?array $params): Notification + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Page.php b/admin/vendor/mcp/sdk/src/Schema/Page.php new file mode 100644 index 0000000..3d54646 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Page.php @@ -0,0 +1,35 @@ + + */ +final class Page extends \ArrayObject +{ + /** + * @param array $references Items can be Tool, Prompt, ResourceTemplate, or ResourceDefinition + */ + public function __construct( + public readonly array $references, + public readonly ?string $nextCursor, + ) { + parent::__construct($references, \ArrayObject::ARRAY_AS_PROPS); + } + + public function count(): int + { + return \count($this->references); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Prompt.php b/admin/vendor/mcp/sdk/src/Schema/Prompt.php new file mode 100644 index 0000000..1f33e41 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Prompt.php @@ -0,0 +1,133 @@ + + * } + * + * @author Kyrian Obikwelu + */ +class Prompt implements \JsonSerializable +{ + /** + * @param string $name the name of the prompt or prompt template + * @param ?string $title Optional human-readable title for display in UI + * @param ?string $description an optional description of what this prompt provides + * @param ?PromptArgument[] $arguments A list of arguments for templating. Null if not a template. + * @param ?Icon[] $icons optional icons representing the prompt + * @param ?array $meta Optional metadata + */ + public function __construct( + public readonly string $name, + public readonly ?string $title = null, + public readonly ?string $description = null, + public readonly ?array $arguments = null, + public readonly ?array $icons = null, + public readonly ?array $meta = null, + ) { + if (null !== $this->arguments) { + foreach ($this->arguments as $arg) { + if (!$arg instanceof PromptArgument) { + throw new InvalidArgumentException('All items in Prompt "arguments" must be PromptArgument instances.'); + } + } + } + } + + /** + * @param PromptData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in Prompt data.'); + } + $arguments = null; + if (isset($data['arguments']) && \is_array($data['arguments'])) { + $arguments = array_map( + static function (mixed $argData): PromptArgument { + if (!\is_array($argData)) { + throw new InvalidArgumentException('Each entry in "arguments" of Prompt data must be an array.'); + } + + return PromptArgument::fromArray($argData); + }, + $data['arguments'], + ); + } + + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in Prompt data.'); + } + if (isset($data['title']) && !\is_string($data['title'])) { + throw new InvalidArgumentException('Invalid "title" in Prompt data.'); + } + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in Prompt data.'); + } + + return new self( + name: $data['name'], + title: $data['title'] ?? null, + description: $data['description'] ?? null, + arguments: $arguments, + icons: isset($data['icons']) && \is_array($data['icons']) ? Icon::listFromArray($data['icons'], 'Prompt') : null, + meta: isset($data['_meta']) ? $data['_meta'] : null + ); + } + + /** + * @return array{ + * name: string, + * title?: string, + * description?: string, + * arguments?: array, + * icons?: Icon[], + * _meta?: array + * } + */ + public function jsonSerialize(): array + { + $data = ['name' => $this->name]; + if (null !== $this->title) { + $data['title'] = $this->title; + } + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->arguments) { + $data['arguments'] = $this->arguments; + } + if (null !== $this->icons) { + $data['icons'] = $this->icons; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/PromptArgument.php b/admin/vendor/mcp/sdk/src/Schema/PromptArgument.php new file mode 100644 index 0000000..38fe3dc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/PromptArgument.php @@ -0,0 +1,79 @@ + + */ +class PromptArgument implements \JsonSerializable +{ + /** + * @param string $name the name of the argument + * @param string|null $description a human-readable description of the argument + * @param bool|null $required Whether this argument must be provided. Defaults to false per MCP spec if omitted. + */ + public function __construct( + public readonly string $name, + public readonly ?string $description = null, + public readonly ?bool $required = null, + ) { + } + + /** + * @param PromptArgumentData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in PromptArgument data.'); + } + + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in PromptArgument data.'); + } + if (isset($data['required']) && !\is_bool($data['required'])) { + throw new InvalidArgumentException('Invalid "required" in PromptArgument data.'); + } + + return new self( + name: $data['name'], + description: $data['description'] ?? null, + required: $data['required'] ?? null // Keep null if not present, MCP implies default false + ); + } + + /** + * @return PromptArgumentData + */ + public function jsonSerialize(): array + { + $data = ['name' => $this->name]; + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->required) { + $data['required'] = $this->required; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/PromptReference.php b/admin/vendor/mcp/sdk/src/Schema/PromptReference.php new file mode 100644 index 0000000..10ec755 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/PromptReference.php @@ -0,0 +1,44 @@ + + */ +class PromptReference implements \JsonSerializable +{ + public string $type = 'ref/prompt'; + + /** + * @param string $name The name of the prompt or prompt template + */ + public function __construct( + public readonly string $name, + ) { + } + + /** + * @return array{ + * type: string, + * name: string, + * } + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'name' => $this->name, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/CallToolRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/CallToolRequest.php new file mode 100644 index 0000000..0674066 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/CallToolRequest.php @@ -0,0 +1,71 @@ + + */ +final class CallToolRequest extends Request +{ + /** + * @param string $name the name of the tool to invoke + * @param array $arguments the arguments to pass to the tool + */ + public function __construct( + public readonly string $name, + public readonly array $arguments, + ) { + } + + public static function getMethod(): string + { + return 'tools/call'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['name']) || !\is_string($params['name'])) { + throw new InvalidArgumentException('Missing or invalid "name" parameter for tools/call.'); + } + + $arguments = $params['arguments'] ?? []; + + if ($arguments instanceof \stdClass) { + $arguments = (array) $arguments; + } + + if (!\is_array($arguments)) { + throw new InvalidArgumentException('Parameter "arguments" must be an array.'); + } + + return new self( + $params['name'], + $arguments, + ); + } + + /** + * @return array{name: string, arguments: array} + */ + protected function getParams(): array + { + return [ + 'name' => $this->name, + 'arguments' => $this->arguments ?: new \stdClass(), + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/CompletionCompleteRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/CompletionCompleteRequest.php new file mode 100644 index 0000000..7ad0332 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/CompletionCompleteRequest.php @@ -0,0 +1,85 @@ + + */ +final class CompletionCompleteRequest extends Request +{ + /** + * @param PromptReference|ResourceReference $ref the prompt or resource to complete + * @param array{ name: string, value: string } $argument the argument to complete + */ + public function __construct( + public readonly PromptReference|ResourceReference $ref, + public readonly array $argument, + ) { + } + + public static function getMethod(): string + { + return 'completion/complete'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['ref']) || !\is_array($params['ref'])) { + throw new InvalidArgumentException('Missing or invalid "ref" parameter for completion/complete.'); + } + + $ref = match ($params['ref']['type'] ?? null) { + 'ref/prompt' => new PromptReference(self::refString($params['ref'], 'name')), + 'ref/resource' => new ResourceReference(self::refString($params['ref'], 'uri')), + default => throw new InvalidArgumentException('Invalid "ref" parameter for completion/complete.'), + }; + + if (!isset($params['argument']) || !\is_array($params['argument'])) { + throw new InvalidArgumentException('Missing or invalid "argument" parameter for completion/complete.'); + } + + return new self($ref, $params['argument']); + } + + /** + * @param array $ref + */ + private static function refString(array $ref, string $key): string + { + if (!isset($ref[$key]) || !\is_string($ref[$key])) { + throw new InvalidArgumentException(\sprintf('Missing or invalid "ref.%s" parameter for completion/complete.', $key)); + } + + return $ref[$key]; + } + + /** + * @return array{ + * ref: PromptReference|ResourceReference, + * argument: array{ name: string, value: string } + * } + */ + protected function getParams(): array + { + return [ + 'ref' => $this->ref, + 'argument' => $this->argument, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/CreateSamplingMessageRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/CreateSamplingMessageRequest.php new file mode 100644 index 0000000..12e0309 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/CreateSamplingMessageRequest.php @@ -0,0 +1,299 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +final class CreateSamplingMessageRequest extends Request +{ + /** + * @param SamplingMessage[] $messages the messages to send to the model + * @param int $maxTokens The maximum number of tokens to sample, as requested by the server. + * The client MAY choose to sample fewer tokens than requested. + * @param ?ModelPreferences $preferences The server's preferences for which model to select. The client MAY + * ignore these preferences. + * @param ?string $systemPrompt An optional system prompt the server wants to use for sampling. The + * client MAY modify or omit this prompt. + * @param ?SamplingContext $includeContext A request to include context from one or more MCP servers (including + * the caller), to be attached to the prompt. The client MAY ignore this request. + * Allowed values: "none", "thisServer", "allServers" + * Values other than "none" are soft-deprecated and SHOULD only be sent + * when the client advertises the sampling.context capability. + * @param ?float $temperature The temperature to use for sampling. The client MAY ignore this request. + * @param ?string[] $stopSequences A list of sequences to stop sampling at. The client MAY ignore this request. + * @param ?array $metadata Optional metadata to pass through to the LLM provider. The format of + * this metadata is provider-specific. + * @param ?Tool[] $tools tools that the model may use during generation + * @param ?ToolChoice $toolChoice controls how the model uses tools + */ + public function __construct( + public readonly array $messages, + public readonly int $maxTokens, + public readonly ?ModelPreferences $preferences = null, + public readonly ?string $systemPrompt = null, + public readonly ?SamplingContext $includeContext = null, + public readonly ?float $temperature = null, + public readonly ?array $stopSequences = null, + public readonly ?array $metadata = null, + public readonly ?array $tools = null, + public readonly ?ToolChoice $toolChoice = null, + ) { + foreach ($this->messages as $message) { + if (!$message instanceof SamplingMessage) { + throw new InvalidArgumentException('Messages must be instance of SamplingMessage.'); + } + } + foreach ($this->tools ?? [] as $tool) { + if (!$tool instanceof Tool) { + throw new InvalidArgumentException('Tools must be instances of Tool.'); + } + } + } + + public static function getMethod(): string + { + return 'sampling/createMessage'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['messages']) || !\is_array($params['messages'])) { + throw new InvalidArgumentException('Missing or invalid "messages" parameter for sampling/createMessage.'); + } + + if (!isset($params['maxTokens']) || !\is_int($params['maxTokens'])) { + throw new InvalidArgumentException('Missing or invalid "maxTokens" parameter for sampling/createMessage.'); + } + + $messages = []; + foreach ($params['messages'] as $messageData) { + if ($messageData instanceof SamplingMessage) { + $messages[] = $messageData; + } elseif (\is_array($messageData)) { + $messages[] = SamplingMessage::fromArray($messageData); + } else { + throw new InvalidArgumentException('Invalid message format in sampling/createMessage.'); + } + } + + $preferences = null; + if (isset($params['preferences'])) { + if (!\is_array($params['preferences'])) { + throw new InvalidArgumentException('Invalid "preferences" parameter for sampling/createMessage.'); + } + $preferences = ModelPreferences::fromArray($params['preferences']); + } + + $includeContext = null; + if (isset($params['includeContext']) && \is_string($params['includeContext'])) { + $includeContext = SamplingContext::tryFrom($params['includeContext']); + } + + if (isset($params['systemPrompt']) && !\is_string($params['systemPrompt'])) { + throw new InvalidArgumentException('Invalid "systemPrompt" parameter for sampling/createMessage.'); + } + + if (isset($params['temperature']) && !\is_float($params['temperature']) && !\is_int($params['temperature'])) { + throw new InvalidArgumentException('Invalid "temperature" parameter for sampling/createMessage.'); + } + + if (isset($params['stopSequences'])) { + if (!\is_array($params['stopSequences'])) { + throw new InvalidArgumentException('Invalid "stopSequences" parameter for sampling/createMessage.'); + } + + foreach ($params['stopSequences'] as $stopSequence) { + if (!\is_string($stopSequence)) { + throw new InvalidArgumentException('Each entry in "stopSequences" must be a string for sampling/createMessage.'); + } + } + } + + if (isset($params['metadata']) && !\is_array($params['metadata'])) { + throw new InvalidArgumentException('Invalid "metadata" parameter for sampling/createMessage.'); + } + + $tools = null; + if (isset($params['tools'])) { + if (!\is_array($params['tools'])) { + throw new InvalidArgumentException('Invalid "tools" parameter for sampling/createMessage.'); + } + $tools = []; + foreach ($params['tools'] as $toolData) { + if ($toolData instanceof Tool) { + $tools[] = $toolData; + } elseif (\is_array($toolData)) { + $tools[] = Tool::fromArray($toolData); + } else { + throw new InvalidArgumentException('Invalid tool format in sampling/createMessage.'); + } + } + } + + $toolChoice = null; + if (isset($params['toolChoice'])) { + if ($params['toolChoice'] instanceof ToolChoice) { + $toolChoice = $params['toolChoice']; + } elseif (\is_array($params['toolChoice'])) { + $toolChoice = ToolChoice::fromArray($params['toolChoice']); + } else { + throw new InvalidArgumentException('Invalid "toolChoice" parameter for sampling/createMessage.'); + } + } + + return new self( + $messages, + $params['maxTokens'], + $preferences, + $params['systemPrompt'] ?? null, + $includeContext, + isset($params['temperature']) ? (float) $params['temperature'] : null, + $params['stopSequences'] ?? null, + $params['metadata'] ?? null, + $tools, + $toolChoice, + ); + } + + /** + * Assert the spec's tool-flow rules over the whole message list. + * + * These are deliberately kept out of the hydration path: a violation is an + * "invalid params" condition the peer must be told about, not a parse failure + * that would leave the request unanswered. Call it from whatever boundary can + * report it — the request handler when receiving, the gateway when sending. + * + * @throws InvalidArgumentException on the first violation found + */ + public function validateToolFlow(): void + { + $pendingToolUseIds = []; + + foreach ($this->messages as $message) { + $blocks = $message->getContentBlocks(); + + $toolResults = array_filter($blocks, static fn ($block): bool => $block instanceof ToolResultContent); + $toolUses = array_filter($blocks, static fn ($block): bool => $block instanceof ToolUseContent); + + if ($toolResults && \count($toolResults) !== \count($blocks)) { + throw new InvalidArgumentException('Tool results mixed with other content.'); + } + + if (Role::User === $message->role && $toolUses) { + throw new InvalidArgumentException('ToolUseContent is only valid in assistant sampling messages.'); + } + + if (Role::Assistant === $message->role && $toolResults) { + throw new InvalidArgumentException('ToolResultContent is only valid in user sampling messages.'); + } + + if ($pendingToolUseIds && !$toolResults) { + throw new InvalidArgumentException('Tool result missing in request.'); + } + + foreach ($toolResults as $toolResult) { + $matched = array_search($toolResult->toolUseId, $pendingToolUseIds, true); + if (false === $matched) { + throw new InvalidArgumentException(\sprintf('Tool result "%s" does not answer a preceding tool use.', $toolResult->toolUseId)); + } + unset($pendingToolUseIds[$matched]); + } + + if ($pendingToolUseIds) { + throw new InvalidArgumentException('Tool result missing in request.'); + } + + foreach ($toolUses as $toolUse) { + $pendingToolUseIds[] = $toolUse->id; + } + } + + if ($pendingToolUseIds) { + throw new InvalidArgumentException('Tool result missing in request.'); + } + } + + /** + * @return array{ + * messages: SamplingMessage[], + * maxTokens: int, + * preferences?: ModelPreferences, + * systemPrompt?: string, + * includeContext?: string, + * temperature?: float, + * stopSequences?: string[], + * metadata?: array, + * tools?: Tool[], + * toolChoice?: ToolChoice, + * } + */ + protected function getParams(): array + { + $params = [ + 'messages' => $this->messages, + 'maxTokens' => $this->maxTokens, + ]; + + if (null !== $this->preferences) { + $params['preferences'] = $this->preferences; + } + + if (null !== $this->systemPrompt) { + $params['systemPrompt'] = $this->systemPrompt; + } + + if (null !== $this->includeContext) { + $params['includeContext'] = $this->includeContext->value; + } + + if (null !== $this->temperature) { + $params['temperature'] = $this->temperature; + } + + if (null !== $this->stopSequences) { + $params['stopSequences'] = $this->stopSequences; + } + + if (null !== $this->metadata) { + $params['metadata'] = $this->metadata; + } + + if (null !== $this->tools) { + $params['tools'] = $this->tools; + } + + if (null !== $this->toolChoice) { + $params['toolChoice'] = $this->toolChoice; + } + + return $params; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/DiscoverRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/DiscoverRequest.php new file mode 100644 index 0000000..24852bc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/DiscoverRequest.php @@ -0,0 +1,43 @@ + + */ +final class DiscoverRequest extends Request +{ + public static function getMethod(): string + { + return 'server/discover'; + } + + protected static function fromParams(?array $params): static + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ElicitRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ElicitRequest.php new file mode 100644 index 0000000..15a74ee --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ElicitRequest.php @@ -0,0 +1,126 @@ + + */ +final class ElicitRequest extends Request +{ + /** + * @param string $message A human-readable message describing what information is needed + * @param ?ElicitationSchema $requestedSchema The schema defining the fields to elicit; required in form mode + * @param ElicitationMode $mode how the client should collect the information + * @param ?string $url the URL to send the user to; required in url mode + */ + public function __construct( + public readonly string $message, + public readonly ?ElicitationSchema $requestedSchema = null, + public readonly ElicitationMode $mode = ElicitationMode::Form, + public readonly ?string $url = null, + ) { + if (ElicitationMode::Form === $mode && null === $requestedSchema) { + throw new InvalidArgumentException('Form elicitation requires a "requestedSchema".'); + } + + if (ElicitationMode::Url === $mode && (null === $url || '' === $url)) { + throw new InvalidArgumentException('URL elicitation requires a non-empty "url".'); + } + } + + /** + * Elicit values from the user through a form built from $requestedSchema. + */ + public static function forForm(string $message, ElicitationSchema $requestedSchema): self + { + return new self($message, $requestedSchema); + } + + /** + * Send the user to $url to complete the interaction out of band. + */ + public static function forUrl(string $message, string $url): self + { + return new self($message, null, ElicitationMode::Url, $url); + } + + public static function getMethod(): string + { + return 'elicitation/create'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['message']) || !\is_string($params['message'])) { + throw new InvalidArgumentException('Missing or invalid "message" parameter for elicitation/create.'); + } + + // Default `mode` is form - url elicitation is opt-in. + $mode = ElicitationMode::Form; + if (\array_key_exists('mode', $params)) { + if (!\is_string($params['mode']) || null === $mode = ElicitationMode::tryFrom($params['mode'])) { + throw new InvalidArgumentException('Invalid "mode" parameter for elicitation/create.'); + } + } + + if (ElicitationMode::Url === $mode) { + if (!isset($params['url']) || !\is_string($params['url'])) { + throw new InvalidArgumentException('Missing or invalid "url" parameter for url-mode elicitation/create.'); + } + + return new self($params['message'], null, $mode, $params['url']); + } + + if (!isset($params['requestedSchema']) || !\is_array($params['requestedSchema'])) { + throw new InvalidArgumentException('Missing or invalid "requestedSchema" parameter for elicitation/create.'); + } + + return new self($params['message'], ElicitationSchema::fromArray($params['requestedSchema']), $mode); + } + + /** + * @return array{ + * message: string, + * mode?: string, + * requestedSchema?: ElicitationSchema, + * url?: string, + * } + */ + protected function getParams(): array + { + if (ElicitationMode::Url === $this->mode) { + return [ + 'message' => $this->message, + 'mode' => $this->mode->value, + 'url' => $this->url, + ]; + } + + // We don't need to send the mode if it's the default (form). + return [ + 'message' => $this->message, + 'requestedSchema' => $this->requestedSchema, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/GetPromptRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/GetPromptRequest.php new file mode 100644 index 0000000..5d91ff5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/GetPromptRequest.php @@ -0,0 +1,71 @@ + + */ +final class GetPromptRequest extends Request +{ + /** + * @param string $name the name of the prompt to get + * @param array|null $arguments the arguments to pass to the prompt + */ + public function __construct( + public readonly string $name, + public readonly ?array $arguments = null, + ) { + } + + public static function getMethod(): string + { + return 'prompts/get'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['name']) || !\is_string($params['name']) || empty($params['name'])) { + throw new InvalidArgumentException('Missing or invalid "name" parameter for prompts/get.'); + } + + $arguments = $params['arguments'] ?? null; + if (null !== $arguments) { + if ($arguments instanceof \stdClass) { + $arguments = (array) $arguments; + } + if (!\is_array($arguments)) { + throw new InvalidArgumentException('Parameter "arguments" must be an array for prompts/get.'); + } + } + + return new self($params['name'], $arguments); + } + + /** + * @return array{name: string, arguments?: array} + */ + protected function getParams(): array + { + $params = ['name' => $this->name]; + + if (null !== $this->arguments) { + $params['arguments'] = $this->arguments; + } + + return $params; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/InitializeRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/InitializeRequest.php new file mode 100644 index 0000000..f7b7460 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/InitializeRequest.php @@ -0,0 +1,73 @@ + + */ +final class InitializeRequest extends Request +{ + /** + * @param string $protocolVersion The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. + * @param ClientCapabilities $capabilities the capabilities of the client + * @param Implementation $clientInfo information about the client + */ + public function __construct( + public readonly string $protocolVersion, + public readonly ClientCapabilities $capabilities, + public readonly Implementation $clientInfo, + ) { + } + + public static function getMethod(): string + { + return 'initialize'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['protocolVersion']) || !\is_string($params['protocolVersion'])) { + throw new InvalidArgumentException('Missing or invalid "protocolVersion" parameter for initialize.'); + } + + if (!isset($params['capabilities']) || !\is_array($params['capabilities'])) { + throw new InvalidArgumentException('Missing or invalid "capabilities" parameter for initialize.'); + } + $capabilities = ClientCapabilities::fromArray($params['capabilities']); + + if (!isset($params['clientInfo']) || !\is_array($params['clientInfo'])) { + throw new InvalidArgumentException('Missing or invalid "clientInfo" parameter for initialize.'); + } + $clientInfo = Implementation::fromArray($params['clientInfo']); + + return new self($params['protocolVersion'], $capabilities, $clientInfo); + } + + /** + * @return array{protocolVersion: string, capabilities: ClientCapabilities, clientInfo: Implementation} + */ + protected function getParams(): array + { + return [ + 'protocolVersion' => $this->protocolVersion, + 'capabilities' => $this->capabilities, + 'clientInfo' => $this->clientInfo, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ListPromptsRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ListPromptsRequest.php new file mode 100644 index 0000000..3f1cff0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ListPromptsRequest.php @@ -0,0 +1,60 @@ + + */ +final class ListPromptsRequest extends Request +{ + /** + * If provided, the server should return results starting after this cursor. + * + * @param string|null $cursor an opaque token representing the current pagination position + */ + public function __construct( + public readonly ?string $cursor = null, + ) { + } + + public static function getMethod(): string + { + return 'prompts/list'; + } + + protected static function fromParams(?array $params): static + { + if (isset($params['cursor']) && !\is_string($params['cursor'])) { + throw new InvalidArgumentException('Invalid "cursor" parameter for prompts/list.'); + } + + return new self($params['cursor'] ?? null); + } + + /** + * @return array{cursor:string}|null + */ + protected function getParams(): ?array + { + $params = []; + if (null !== $this->cursor) { + $params['cursor'] = $this->cursor; + } + + return $params ?: null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ListResourceTemplatesRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ListResourceTemplatesRequest.php new file mode 100644 index 0000000..1822c57 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ListResourceTemplatesRequest.php @@ -0,0 +1,60 @@ + + */ +final class ListResourceTemplatesRequest extends Request +{ + /** + * @param string|null $cursor An opaque token representing the current pagination position. + * + * If provided, the server should return results starting after this cursor. + */ + public function __construct( + public readonly ?string $cursor = null, + ) { + } + + public static function getMethod(): string + { + return 'resources/templates/list'; + } + + protected static function fromParams(?array $params): static + { + if (isset($params['cursor']) && !\is_string($params['cursor'])) { + throw new InvalidArgumentException('Invalid "cursor" parameter for resources/templates/list.'); + } + + return new self($params['cursor'] ?? null); + } + + /** + * @return array{cursor:string}|null + */ + protected function getParams(): ?array + { + $params = []; + if (null !== $this->cursor) { + $params['cursor'] = $this->cursor; + } + + return $params ?: null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ListResourcesRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ListResourcesRequest.php new file mode 100644 index 0000000..a8fe772 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ListResourcesRequest.php @@ -0,0 +1,60 @@ + + */ +final class ListResourcesRequest extends Request +{ + /** + * @param string|null $cursor An opaque token representing the current pagination position. + * + * If provided, the server should return results starting after this cursor. + */ + public function __construct( + public readonly ?string $cursor = null, + ) { + } + + public static function getMethod(): string + { + return 'resources/list'; + } + + protected static function fromParams(?array $params): static + { + if (isset($params['cursor']) && !\is_string($params['cursor'])) { + throw new InvalidArgumentException('Invalid "cursor" parameter for resources/list.'); + } + + return new self($params['cursor'] ?? null); + } + + /** + * @return array{cursor:string}|null + */ + protected function getParams(): ?array + { + $params = []; + if (null !== $this->cursor) { + $params['cursor'] = $this->cursor; + } + + return $params ?: null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ListRootsRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ListRootsRequest.php new file mode 100644 index 0000000..ea5987f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ListRootsRequest.php @@ -0,0 +1,51 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +final class ListRootsRequest extends Request +{ + public function __construct( + ) { + } + + public static function getMethod(): string + { + return 'roots/list'; + } + + protected static function fromParams(?array $params): static + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ListToolsRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ListToolsRequest.php new file mode 100644 index 0000000..83cf887 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ListToolsRequest.php @@ -0,0 +1,60 @@ + + */ +final class ListToolsRequest extends Request +{ + /** + * @param string|null $cursor An opaque token representing the current pagination position. + * + * If provided, the server should return results starting after this cursor. + */ + public function __construct( + public readonly ?string $cursor = null, + ) { + } + + public static function getMethod(): string + { + return 'tools/list'; + } + + protected static function fromParams(?array $params): static + { + if (isset($params['cursor']) && !\is_string($params['cursor'])) { + throw new InvalidArgumentException('Invalid "cursor" parameter for tools/list.'); + } + + return new self($params['cursor'] ?? null); + } + + /** + * @return array{cursor:string}|null + */ + protected function getParams(): ?array + { + $params = []; + if (null !== $this->cursor) { + $params['cursor'] = $this->cursor; + } + + return $params ?: null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/PingRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/PingRequest.php new file mode 100644 index 0000000..31fe64b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/PingRequest.php @@ -0,0 +1,38 @@ + + */ +final class PingRequest extends Request +{ + public static function getMethod(): string + { + return 'ping'; + } + + protected static function fromParams(?array $params): static + { + return new self(); + } + + protected function getParams(): ?array + { + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ReadResourceRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ReadResourceRequest.php new file mode 100644 index 0000000..69523ef --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ReadResourceRequest.php @@ -0,0 +1,55 @@ + + */ +final class ReadResourceRequest extends Request +{ + /** + * @param non-empty-string $uri the URI of the resource to read + */ + public function __construct( + public readonly string $uri, + ) { + } + + public static function getMethod(): string + { + return 'resources/read'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['uri']) || !\is_string($params['uri']) || empty($params['uri'])) { + throw new InvalidArgumentException('Missing or invalid "uri" parameter for resources/read.'); + } + + return new self($params['uri']); + } + + /** + * @return array{uri: non-empty-string} + */ + protected function getParams(): array + { + return [ + 'uri' => $this->uri, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ResourceSubscribeRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ResourceSubscribeRequest.php new file mode 100644 index 0000000..036785a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ResourceSubscribeRequest.php @@ -0,0 +1,54 @@ + + */ +final class ResourceSubscribeRequest extends Request +{ + /** + * @param non-empty-string $uri the URI of the resource to subscribe to + */ + public function __construct( + public readonly string $uri, + ) { + } + + public static function getMethod(): string + { + return 'resources/subscribe'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['uri']) || !\is_string($params['uri']) || empty($params['uri'])) { + throw new InvalidArgumentException('Missing or invalid "uri" parameter for resources/subscribe.'); + } + + return new self($params['uri']); + } + + /** + * @return array{uri: non-empty-string} + */ + protected function getParams(): array + { + return ['uri' => $this->uri]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/ResourceUnsubscribeRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/ResourceUnsubscribeRequest.php new file mode 100644 index 0000000..fd93727 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/ResourceUnsubscribeRequest.php @@ -0,0 +1,54 @@ + + */ +final class ResourceUnsubscribeRequest extends Request +{ + /** + * @param non-empty-string $uri the URI of the resource to unsubscribe from + */ + public function __construct( + public readonly string $uri, + ) { + } + + public static function getMethod(): string + { + return 'resources/unsubscribe'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['uri']) || !\is_string($params['uri']) || empty($params['uri'])) { + throw new InvalidArgumentException('Missing or invalid "uri" parameter for resources/unsubscribe.'); + } + + return new self($params['uri']); + } + + /** + * @return array{uri: non-empty-string} + */ + protected function getParams(): array + { + return ['uri' => $this->uri]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Request/SetLogLevelRequest.php b/admin/vendor/mcp/sdk/src/Schema/Request/SetLogLevelRequest.php new file mode 100644 index 0000000..f781ca5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Request/SetLogLevelRequest.php @@ -0,0 +1,65 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +final class SetLogLevelRequest extends Request +{ + /** + * @param LoggingLevel $level The level of logging that the client wants to receive from the server. The server + * should send all logs at this level and higher (i.e., more severe) to the client as + * notifications/message. + */ + public function __construct( + public readonly LoggingLevel $level, + ) { + } + + public static function getMethod(): string + { + return 'logging/setLevel'; + } + + protected static function fromParams(?array $params): static + { + if (!isset($params['level']) || !\is_string($params['level']) || '' === $params['level']) { + throw new InvalidArgumentException('Missing or invalid "level" parameter for "logging/setLevel".'); + } + + if (null === $level = LoggingLevel::tryFrom($params['level'])) { + throw new InvalidArgumentException(\sprintf('Invalid "level" parameter "%s" for "logging/setLevel".', $params['level'])); + } + + return new self($level); + } + + /** + * @return array{level: value-of} + */ + protected function getParams(): array + { + return [ + 'level' => $this->level->value, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ResourceDefinition.php b/admin/vendor/mcp/sdk/src/Schema/ResourceDefinition.php new file mode 100644 index 0000000..69c44d5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ResourceDefinition.php @@ -0,0 +1,152 @@ +, + * } + * + * @author Kyrian Obikwelu + */ +class ResourceDefinition implements \JsonSerializable +{ + /** + * URI pattern regex - requires a valid scheme followed by colon and optional path (RFC 3986). + * Example patterns: file://path, db://table, urn:isbn:123, config:key, etc. + */ + private const URI_PATTERN = '/^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/'; + + /** + * @param string $uri the URI of this resource + * @param string $name a short identifier for this resource + * @param ?string $title optional human-readable title for display in UI + * @param ?string $description A description of what this resource represents. This can be used by clients to improve the LLM's understanding of available resources. + * @param ?string $mimeType the MIME type of this resource, if known + * @param ?Annotations $annotations optional annotations for the client + * @param ?int $size the size of the raw resource content, in bytes (before base64 encoding or any tokenization), if known + * @param ?Icon[] $icons optional icons representing the resource + * @param ?array $meta optional metadata + */ + public function __construct( + public readonly string $uri, + public readonly string $name, + public readonly ?string $title = null, + public readonly ?string $description = null, + public readonly ?string $mimeType = null, + public readonly ?Annotations $annotations = null, + public readonly ?int $size = null, + public readonly ?array $icons = null, + public readonly ?array $meta = null, + ) { + if (!preg_match(self::URI_PATTERN, $uri)) { + throw new InvalidArgumentException(\sprintf('Invalid resource URI: "%s" must be a valid URI with a scheme and optional path.', $uri)); + } + } + + /** + * @param ResourceDefinitionData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['uri']) || !\is_string($data['uri'])) { + throw new InvalidArgumentException('Invalid or missing "uri" in ResourceDefinition data.'); + } + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in ResourceDefinition data.'); + } + + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in ResourceDefinition data.'); + } + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in ResourceDefinition data.'); + } + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" in ResourceDefinition data.'); + } + if (isset($data['size']) && !\is_int($data['size'])) { + throw new InvalidArgumentException('Invalid "size" in ResourceDefinition data; expected an integer.'); + } + + return new self( + uri: $data['uri'], + name: $data['name'], + title: isset($data['title']) && \is_string($data['title']) ? $data['title'] : null, + description: $data['description'] ?? null, + mimeType: $data['mimeType'] ?? null, + annotations: Annotations::tryFromArray($data['annotations'] ?? null, 'ResourceDefinition'), + size: $data['size'] ?? null, + icons: isset($data['icons']) && \is_array($data['icons']) ? Icon::listFromArray($data['icons'], 'ResourceDefinition') : null, + meta: isset($data['_meta']) ? $data['_meta'] : null + ); + } + + /** + * @return array{ + * uri: string, + * name: string, + * title?: string, + * description?: string, + * mimeType?: string, + * annotations?: Annotations, + * size?: int, + * icons?: Icon[], + * _meta?: array + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'uri' => $this->uri, + 'name' => $this->name, + ]; + if (null !== $this->title) { + $data['title'] = $this->title; + } + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->mimeType) { + $data['mimeType'] = $this->mimeType; + } + if (null !== $this->annotations) { + $data['annotations'] = $this->annotations; + } + if (null !== $this->size) { + $data['size'] = $this->size; + } + if (null !== $this->icons) { + $data['icons'] = $this->icons; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ResourceReference.php b/admin/vendor/mcp/sdk/src/Schema/ResourceReference.php new file mode 100644 index 0000000..c861c7e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ResourceReference.php @@ -0,0 +1,44 @@ + + */ +class ResourceReference implements \JsonSerializable +{ + public string $type = 'ref/resource'; + + /** + * @param string $uri the URI or URI template of the resource + */ + public function __construct( + public readonly string $uri, + ) { + } + + /** + * @return array{ + * type: string, + * uri: string, + * } + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'uri' => $this->uri, + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ResourceTemplate.php b/admin/vendor/mcp/sdk/src/Schema/ResourceTemplate.php new file mode 100644 index 0000000..4603397 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ResourceTemplate.php @@ -0,0 +1,132 @@ + + * } + * + * @author Kyrian Obikwelu + */ +class ResourceTemplate implements \JsonSerializable +{ + /** + * URI Template pattern regex - requires a valid scheme followed by colon and path with at least one placeholder (RFC 3986). + * Example patterns: file://{path}/contents.txt, db://{table}/{id}, config:{key}, etc. + */ + private const URI_TEMPLATE_PATTERN = '/^[a-zA-Z][a-zA-Z0-9+.-]*:.*{[^{}]+}.*/'; + + /** + * @param string $uriTemplate a URI template (according to RFC 6570) that can be used to construct resource URIs + * @param string $name a short identifier for this resource template type + * @param ?string $title optional human-readable title for display in UI + * @param ?string $description a description to help the LLM understand available resources + * @param ?string $mimeType the MIME type for all resources that match this template, if uniform + * @param ?Annotations $annotations optional annotations for the client + * @param ?array $meta optional metadata + */ + public function __construct( + public readonly string $uriTemplate, + public readonly string $name, + public readonly ?string $title = null, + public readonly ?string $description = null, + public readonly ?string $mimeType = null, + public readonly ?Annotations $annotations = null, + public readonly ?array $meta = null, + ) { + if (!preg_match(self::URI_TEMPLATE_PATTERN, $uriTemplate)) { + throw new InvalidArgumentException(\sprintf('Invalid URI template : "%s" must be a valid URI template with at least one placeholder.', $uriTemplate)); + } + } + + /** + * @param ResourceTemplateData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['uriTemplate']) || !\is_string($data['uriTemplate'])) { + throw new InvalidArgumentException('Invalid or missing "uriTemplate" in ResourceTemplate data.'); + } + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in ResourceTemplate data.'); + } + + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in ResourceTemplate data.'); + } + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in ResourceTemplate data.'); + } + if (isset($data['mimeType']) && !\is_string($data['mimeType'])) { + throw new InvalidArgumentException('Invalid "mimeType" in ResourceTemplate data.'); + } + + return new self( + uriTemplate: $data['uriTemplate'], + name: $data['name'], + title: isset($data['title']) && \is_string($data['title']) ? $data['title'] : null, + description: $data['description'] ?? null, + mimeType: $data['mimeType'] ?? null, + annotations: Annotations::tryFromArray($data['annotations'] ?? null, 'ResourceTemplate'), + meta: isset($data['_meta']) ? $data['_meta'] : null + ); + } + + /** + * @return array{ + * uriTemplate: string, + * name: string, + * title?: string, + * description?: string, + * mimeType?: string, + * annotations?: Annotations, + * _meta?: array + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'uriTemplate' => $this->uriTemplate, + 'name' => $this->name, + ]; + if (null !== $this->title) { + $data['title'] = $this->title; + } + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->mimeType) { + $data['mimeType'] = $this->mimeType; + } + if (null !== $this->annotations) { + $data['annotations'] = $this->annotations; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/CallToolResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/CallToolResult.php new file mode 100644 index 0000000..b986bec --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/CallToolResult.php @@ -0,0 +1,156 @@ + + */ +class CallToolResult implements ResultInterface +{ + /** + * Create a new CallToolResult. + * + * @param Content[] $content The content of the tool result + * @param bool $isError Whether the tool execution resulted in an error. If not set, this is assumed to be false (the call was successful). + * @param mixed $structuredContent Structured result of the call. Any JSON value — object, array, + * string, number, boolean or null — conforming to the tool's + * outputSchema when one is declared. + * @param array|null $meta Optional metadata + */ + public function __construct( + public readonly array $content, + public readonly bool $isError = false, + public readonly mixed $structuredContent = null, + public readonly ?array $meta = null, + ) { + foreach ($this->content as $item) { + if (!$item instanceof Content) { + throw new InvalidArgumentException('Content must be an array of Content objects.'); + } + } + } + + /** + * Create a new CallToolResult with success status. + * + * @param Content[] $content The content of the tool result + * @param array|null $meta Optional metadata + */ + public static function success(array $content, ?array $meta = null): self + { + return new self($content, false, null, $meta); + } + + /** + * Create a new CallToolResult with error status. + * + * @param Content[] $content The content of the tool result + * @param array|null $meta Optional metadata + */ + public static function error(array $content, ?array $meta = null): self + { + return new self($content, true, null, $meta); + } + + /** + * @param array{ + * content: array, + * isError?: bool, + * _meta?: array, + * structuredContent?: mixed + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['content']) || !\is_array($data['content'])) { + throw new InvalidArgumentException('Missing or invalid "content" array in CallToolResult data.'); + } + + $contents = []; + + foreach ($data['content'] as $item) { + $type = \is_array($item) ? $item['type'] ?? null : null; + if (!\is_string($type)) { + throw new InvalidArgumentException('Missing or invalid content "type" in CallToolResult data.'); + } + + $contents[] = match ($type) { + 'text' => TextContent::fromArray($item), + 'image' => ImageContent::fromArray($item), + 'audio' => AudioContent::fromArray($item), + 'resource' => EmbeddedResource::fromArray($item), + 'resource_link' => ResourceLink::fromArray($item), + default => throw new InvalidArgumentException(\sprintf('Invalid content type in CallToolResult data: "%s".', $type)), + }; + } + + if (isset($data['isError']) && !\is_bool($data['isError'])) { + throw new InvalidArgumentException('Invalid "isError" in CallToolResult data.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in CallToolResult data.'); + } + + return new self( + $contents, + $data['isError'] ?? false, + $data['structuredContent'] ?? null, + $data['_meta'] ?? null + ); + } + + /** + * @return array{ + * content: array, + * isError: bool, + * structuredContent?: mixed, + * _meta?: array, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'content' => $this->content, + 'isError' => $this->isError, + ]; + + if (null !== $this->structuredContent) { + $result['structuredContent'] = $this->structuredContent; + } + + if ($this->meta) { + $result['_meta'] = $this->meta; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/CompletionCompleteResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/CompletionCompleteResult.php new file mode 100644 index 0000000..43c9fed --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/CompletionCompleteResult.php @@ -0,0 +1,89 @@ + + */ +class CompletionCompleteResult implements ResultInterface +{ + /** + * @param string[] $values An array of completion values. Must not exceed 100 items. + * @param int|null $total The total number of completion options available. This can exceed the number of values actually sent in the response. + * @param bool|null $hasMore indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown + */ + public function __construct( + public readonly array $values, + public readonly ?int $total = null, + public readonly ?bool $hasMore = null, + ) { + if (\count($this->values) > 100) { + throw new InvalidArgumentException('Values must not exceed 100 items'); + } + } + + /** + * @return array{ + * completion: array{ + * values: string[], + * total?: int, + * hasMore?: bool, + * } + * } + */ + public function jsonSerialize(): array + { + $completion = [ + 'values' => $this->values, + ]; + + if (null !== $this->total) { + $completion['total'] = $this->total; + } + if (null !== $this->hasMore) { + $completion['hasMore'] = $this->hasMore; + } + + return ['completion' => $completion]; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + $completion = $data['completion'] ?? []; + if (!\is_array($completion)) { + throw new InvalidArgumentException('Invalid "completion" in CompletionCompleteResult data.'); + } + if (isset($completion['values']) && !\is_array($completion['values'])) { + throw new InvalidArgumentException('Invalid "completion.values" in CompletionCompleteResult data.'); + } + if (isset($completion['total']) && !\is_int($completion['total'])) { + throw new InvalidArgumentException('Invalid "completion.total" in CompletionCompleteResult data.'); + } + if (isset($completion['hasMore']) && !\is_bool($completion['hasMore'])) { + throw new InvalidArgumentException('Invalid "completion.hasMore" in CompletionCompleteResult data.'); + } + + return new self( + $completion['values'] ?? [], + $completion['total'] ?? null, + $completion['hasMore'] ?? null, + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/CreateSamplingMessageResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/CreateSamplingMessageResult.php new file mode 100644 index 0000000..a325ed9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/CreateSamplingMessageResult.php @@ -0,0 +1,177 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ +class CreateSamplingMessageResult implements ResultInterface +{ + /** + * @var TextContent|ImageContent|AudioContent|ToolUseContent|list + */ + public readonly TextContent|ImageContent|AudioContent|ToolUseContent|array $content; + + /** + * @param Role $role the role of the message + * @param TextContent|ImageContent|AudioContent|ToolUseContent|array $content The content of the message. Keys are discarded, the property always holds a list. + * @param string $model the name of the model that generated the message + * @param ?string $stopReason The reason why sampling stopped, if known. The spec defines "endTurn", + * "stopSequence", "maxTokens" and "toolUse", but leaves the set open for + * provider-specific values, so this stays an unconstrained string. + * @param ?array $meta optional message metadata + */ + public function __construct( + public readonly Role $role, + TextContent|ImageContent|AudioContent|ToolUseContent|array $content, + public readonly string $model, + public readonly ?string $stopReason = null, + public readonly ?array $meta = null, + ) { + if (Role::Assistant !== $role) { + throw new InvalidArgumentException('CreateSamplingMessageResult role must be "assistant".'); + } + + if (\is_array($content)) { + if ([] === $content) { + throw new InvalidArgumentException('CreateSamplingMessageResult content must not be empty.'); + } + + foreach ($content as $item) { + if (!$item instanceof TextContent && !$item instanceof ImageContent && !$item instanceof AudioContent && !$item instanceof ToolUseContent) { + throw new InvalidArgumentException('CreateSamplingMessageResult contains an unsupported content block.'); + } + } + + // array_filter() and friends preserve keys, and a keyed array serializes + // as a JSON object rather than the array the schema requires. + $content = array_values($content); + } + + $this->content = $content; + } + + /** + * @return list + */ + public function getContentBlocks(): array + { + return \is_array($this->content) ? $this->content : [$this->content]; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['role']) || !\is_string($data['role'])) { + throw new InvalidArgumentException('Missing or invalid "role" in CreateSamplingMessageResult data.'); + } + + if (!isset($data['content']) || !\is_array($data['content']) || [] === $data['content']) { + throw new InvalidArgumentException('Missing or invalid "content" in CreateSamplingMessageResult data.'); + } + + if (!isset($data['model']) || !\is_string($data['model'])) { + throw new InvalidArgumentException('Missing or invalid "model" in CreateSamplingMessageResult data.'); + } + + if (null === $role = Role::tryFrom($data['role'])) { + throw new InvalidArgumentException(\sprintf('Invalid "role" value "%s" in CreateSamplingMessageResult data.', $data['role'])); + } + + $contentPayload = $data['content']; + + $isSingleContent = isset($contentPayload['type']); + $contentItems = $isSingleContent ? [$contentPayload] : $contentPayload; + $content = []; + foreach ($contentItems as $item) { + if (!\is_array($item)) { + throw new InvalidArgumentException('Invalid content block in CreateSamplingMessageResult data.'); + } + $content[] = self::hydrateContent($item); + } + + $stopReason = isset($data['stopReason']) && \is_string($data['stopReason']) ? $data['stopReason'] : null; + + return new self( + $role, + $isSingleContent ? $content[0] : $content, + $data['model'], + $stopReason, + isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null, + ); + } + + /** + * @param array $contentData + */ + private static function hydrateContent(array $contentData): TextContent|ImageContent|AudioContent|ToolUseContent + { + $type = $contentData['type'] ?? null; + + if (!\is_string($type)) { + throw new InvalidArgumentException('Missing or invalid "type" in sampling content payload.'); + } + + return match ($type) { + 'text' => TextContent::fromArray($contentData), + 'image' => ImageContent::fromArray($contentData), + 'audio' => AudioContent::fromArray($contentData), + 'tool_use' => ToolUseContent::fromArray($contentData), + default => throw new InvalidArgumentException(\sprintf('Unsupported sampling content type "%s".', $type)), + }; + } + + /** + * @return array{ + * role: string, + * content: TextContent|ImageContent|AudioContent|ToolUseContent|list, + * model: string, + * stopReason?: string, + * _meta?: array, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'role' => $this->role->value, + 'content' => $this->content, + 'model' => $this->model, + ]; + + if (null !== $this->stopReason) { + $result['stopReason'] = $this->stopReason; + } + + if (null !== $this->meta) { + $result['_meta'] = $this->meta; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/DiscoverResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/DiscoverResult.php new file mode 100644 index 0000000..22222e9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/DiscoverResult.php @@ -0,0 +1,70 @@ + + */ +class DiscoverResult implements ResultInterface +{ + /** + * @param list $supportedVersions every revision this server can answer + */ + public function __construct( + public readonly array $supportedVersions, + public readonly ServerCapabilities $capabilities, + public readonly ?string $instructions = null, + ) { + if ([] === $this->supportedVersions) { + throw new InvalidArgumentException('A DiscoverResult must advertise at least one supported version.'); + } + } + + /** + * `resultType`, caching hints and serverInfo are wire vocabulary, stamped + * by {@see \Mcp\Server\Wire\Rev2026Codec} rather than modelled here. + * + * @return array{ + * supportedVersions: list, + * capabilities: ServerCapabilities, + * instructions?: string, + * } + */ + public function jsonSerialize(): array + { + $data = [ + 'supportedVersions' => array_values(array_map( + static fn (ProtocolVersion $version): string => $version->value, + $this->supportedVersions, + )), + 'capabilities' => $this->capabilities, + ]; + + if (null !== $this->instructions) { + $data['instructions'] = $this->instructions; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ElicitResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ElicitResult.php new file mode 100644 index 0000000..533d28f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ElicitResult.php @@ -0,0 +1,100 @@ + + */ +final class ElicitResult implements ResultInterface +{ + /** + * @param ElicitAction $action The user's action in response to the elicitation + * @param array|null $content The content provided by the user (only present when action is "accept") + */ + public function __construct( + public readonly ElicitAction $action, + public readonly ?array $content = null, + ) { + } + + /** + * The result carries no discriminator of its own, so the mode of the request it + * answers has to be passed in: it decides whether an accepted response is + * expected to carry content. + * + * @param array{action: string, content?: array} $data + */ + public static function fromArray(array $data, ElicitationMode $mode = ElicitationMode::Form): self + { + if (!isset($data['action']) || !\is_string($data['action'])) { + throw new InvalidArgumentException('Missing or invalid "action" in ElicitResult data.'); + } + + if (null === $action = ElicitAction::tryFrom($data['action'])) { + throw new InvalidArgumentException(\sprintf('Invalid "action" value "%s" in ElicitResult data.', $data['action'])); + } + + $content = isset($data['content']) && \is_array($data['content']) ? $data['content'] : null; + + if (ElicitationMode::Form === $mode) { + if (ElicitAction::Accept === $action && null === $content) { + throw new InvalidArgumentException('Content must be provided when action is "accept".'); + } + } elseif (isset($data['content'])) { + throw new InvalidArgumentException('Content must not be provided for a url-mode elicitation result.'); + } + + return new self($action, $content); + } + + public function isAccepted(): bool + { + return ElicitAction::Accept === $this->action; + } + + public function isDeclined(): bool + { + return ElicitAction::Decline === $this->action; + } + + public function isCancelled(): bool + { + return ElicitAction::Cancel === $this->action; + } + + /** + * @return array{action: string, content?: array} + */ + public function jsonSerialize(): array + { + $result = [ + 'action' => $this->action->value, + ]; + + if (null !== $this->content) { + $result['content'] = $this->content; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/EmptyResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/EmptyResult.php new file mode 100644 index 0000000..2641698 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/EmptyResult.php @@ -0,0 +1,39 @@ + + */ +class EmptyResult implements ResultInterface +{ + /** + * Create a new EmptyResult. + */ + public function __construct() + { + } + + public static function fromArray(): self + { + return new self(); + } + + public function jsonSerialize(): object + { + return new \stdClass(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/GetPromptResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/GetPromptResult.php new file mode 100644 index 0000000..0083a83 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/GetPromptResult.php @@ -0,0 +1,88 @@ + + */ +class GetPromptResult implements ResultInterface +{ + /** + * Create a new GetPromptResult. + * + * @param PromptMessage[] $messages The messages in the prompt + * @param string|null $description Optional description of the prompt + */ + public function __construct( + public readonly array $messages, + public readonly ?string $description = null, + ) { + foreach ($this->messages as $message) { + if (!$message instanceof PromptMessage) { + throw new InvalidArgumentException('Messages must be an array of PromptMessage objects.'); + } + } + } + + /** + * @param array{ + * messages: array, + * description?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['messages']) || !\is_array($data['messages'])) { + throw new InvalidArgumentException('Missing or invalid "messages" array in GetPromptResult data.'); + } + + if (isset($data['description']) && !\is_string($data['description'])) { + throw new InvalidArgumentException('Invalid "description" in GetPromptResult data.'); + } + + $messages = []; + foreach ($data['messages'] as $message) { + if (!\is_array($message)) { + throw new InvalidArgumentException('Each entry in "messages" of GetPromptResult data must be an array.'); + } + + $messages[] = PromptMessage::fromArray($message); + } + + return new self($messages, $data['description'] ?? null); + } + + /** + * @return array{ + * messages: array, + * description?: string, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'messages' => $this->messages, + ]; + + if (null !== $this->description) { + $result['description'] = $this->description; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/InitializeResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/InitializeResult.php new file mode 100644 index 0000000..e80c28c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/InitializeResult.php @@ -0,0 +1,109 @@ + + */ +class InitializeResult implements ResultInterface +{ + /** + * Create a new InitializeResult. + * + * @param ServerCapabilities $capabilities the capabilities of the server + * @param Implementation $serverInfo information about the server + * @param string|null $instructions Instructions describing how to use the server and its features. This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + * @param array|null $meta optional _meta field + */ + public function __construct( + public readonly ServerCapabilities $capabilities, + public readonly Implementation $serverInfo, + public readonly ?string $instructions = null, + public readonly ?array $meta = null, + public readonly ?ProtocolVersion $protocolVersion = null, + ) { + } + + /** + * @param array{ + * protocolVersion: string, + * capabilities: array, + * serverInfo: array, + * instructions?: string, + * _meta?: array, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['protocolVersion']) || !\is_string($data['protocolVersion'])) { + throw new InvalidArgumentException('Missing or invalid "protocolVersion".'); + } + if (!isset($data['capabilities']) || !\is_array($data['capabilities'])) { + throw new InvalidArgumentException('Missing or invalid "capabilities".'); + } + if (!isset($data['serverInfo']) || !\is_array($data['serverInfo'])) { + throw new InvalidArgumentException('Missing or invalid "serverInfo".'); + } + + if (isset($data['instructions']) && !\is_string($data['instructions'])) { + throw new InvalidArgumentException('Invalid "instructions" in InitializeResult data.'); + } + if (isset($data['_meta']) && !\is_array($data['_meta'])) { + throw new InvalidArgumentException('Invalid "_meta" in InitializeResult data.'); + } + + return new self( + ServerCapabilities::fromArray($data['capabilities']), + Implementation::fromArray($data['serverInfo']), + $data['instructions'] ?? null, + $data['_meta'] ?? null, + ProtocolVersion::tryFrom($data['protocolVersion']), + ); + } + + /** + * @return array{ + * protocolVersion: string, + * capabilities: ServerCapabilities, + * serverInfo: Implementation, + * instructions?: string, + * _meta?: array, + * } + */ + public function jsonSerialize(): array + { + $protocolVersion = $this->protocolVersion ?? MessageInterface::PROTOCOL_VERSION; + $data = [ + 'protocolVersion' => $protocolVersion->value, + 'capabilities' => $this->capabilities, + 'serverInfo' => $this->serverInfo, + ]; + if (null !== $this->instructions) { + $data['instructions'] = $this->instructions; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/InputRequiredResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/InputRequiredResult.php new file mode 100644 index 0000000..a0fca5e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/InputRequiredResult.php @@ -0,0 +1,86 @@ + + */ +class InputRequiredResult implements ResultInterface +{ + public const RESULT_TYPE = 'input_required'; + + /** + * @param array $inputRequests server-assigned keys, unique within this request + * @param string|null $requestState opaque server context the client echoes back + */ + public function __construct( + public readonly array $inputRequests = [], + public readonly ?string $requestState = null, + ) { + // Neither member would tell the client to retry with nothing new. + if ([] === $this->inputRequests && null === $this->requestState) { + throw new InvalidArgumentException('An InputRequiredResult must carry at least one of "inputRequests" or "requestState".'); + } + } + + /** + * @return array{ + * resultType: string, + * inputRequests?: array, + * requestState?: string, + * } + */ + public function jsonSerialize(): array + { + $data = ['resultType' => self::RESULT_TYPE]; + + if ([] !== $this->inputRequests) { + $requests = []; + foreach ($this->inputRequests as $key => $request) { + // Values are bare method/params pairs, not messages: the client + // keys answers by the map key. getParams() is protected, so the + // envelope is built with a throwaway id and then discarded. + $envelope = $request->withId(0)->jsonSerialize(); + + $params = $envelope['params'] ?? null; + + $requests[$key] = [ + 'method' => $request::getMethod(), + // An empty PHP array would encode as `[]`, not `{}`. + 'params' => [] === $params || null === $params ? new \stdClass() : $params, + ]; + } + $data['inputRequests'] = $requests; + } + + if (null !== $this->requestState) { + $data['requestState'] = $this->requestState; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ListPromptsResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ListPromptsResult.php new file mode 100644 index 0000000..fd164c4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ListPromptsResult.php @@ -0,0 +1,89 @@ + + */ +class ListPromptsResult implements ResultInterface +{ + /** + * @param array $prompts the list of prompt definitions + * @param string|null $nextCursor An opaque token representing the pagination position after the last returned result. + * + * If present, there may be more results available. + */ + public function __construct( + public readonly array $prompts, + public readonly ?string $nextCursor = null, + ) { + } + + /** + * @param array{ + * prompts: array, + * nextCursor?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['prompts']) || !\is_array($data['prompts'])) { + throw new InvalidArgumentException('Missing or invalid "prompts" array in ListPromptsResult data.'); + } + + if (isset($data['nextCursor']) && !\is_string($data['nextCursor'])) { + throw new InvalidArgumentException('Invalid "nextCursor" in ListPromptsResult data.'); + } + + return new self( + array_map( + static function (mixed $entry): Prompt { + if (!\is_array($entry)) { + throw new InvalidArgumentException('Each entry in "prompts" of ListPromptsResult data must be an array.'); + } + + return Prompt::fromArray($entry); + }, + $data['prompts'], + ), + $data['nextCursor'] ?? null + ); + } + + /** + * @return array{ + * prompts: array, + * nextCursor?: string, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'prompts' => array_values($this->prompts), + ]; + + if ($this->nextCursor) { + $result['nextCursor'] = $this->nextCursor; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ListResourceTemplatesResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ListResourceTemplatesResult.php new file mode 100644 index 0000000..e0b7114 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ListResourceTemplatesResult.php @@ -0,0 +1,89 @@ + + */ +class ListResourceTemplatesResult implements ResultInterface +{ + /** + * @param array $resourceTemplates the list of resource template definitions + * @param string|null $nextCursor An opaque token representing the pagination position after the last returned result. + * + * If present, there may be more results available. + */ + public function __construct( + public readonly array $resourceTemplates, + public readonly ?string $nextCursor = null, + ) { + } + + /** + * @param array{ + * resourceTemplates: array, + * nextCursor?: string + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['resourceTemplates']) || !\is_array($data['resourceTemplates'])) { + throw new InvalidArgumentException('Missing or invalid "resourceTemplates" array in ListResourceTemplatesResult data.'); + } + + if (isset($data['nextCursor']) && !\is_string($data['nextCursor'])) { + throw new InvalidArgumentException('Invalid "nextCursor" in ListResourceTemplatesResult data.'); + } + + return new self( + array_map( + static function (mixed $entry): ResourceTemplate { + if (!\is_array($entry)) { + throw new InvalidArgumentException('Each entry in "resourceTemplates" of ListResourceTemplatesResult data must be an array.'); + } + + return ResourceTemplate::fromArray($entry); + }, + $data['resourceTemplates'], + ), + $data['nextCursor'] ?? null + ); + } + + /** + * @return array{ + * resourceTemplates: array, + * nextCursor?: string, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'resourceTemplates' => array_values($this->resourceTemplates), + ]; + + if ($this->nextCursor) { + $result['nextCursor'] = $this->nextCursor; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ListResourcesResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ListResourcesResult.php new file mode 100644 index 0000000..63066b0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ListResourcesResult.php @@ -0,0 +1,89 @@ + + */ +class ListResourcesResult implements ResultInterface +{ + /** + * @param array $resources the list of resource definitions + * @param string|null $nextCursor An opaque token representing the pagination position after the last returned result. + * + * If present, there may be more results available. + */ + public function __construct( + public readonly array $resources, + public readonly ?string $nextCursor = null, + ) { + } + + /** + * @param array{ + * resources: array, + * nextCursor?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['resources']) || !\is_array($data['resources'])) { + throw new InvalidArgumentException('Missing or invalid "resources" array in ListResourcesResult data.'); + } + + if (isset($data['nextCursor']) && !\is_string($data['nextCursor'])) { + throw new InvalidArgumentException('Invalid "nextCursor" in ListResourcesResult data.'); + } + + return new self( + array_map( + static function (mixed $entry): ResourceDefinition { + if (!\is_array($entry)) { + throw new InvalidArgumentException('Each entry in "resources" of ListResourcesResult data must be an array.'); + } + + return ResourceDefinition::fromArray($entry); + }, + $data['resources'], + ), + $data['nextCursor'] ?? null + ); + } + + /** + * @return array{ + * resources: array, + * nextCursor?: string, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'resources' => array_values($this->resources), + ]; + + if (null !== $this->nextCursor) { + $result['nextCursor'] = $this->nextCursor; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ListRootsResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ListRootsResult.php new file mode 100644 index 0000000..613b53f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ListRootsResult.php @@ -0,0 +1,85 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +class ListRootsResult implements ResultInterface +{ + /** + * @param Root[] $roots an array of root URIs + * @param ?array $meta optional metadata about the result + */ + public function __construct( + public readonly array $roots, + public readonly ?array $meta = null, + ) { + } + + /** + * @param array{ + * roots: array, + * _meta?: ?array + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['roots']) || !\is_array($data['roots'])) { + throw new InvalidArgumentException('Missing or invalid "roots" in ListRootsResult data.'); + } + + $roots = []; + foreach ($data['roots'] as $root) { + if (!\is_array($root)) { + throw new InvalidArgumentException('Invalid root in ListRootsResult data, expected an array.'); + } + + $roots[] = Root::fromArray($root); + } + + $meta = isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null; + + return new self($roots, $meta); + } + + /** + * @return array{ + * roots: Root[], + * _meta?: ?array + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'roots' => array_values($this->roots), + ]; + + if (null !== $this->meta) { + $result['_meta'] = $this->meta; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ListToolsResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ListToolsResult.php new file mode 100644 index 0000000..c6ba6c8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ListToolsResult.php @@ -0,0 +1,89 @@ + + */ +class ListToolsResult implements ResultInterface +{ + /** + * @param array $tools the list of tool definitions + * @param string|null $nextCursor An opaque token representing the pagination position after the last returned result. + * + * If present, there may be more results available. + */ + public function __construct( + public readonly array $tools, + public readonly ?string $nextCursor = null, + ) { + } + + /** + * @param array{ + * tools: array, + * nextCursor?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['tools']) || !\is_array($data['tools'])) { + throw new InvalidArgumentException('Missing or invalid "tools" array in ListToolsResult data.'); + } + + if (isset($data['nextCursor']) && !\is_string($data['nextCursor'])) { + throw new InvalidArgumentException('Invalid "nextCursor" in ListToolsResult data.'); + } + + return new self( + array_map( + static function (mixed $entry): Tool { + if (!\is_array($entry)) { + throw new InvalidArgumentException('Each entry in "tools" of ListToolsResult data must be an array.'); + } + + return Tool::fromArray($entry); + }, + $data['tools'], + ), + $data['nextCursor'] ?? null + ); + } + + /** + * @return array{ + * tools: array, + * nextCursor?: string, + * } + */ + public function jsonSerialize(): array + { + $result = [ + 'tools' => array_values($this->tools), + ]; + + if ($this->nextCursor) { + $result['nextCursor'] = $this->nextCursor; + } + + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Result/ReadResourceResult.php b/admin/vendor/mcp/sdk/src/Schema/Result/ReadResourceResult.php new file mode 100644 index 0000000..7c6d0c5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Result/ReadResourceResult.php @@ -0,0 +1,103 @@ + + */ +class ReadResourceResult implements ResultInterface +{ + /** + * Create a new ReadResourceResult. + * + * @param ResourceContents[] $contents The contents of the resource + * @param ?int $ttlMs How long a client may consider this fresh, in milliseconds. Null + * leaves it to the server's configured {@see \Mcp\Server\Wire\CachePolicy}; + * set it when one resource's freshness differs from the rest. + * @param ?CacheScope $cacheScope Who may cache it. Null defers to the policy. `Private` for anything + * shaped by who asked. + */ + public function __construct( + public readonly array $contents, + public readonly ?int $ttlMs = null, + public readonly ?CacheScope $cacheScope = null, + ) { + if (null !== $this->ttlMs && $this->ttlMs < 0) { + throw new InvalidArgumentException(\sprintf('A resource "ttlMs" must be zero or more, got %d.', $this->ttlMs)); + } + } + + /** + * @param array{ + * contents: array, + * ttlMs?: int, + * cacheScope?: string, + * } $data + */ + public static function fromArray(array $data): self + { + if (!isset($data['contents']) || !\is_array($data['contents'])) { + throw new InvalidArgumentException('Missing or invalid "contents" array in ReadResourceResult data.'); + } + + $contents = []; + foreach ($data['contents'] as $content) { + if (isset($content['text'])) { + $contents[] = TextResourceContents::fromArray($content); + } elseif (isset($content['blob'])) { + $contents[] = BlobResourceContents::fromArray($content); + } else { + throw new InvalidArgumentException('Invalid content type in ReadResourceResult data: '.json_encode($content)); + } + } + + $scope = isset($data['cacheScope']) && \is_string($data['cacheScope']) ? CacheScope::tryFrom($data['cacheScope']) : null; + + return new self($contents, isset($data['ttlMs']) && \is_int($data['ttlMs']) ? $data['ttlMs'] : null, $scope); + } + + /** + * @return array{ + * contents: array, + * ttlMs?: int, + * cacheScope?: string, + * } + */ + public function jsonSerialize(): array + { + $data = ['contents' => $this->contents]; + + // Only what this result actually decided; the wire codec fills the rest + // from policy, and an absent member is the signal for it to do so. + if (null !== $this->ttlMs) { + $data['ttlMs'] = $this->ttlMs; + } + + if (null !== $this->cacheScope) { + $data['cacheScope'] = $this->cacheScope->value; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Root.php b/admin/vendor/mcp/sdk/src/Schema/Root.php new file mode 100644 index 0000000..97a2e3a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Root.php @@ -0,0 +1,80 @@ + + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource + * URIs or server configuration instead. + */ +class Root implements \JsonSerializable +{ + private const URI_PATTERN = '/^file:\/\/.*$/'; + + /** + * @param string $uri The URI identifying the root. This *must* start with file:// for now. + * + * This restriction may be relaxed in future versions of the protocol to allow other URI schemes. + * @param string|null $name An optional name for the root. + * + * This can be used to provide a human-readable identifier for the root, which may be useful for + * display purposes or for referencing the root in other parts of the application. + */ + public function __construct( + public readonly string $uri, + public readonly ?string $name = null, + ) { + if (!preg_match(self::URI_PATTERN, $this->uri)) { + throw new InvalidArgumentException(\sprintf('Root URI must start with "file://". Given: "%s".', $this->uri)); + } + } + + /** + * @param RootData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['uri']) || !\is_string($data['uri'])) { + throw new InvalidArgumentException('Invalid or missing "uri" in Root data.'); + } + + if (isset($data['name']) && !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid "name" in Root data.'); + } + + return new self($data['uri'], $data['name'] ?? null); + } + + /** + * @return RootData + */ + public function jsonSerialize(): array + { + $data = ['uri' => $this->uri]; + if (null !== $this->name) { + $data['name'] = $this->name; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ServerCapabilities.php b/admin/vendor/mcp/sdk/src/Schema/ServerCapabilities.php new file mode 100644 index 0000000..33cc1ee --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ServerCapabilities.php @@ -0,0 +1,202 @@ + + */ +class ServerCapabilities implements \JsonSerializable +{ + /** + * @param ?bool $tools server exposes callable tools + * @param ?bool $toolsListChanged server supports list changed notifications for tools + * @param ?bool $resources server provides readable resources + * @param ?bool $resourcesSubscribe server supports subscribing to changes in the list of resources + * @param ?bool $resourcesListChanged server supports list changed notifications for resources + * @param ?bool $prompts server provides prompts templates + * @param ?bool $promptsListChanged server supports list changed notifications for prompts + * @param ?bool $logging server emits structured log messages + * @param ?bool $completions Server supports argument autocompletion + * @param ?array $experimental experimental, non-standard features that the server supports + * @param ?array $extensions protocol extensions the server supports (e.g. io.modelcontextprotocol/ui) + */ + public function __construct( + public readonly ?bool $tools = true, + public readonly ?bool $toolsListChanged = false, + public readonly ?bool $resources = true, + public readonly ?bool $resourcesSubscribe = false, + public readonly ?bool $resourcesListChanged = false, + public readonly ?bool $prompts = true, + public readonly ?bool $promptsListChanged = false, + public readonly ?bool $logging = false, + public readonly ?bool $completions = false, + public readonly ?array $experimental = null, + public readonly ?array $extensions = null, + ) { + } + + /** + * @param array{ + * logging?: mixed, + * completions?: mixed, + * prompts?: array{listChanged?: bool}|object, + * resources?: array{listChanged?: bool, subscribe?: bool}|object, + * tools?: object|array{listChanged?: bool}, + * experimental?: array, + * extensions?: array, + * } $data + */ + public static function fromArray(array $data): self + { + $loggingEnabled = isset($data['logging']); + $completionsEnabled = isset($data['completions']); + $toolsEnabled = isset($data['tools']); + $promptsEnabled = isset($data['prompts']); + $resourcesEnabled = isset($data['resources']); + + $promptsListChanged = null; + if (isset($data['prompts'])) { + if (\is_array($data['prompts']) && \array_key_exists('listChanged', $data['prompts'])) { + $promptsListChanged = (bool) $data['prompts']['listChanged']; + } elseif (\is_object($data['prompts']) && property_exists($data['prompts'], 'listChanged')) { + $promptsListChanged = (bool) $data['prompts']->listChanged; + } + } + + $resourcesSubscribe = null; + $resourcesListChanged = null; + if (isset($data['resources'])) { + if (\is_array($data['resources']) && \array_key_exists('subscribe', $data['resources'])) { + $resourcesSubscribe = (bool) $data['resources']['subscribe']; + } elseif (\is_object($data['resources']) && property_exists($data['resources'], 'subscribe')) { + $resourcesSubscribe = (bool) $data['resources']->subscribe; + } + if (\is_array($data['resources']) && \array_key_exists('listChanged', $data['resources'])) { + $resourcesListChanged = (bool) $data['resources']['listChanged']; + } elseif (\is_object($data['resources']) && property_exists($data['resources'], 'listChanged')) { + $resourcesListChanged = (bool) $data['resources']->listChanged; + } + } + + $toolsListChanged = null; + if (isset($data['tools'])) { + if (\is_array($data['tools']) && \array_key_exists('listChanged', $data['tools'])) { + $toolsListChanged = (bool) $data['tools']['listChanged']; + } elseif (\is_object($data['tools']) && property_exists($data['tools'], 'listChanged')) { + $toolsListChanged = (bool) $data['tools']->listChanged; + } + } + + return new self( + tools: $toolsEnabled, + toolsListChanged: $toolsListChanged, + resources: $resourcesEnabled, + resourcesSubscribe: $resourcesSubscribe, + resourcesListChanged: $resourcesListChanged, + prompts: $promptsEnabled, + promptsListChanged: $promptsListChanged, + logging: $loggingEnabled, + completions: $completionsEnabled, + experimental: \is_array($data['experimental'] ?? null) ? $data['experimental'] : null, + extensions: \is_array($data['extensions'] ?? null) ? $data['extensions'] : null, + ); + } + + /** + * Returns a copy with the given protocol extensions merged into the existing ones. + * + * Entries in $extensions override existing ones sharing the same id. + * + * @param array> $extensions + */ + public function withExtensions(array $extensions): self + { + return new self( + $this->tools, + $this->toolsListChanged, + $this->resources, + $this->resourcesSubscribe, + $this->resourcesListChanged, + $this->prompts, + $this->promptsListChanged, + $this->logging, + $this->completions, + $this->experimental, + [...$this->extensions ?? [], ...$extensions], + ); + } + + /** + * @return array{ + * logging?: object, + * completions?: object, + * prompts?: object, + * resources?: object, + * tools?: object, + * experimental?: object, + * extensions?: object, + * } + */ + public function jsonSerialize(): array + { + $data = []; + + if ($this->logging) { + $data['logging'] = new \stdClass(); + } + if ($this->completions) { + $data['completions'] = new \stdClass(); + } + + if ($this->prompts || $this->promptsListChanged) { + $data['prompts'] = new \stdClass(); + if ($this->promptsListChanged) { + $data['prompts']->listChanged = $this->promptsListChanged; + } + } + + if ($this->resources || $this->resourcesSubscribe || $this->resourcesListChanged) { + $data['resources'] = new \stdClass(); + if ($this->resourcesSubscribe) { + $data['resources']->subscribe = $this->resourcesSubscribe; + } + if ($this->resourcesListChanged) { + $data['resources']->listChanged = $this->resourcesListChanged; + } + } + + if ($this->tools || $this->toolsListChanged) { + $data['tools'] = new \stdClass(); + if ($this->toolsListChanged) { + $data['tools']->listChanged = $this->toolsListChanged; + } + } + + if ($this->experimental) { + $data['experimental'] = (object) $this->experimental; + } + + if ($this->extensions) { + // Each entry is a settings *object*; an extension with no settings + // declares `{}`, and an empty PHP array would serialize as `[]`. + $data['extensions'] = (object) array_map( + static fn (mixed $settings): mixed => \is_array($settings) ? (object) $settings : $settings, + $this->extensions, + ); + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Tool.php b/admin/vendor/mcp/sdk/src/Schema/Tool.php new file mode 100644 index 0000000..1e939f6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Tool.php @@ -0,0 +1,291 @@ +|\stdClass, + * required: string[]|null + * } + * @phpstan-type ToolOutputSchema array{ + * type?: string, + * properties?: array|\stdClass, + * required?: string[]|null, + * additionalProperties?: bool|array|\stdClass, + * description?: string + * } + * @phpstan-type ToolData array{ + * name: string, + * title?: string, + * inputSchema: ToolInputSchema, + * description?: string|null, + * annotations?: ToolAnnotationsData, + * icons?: IconData[], + * _meta?: array, + * outputSchema?: ToolOutputSchema + * } + * + * @author Kyrian Obikwelu + */ +class Tool implements \JsonSerializable +{ + /** + * JSON Schema keywords whose value is a single sub-schema. + */ + private const SUB_SCHEMA_KEYWORDS = [ + 'additionalItems', + 'additionalProperties', + 'contains', + 'else', + 'if', + 'not', + 'propertyNames', + 'then', + 'unevaluatedItems', + 'unevaluatedProperties', + ]; + + /** + * JSON Schema keywords whose value maps names to sub-schemas. + */ + private const SUB_SCHEMA_MAP_KEYWORDS = [ + '$defs', + 'definitions', + 'dependentSchemas', + 'patternProperties', + 'properties', + ]; + + /** + * JSON Schema keywords whose value is a list of sub-schemas. + */ + private const SUB_SCHEMA_LIST_KEYWORDS = [ + 'allOf', + 'anyOf', + 'oneOf', + 'prefixItems', + ]; + + /** + * @var ToolInputSchema + */ + public readonly array $inputSchema; + + /** + * @var ToolOutputSchema|null + */ + public readonly ?array $outputSchema; + + /** + * @param string $name the name of the tool + * @param ?string $title Optional human-readable title for display in UI + * @param ToolInputSchema $inputSchema a JSON Schema object (as a PHP array) defining the expected 'arguments' for the tool + * @param ?string $description A human-readable description of the tool. + * This can be used by clients to improve the LLM's understanding of + * available tools. It can be thought of like a "hint" to the model. + * @param ?ToolAnnotations $annotations optional additional tool information + * @param ?Icon[] $icons optional icons representing the tool + * @param ?array $meta Optional metadata + * @param ToolOutputSchema|null $outputSchema Optional JSON Schema (as a PHP array) describing the tool's + * structuredContent. Unlike $inputSchema its root is unconstrained — + * it may describe an array, a primitive, or a composition. + */ + public function __construct( + public readonly string $name, + public readonly ?string $title, + array $inputSchema, + public readonly ?string $description, + public readonly ?ToolAnnotations $annotations, + public readonly ?array $icons = null, + public readonly ?array $meta = null, + ?array $outputSchema = null, + ) { + if (!isset($inputSchema['type']) || 'object' !== $inputSchema['type']) { + throw new InvalidArgumentException('Tool inputSchema must be a JSON Schema of type "object".'); + } + + // Always normalize here so every construction path emits `{}` for empty + // sub-schemas — not only SchemaGenerator / fromArray. + $this->inputSchema = self::normalizeSchema($inputSchema); + $this->outputSchema = null !== $outputSchema ? self::normalizeSchema($outputSchema) : null; + + // An out-of-bounds `x-mcp-header` reachable through `properties` makes + // the whole tool definition invalid, so it is refused where the tool + // is defined rather than discovered when a header comparison + // mysteriously fails. + if (null !== $reason = McpHeader::checkAnnotations($this->inputSchema)) { + throw new InvalidArgumentException(\sprintf('Tool "%s" has an invalid "x-mcp-header" annotation: %s', $this->name, $reason)); + } + } + + /** + * @param ToolData $data + */ + public static function fromArray(array $data): self + { + if (empty($data['name']) || !\is_string($data['name'])) { + throw new InvalidArgumentException('Invalid or missing "name" in Tool data.'); + } + if (!isset($data['inputSchema']) || !\is_array($data['inputSchema'])) { + throw new InvalidArgumentException('Invalid or missing "inputSchema" in Tool data.'); + } + if (!isset($data['inputSchema']['type']) || 'object' !== $data['inputSchema']['type']) { + throw new InvalidArgumentException('Tool inputSchema must be of type "object".'); + } + + $outputSchema = null; + if (isset($data['outputSchema']) && \is_array($data['outputSchema'])) { + $outputSchema = $data['outputSchema']; + } + + return new self( + name: $data['name'], + title: isset($data['title']) && \is_string($data['title']) ? $data['title'] : null, + inputSchema: $data['inputSchema'], + description: isset($data['description']) && \is_string($data['description']) ? $data['description'] : null, + annotations: isset($data['annotations']) && \is_array($data['annotations']) ? ToolAnnotations::fromArray($data['annotations']) : null, + icons: isset($data['icons']) && \is_array($data['icons']) ? Icon::listFromArray($data['icons'], 'Tool') : null, + meta: isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null, + outputSchema: $outputSchema, + ); + } + + /** + * @return array{ + * name: string, + * title?: string, + * inputSchema: ToolInputSchema, + * description?: string, + * annotations?: ToolAnnotations, + * icons?: Icon[], + * _meta?: array, + * outputSchema?: ToolOutputSchema|\stdClass + * } + */ + public function jsonSerialize(): array + { + $data = ['name' => $this->name]; + if (null !== $this->title) { + $data['title'] = $this->title; + } + $data['inputSchema'] = $this->inputSchema; + if (null !== $this->description) { + $data['description'] = $this->description; + } + if (null !== $this->annotations) { + $data['annotations'] = $this->annotations; + } + if (null !== $this->icons) { + $data['icons'] = $this->icons; + } + if (null !== $this->meta) { + $data['_meta'] = $this->meta; + } + if (null !== $this->outputSchema) { + $data['outputSchema'] = [] === $this->outputSchema ? new \stdClass() : $this->outputSchema; + } + + return $data; + } + + /** + * Normalize a JSON Schema so that empty sub-schemas JSON-encode as `{}` rather than `[]`. + * + * Once JSON is decoded into associative arrays, PHP cannot tell the empty object `{}` + * from the empty array `[]` — both are `[]`. Re-encoding then produces `[]`, which is + * invalid wherever a schema is expected (`properties`, `items`, `additionalProperties`, + * …), and strict clients reject it. Every empty sub-schema is therefore replaced with a + * `\stdClass` before serialization. + * + * The walk is recursive and covers the schema keywords of draft-07 through 2020-12, so + * nested object parameters, `$defs`, combinators, and `outputSchema` are all covered — + * not only the top-level `properties` map. + * + * @param array $schema + * + * @return array + */ + private static function normalizeSchema(array $schema): array + { + foreach (self::SUB_SCHEMA_KEYWORDS as $keyword) { + if (isset($schema[$keyword]) && \is_array($schema[$keyword])) { + $schema[$keyword] = self::normalizeSubSchema($schema[$keyword]); + } + } + + foreach (self::SUB_SCHEMA_MAP_KEYWORDS as $keyword) { + if (!isset($schema[$keyword]) || !\is_array($schema[$keyword])) { + continue; + } + + if ([] === $schema[$keyword]) { + $schema[$keyword] = new \stdClass(); + continue; + } + + foreach ($schema[$keyword] as $name => $subSchema) { + if (\is_array($subSchema)) { + $schema[$keyword][$name] = self::normalizeSubSchema($subSchema); + } + } + } + + foreach (self::SUB_SCHEMA_LIST_KEYWORDS as $keyword) { + if (!isset($schema[$keyword]) || !\is_array($schema[$keyword])) { + continue; + } + + // An empty list stays a list — `allOf: []` is already valid JSON. + foreach ($schema[$keyword] as $index => $subSchema) { + if (\is_array($subSchema)) { + $schema[$keyword][$index] = self::normalizeSubSchema($subSchema); + } + } + } + + if (isset($schema['items']) && \is_array($schema['items'])) { + // `items` is a single sub-schema, or a list of them in draft-07 tuple form. + // An empty array is read as the empty schema `{}` — what an `items: {}` from + // SchemaGenerator decodes to — rather than as an empty tuple. + if ([] !== $schema['items'] && array_is_list($schema['items'])) { + foreach ($schema['items'] as $index => $itemSchema) { + if (\is_array($itemSchema)) { + $schema['items'][$index] = self::normalizeSubSchema($itemSchema); + } + } + } else { + $schema['items'] = self::normalizeSubSchema($schema['items']); + } + } + + return $schema; + } + + /** + * @param array $schema + * + * @return array|\stdClass + */ + private static function normalizeSubSchema(array $schema): array|\stdClass + { + return [] === $schema ? new \stdClass() : self::normalizeSchema($schema); + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ToolAnnotations.php b/admin/vendor/mcp/sdk/src/Schema/ToolAnnotations.php new file mode 100644 index 0000000..c4f2774 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ToolAnnotations.php @@ -0,0 +1,96 @@ + + */ +class ToolAnnotations implements \JsonSerializable +{ + /** + * @param ?string $title a human-readable title for the tool — deprecated for display in favor of `Mcp\Schema\Tool::$title` per MCP spec revision 2025-06-18; retained for backward compatibility + * @param ?bool $readOnlyHint if true, the tool does not modify its environment + * @param ?bool $destructiveHint If true, the tool may perform destructive updates to its environment. If false, the tool performs only additive updates. + * @param ?bool $idempotentHint If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment. (This property is meaningful only when `readOnlyHint == false`) + * @param ?bool $openWorldHint If true, this tool may interact with an "open world" of external entities. If false, the tool's domain of interaction is closed. For example, the world of a web search tool is open, whereas that of a memory tool is not. + */ + public function __construct( + public readonly ?string $title = null, + public readonly ?bool $readOnlyHint = null, + public readonly ?bool $destructiveHint = null, + public readonly ?bool $idempotentHint = null, + public readonly ?bool $openWorldHint = null, + ) { + } + + /** + * @param ToolAnnotationsData $data + */ + public static function fromArray(array $data): self + { + if (isset($data['title']) && !\is_string($data['title'])) { + throw new InvalidArgumentException('Invalid "title" in ToolAnnotations data.'); + } + + foreach (['readOnlyHint', 'destructiveHint', 'idempotentHint', 'openWorldHint'] as $hint) { + if (isset($data[$hint]) && !\is_bool($data[$hint])) { + throw new InvalidArgumentException(\sprintf('Invalid "%s" in ToolAnnotations data; expected a boolean.', $hint)); + } + } + + return new self( + $data['title'] ?? null, + $data['readOnlyHint'] ?? null, + $data['destructiveHint'] ?? null, + $data['idempotentHint'] ?? null, + $data['openWorldHint'] ?? null + ); + } + + /** + * @return ToolAnnotationsData + */ + public function jsonSerialize(): array + { + $data = []; + if (null !== $this->title) { + $data['title'] = $this->title; + } + if (null !== $this->readOnlyHint) { + $data['readOnlyHint'] = $this->readOnlyHint; + } + if (null !== $this->destructiveHint) { + $data['destructiveHint'] = $this->destructiveHint; + } + if (null !== $this->idempotentHint) { + $data['idempotentHint'] = $this->idempotentHint; + } + if (null !== $this->openWorldHint) { + $data['openWorldHint'] = $this->openWorldHint; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/ToolChoice.php b/admin/vendor/mcp/sdk/src/Schema/ToolChoice.php new file mode 100644 index 0000000..ad00323 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/ToolChoice.php @@ -0,0 +1,54 @@ + $this->mode->value]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Schema/Wire/McpHeader.php b/admin/vendor/mcp/sdk/src/Schema/Wire/McpHeader.php new file mode 100644 index 0000000..e73907b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Schema/Wire/McpHeader.php @@ -0,0 +1,217 @@ + + */ +final class McpHeader +{ + public const METHOD = 'Mcp-Method'; + public const NAME = 'Mcp-Name'; + public const PARAM_PREFIX = 'Mcp-Param-'; + public const PROTOCOL_VERSION = 'MCP-Protocol-Version'; + + /** Wrapper marking a header value as Base64 of its UTF-8 representation. */ + private const BASE64_PREFIX = '=?base64?'; + private const BASE64_SUFFIX = '?='; + + /** + * The subject of a request, per method. Anything unlisted is exempt. + * + * @param array|null $params + */ + public static function nameFor(string $method, ?array $params): ?string + { + $value = match ($method) { + 'tools/call', 'prompts/get' => $params['name'] ?? null, + 'resources/read' => $params['uri'] ?? null, + 'tasks/get', 'tasks/update', 'tasks/cancel' => $params['taskId'] ?? null, + default => null, + }; + + return \is_string($value) ? $value : null; + } + + /** + * Renders a mirrored argument as a header value, wrapping it when it is not + * header-safe. + * + * Booleans travel as `true`/`false` and integers as decimal digits, both of + * which are always safe. A string is wrapped when it carries a control + * character, anything outside US-ASCII, or leading or trailing whitespace — + * the last because a receiver is entitled to trim the value (RFC 9110 + * §5.5), which would otherwise silently change it. + * + * Returns null for a value that cannot be mirrored at all. + */ + public static function encode(mixed $value): ?string + { + $rendered = match (true) { + \is_bool($value) => $value ? 'true' : 'false', + \is_int($value) => (string) $value, + \is_string($value) => $value, + default => null, + }; + + if (null === $rendered) { + return null; + } + + return self::isSafe($rendered) ? $rendered : self::wrap($rendered); + } + + /** + * Unwraps a `=?base64?…?=` value, or returns a plain value unchanged. + * + * Strict: PHP's decoder accepts mispadded input and returns plausible + * bytes, which would turn a corrupted header into a silent mismatch. + * Null when the wrapper is present but its contents are not valid Base64. + */ + public static function decode(string $value): ?string + { + if (!str_starts_with($value, self::BASE64_PREFIX) || !str_ends_with($value, self::BASE64_SUFFIX)) { + return $value; + } + + $encoded = substr($value, \strlen(self::BASE64_PREFIX), -\strlen(self::BASE64_SUFFIX)); + + $decoded = base64_decode($encoded, true); + + if (false === $decoded || base64_encode($decoded) !== $encoded) { + return null; + } + + return $decoded; + } + + /** + * Validates every `x-mcp-header` annotation reachable through `properties` + * in an input schema (SEP-2243). + * + * The value becomes an HTTP field name, so it has to be one; it has to be + * unique case-insensitively, or two arguments would fight over one header; + * and it may only sit on a primitive that is not `number`, because a float + * has no single decimal spelling for a receiver to compare against. + * + * @param array $schema + * + * @return string|null the reason it is invalid, or null when every annotation is well-formed + */ + public static function checkAnnotations(array $schema): ?string + { + $seen = []; + + foreach (self::annotations($schema) as [$name, $type, $path]) { + if ('' === $name) { + return \sprintf('the annotation at "%s" is empty', $path); + } + + // RFC 9110 tchar; excludes CR, LF and every other control character. + if (1 !== preg_match('/^[!#$%&\'*+\-.^_`|~0-9A-Za-z]+$/', $name)) { + return \sprintf('"%s" is not a valid HTTP field name', $name); + } + + $folded = strtolower($name); + if (isset($seen[$folded])) { + return \sprintf('"%s" is declared twice, at "%s" and "%s"', $name, $seen[$folded], $path); + } + $seen[$folded] = $path; + + if ('number' === $type) { + return \sprintf('"%s" is on a "number" property ("%s"), which cannot be mirrored', $name, $path); + } + + if (null !== $type && !\in_array($type, ['string', 'integer', 'boolean'], true)) { + return \sprintf('"%s" is on a "%s" property ("%s"); only string, integer and boolean can be mirrored', $name, $type, $path); + } + } + + return null; + } + + /** + * Every annotation reachable through `properties` alone, as name, declared + * type and dotted path. + * + * @param array $schema + * + * @return list + */ + public static function annotations(array $schema, string $prefix = ''): array + { + $properties = $schema['properties'] ?? null; + + if (!\is_array($properties)) { + return []; + } + + $found = []; + + foreach ($properties as $property => $definition) { + if (!\is_array($definition)) { + continue; + } + + $path = '' === $prefix ? (string) $property : $prefix.'.'.$property; + $annotation = $definition['x-mcp-header'] ?? null; + + if (null !== $annotation) { + if (!\is_string($annotation)) { + $found[] = ['', null, $path]; + } else { + $found[] = [$annotation, \is_string($definition['type'] ?? null) ? $definition['type'] : null, $path]; + } + } + + $found = [...$found, ...self::annotations($definition, $path)]; + } + + return $found; + } + + private static function wrap(string $value): string + { + return self::BASE64_PREFIX.base64_encode($value).self::BASE64_SUFFIX; + } + + /** + * Printable US-ASCII with no leading or trailing whitespace. Interior + * spaces are fine; a tab is not, since it is a control character that + * field parsers are allowed to fold. + * + * A literal that already has the wrapper's shape is not safe either: sent + * unchanged, {@see self::decode()} would unwrap it as if it were Base64 + * and hand back something other than the literal. + */ + private static function isSafe(string $value): bool + { + if ($value !== trim($value)) { + return false; + } + + if (str_starts_with($value, self::BASE64_PREFIX) && str_ends_with($value, self::BASE64_SUFFIX)) { + return false; + } + + return 1 === preg_match('/^[\x20-\x7E]*$/', $value); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server.php b/admin/vendor/mcp/sdk/src/Server.php new file mode 100644 index 0000000..a19e859 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server.php @@ -0,0 +1,72 @@ + + * @author Kyrian Obikwelu + */ +final class Server +{ + /** + * @param StatelessProtocol|null $statelessProtocol the modern-era (SEP-2575) dispatcher, absent on a + * server that serves the handshake era alone + */ + public function __construct( + private readonly Protocol $protocol, + private readonly LoggerInterface $logger = new NullLogger(), + private readonly ?StatelessProtocol $statelessProtocol = null, + ) { + } + + public static function builder(): Builder + { + return new Builder(); + } + + /** + * @template TResult + * + * @param TransportInterface $transport + * + * @return TResult + */ + public function run(TransportInterface $transport): mixed + { + $transport->initialize(); + + $this->protocol->connect($transport); + + // The eras share the transport, not the dispatcher: a transport that + // can tell them apart takes both and picks per request. One that + // cannot — stdio — carries the handshake era alone. + if (null !== $this->statelessProtocol && $transport instanceof StatelessAwareTransportInterface) { + $transport->connectStateless($this->statelessProtocol); + } + + $this->logger->info('Running server...'); + + try { + return $transport->listen(); + } finally { + $transport->close(); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Builder.php b/admin/vendor/mcp/sdk/src/Server/Builder.php new file mode 100644 index 0000000..1a5dd73 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Builder.php @@ -0,0 +1,1173 @@ +>, + * notificationHandlers: list, + * } + * + * @author Kyrian Obikwelu + */ +final class Builder +{ + private ?Implementation $serverInfo = null; + + private RegistryInterface $registry; + + private ?SubscriptionManagerInterface $subscriptionManager = null; + + private ?LoggerInterface $logger = null; + + private ?CacheInterface $discoveryCache = null; + + private ?EventDispatcherInterface $eventDispatcher = null; + + private ?ContainerInterface $container = null; + + private ?SchemaGeneratorInterface $schemaGenerator = null; + + private ?ReferenceHandlerInterface $referenceHandler = null; + + private ?DiscovererInterface $discoverer = null; + + private ?SessionManagerInterface $sessionManager = null; + + private ?SessionStoreInterface $sessionStore = null; + + private int $gcProbability = 1; + + private int $gcDivisor = 100; + + private int $paginationLimit = 50; + + private ?string $instructions = null; + + private ?ProtocolVersion $protocolVersion = null; + + private ?CachePolicy $cachePolicy = null; + + private ?NotificationBusInterface $notificationBus = null; + + private float $subscriptionLifetime = 30.0; + + /** @var array RPC method to the extension identifier defining it */ + private array $extensionMethods = []; + + /** @var list|class-string<\Mcp\Schema\JsonRpc\Notification>> */ + private array $extensionMessages = []; + + private ?string $requestStateKey = null; + + private int $requestStateTtl = 600; + + /** + * @var array> + */ + private array $requestHandlers = []; + + /** + * @var array + */ + private array $notificationHandlers = []; + + /** + * @var array{ + * handler: Handler, + * name: ?string, + * title: ?string, + * description: ?string, + * annotations: ?ToolAnnotations, + * inputSchema: ?array, + * icons: ?Icon[], + * meta: ?array, + * outputSchema: ?array, + * }[] + */ + private array $tools = []; + + /** + * @var array{ + * handler: Handler, + * uri: string, + * name: ?string, + * title: ?string, + * description: ?string, + * mimeType: ?string, + * size: int|null, + * annotations: ?Annotations, + * icons: ?Icon[], + * meta: ?array + * }[] + */ + private array $resources = []; + + /** + * @var array{ + * handler: Handler, + * uriTemplate: string, + * name: ?string, + * title: ?string, + * description: ?string, + * mimeType: ?string, + * annotations: ?Annotations, + * meta: ?array + * }[] + */ + private array $resourceTemplates = []; + + /** + * @var array{ + * handler: Handler, + * name: ?string, + * title: ?string, + * description: ?string, + * icons: ?Icon[], + * meta: ?array + * }[] + */ + private array $prompts = []; + + /** + * @var list + */ + private array $explicitTools = []; + + /** + * @var list + */ + private array $explicitResources = []; + + /** + * @var list}> + */ + private array $explicitResourceTemplates = []; + + /** + * @var list}> + */ + private array $explicitPrompts = []; + + private ?string $discoveryBasePath = null; + + /** + * @var string[] + */ + private array $discoveryScanDirs = []; + + /** + * @var array|string[] + */ + private array $discoveryExcludeDirs = []; + + /** + * @var string[]|null + */ + private ?array $discoveryNamePatterns = null; + + private ?ServerCapabilities $serverCapabilities = null; + + /** + * @var array> + */ + private array $extensions = []; + + /** + * @var LoaderInterface[] + */ + private array $loaders = []; + + private bool $hasCustomRegistry = false; + + private bool $lazyLoading = true; + + private bool $headerValidation = true; + + /** @var list|null null defaults to every modern revision, [] serves none */ + private ?array $modernVersions = null; + + private bool $inputRequiredShim = true; + + private int $inputRequiredRounds = InputRequiredShim::DEFAULT_MAX_ROUNDS; + + private int $inputRequiredTimeout = InputRequiredShim::DEFAULT_ROUND_TIMEOUT; + + /** + * @see self::assemble() for why this is memoized + * + * @var AssembledParts|null + */ + private ?array $parts = null; + + /** + * Sets the server's identity. Required. + * + * @param ?Icon[] $icons + * @param ?string $title Display name for UI and end-user contexts. Falls back to $name when absent. + */ + public function setServerInfo( + string $name, + string $version, + ?string $description = null, + ?array $icons = null, + ?string $websiteUrl = null, + ?string $title = null, + ): self { + $this->serverInfo = new Implementation(trim($name), trim($version), $description, $icons, $websiteUrl, $title); + + return $this; + } + + /** + * Sets the bus carrying server-initiated notifications to open + * `subscriptions/listen` streams (SEP-2575). + * + * Without one, a listen stream acknowledges and then carries nothing: there + * is no safe default, because the right implementation depends on whether + * the publisher and the stream share a process. + * {@see InMemoryNotificationBus} is correct for stdio and persistent + * runtimes; under PHP-FPM, where they are different workers, use + * {@see Psr16NotificationBus} or an implementation over your own broker. + * + * Registry changes are published automatically when an event dispatcher is + * configured; anything else — `notifications/resources/updated` above all — + * is published by the application calling + * {@see NotificationBusInterface::publish()}. + */ + public function setNotificationBus(NotificationBusInterface $bus): self + { + $this->notificationBus = $bus; + + return $this; + } + + /** + * Sets how long a `subscriptions/listen` stream is held open before the + * server closes it gracefully. + * + * The real ceiling is the runtime's: under PHP-FPM a stream cannot outlive + * `max_execution_time`, and a value above it buys a killed worker instead + * of a longer subscription. Pass `0` for "until the client or the runtime + * ends it", which is what a persistent runtime wants. + */ + public function setSubscriptionLifetime(float $seconds): self + { + $this->subscriptionLifetime = max(0.0, $seconds); + + return $this; + } + + /** + * Sets how long, and to whom, this server's answers may be cached (SEP-2549). + * + * The modern lifecycle must put `ttlMs` and `cacheScope` on every cacheable + * result; without a policy it says "private, immediately stale", which is + * conformant and forfeits the point. Build one with + * {@see CachePolicy::default()} and narrow it per method: + * + * ```php + * $builder->setCachePolicy( + * CachePolicy::default(60_000) + * ->withMethod('tools/list', 3_600_000, CacheScope::Public), + * ); + * ``` + */ + public function setCachePolicy(CachePolicy $policy): self + { + $this->cachePolicy = $policy; + + return $this; + } + + /** + * Sets the key signing the `requestState` carried across the rounds of a + * multi round-trip request (SEP-2322). Without one, every echoed state is + * refused. + * + * The same key must reach every instance that might serve the retry, so a + * per-process random value only works for a single-process deployment. + * + * @param string $key at least 32 bytes + * @param int $ttl how long a minted state stays valid, in seconds + */ + public function setRequestState(string $key, int $ttl = 600): self + { + $this->requestStateKey = $key; + $this->requestStateTtl = $ttl; + + return $this; + } + + /** + * Configures the server's pagination limit. + */ + public function setPaginationLimit(int $paginationLimit): self + { + $this->paginationLimit = $paginationLimit; + + return $this; + } + + /** + * Configures the instructions describing how to use the server and its features. + * + * This can be used by clients to improve the LLM's understanding of available tools, resources, + * etc. It can be thought of like a "hint" to the model. For example, this information MAY + * be added to the system prompt. + */ + public function setInstructions(?string $instructions): self + { + $this->instructions = $instructions; + + return $this; + } + + /** + * Explicitly set server capabilities. If set, this overrides automatic detection. + */ + public function setCapabilities(ServerCapabilities $serverCapabilities): self + { + $this->serverCapabilities = $serverCapabilities; + + return $this; + } + + /** + * Enable one or more MCP protocol extensions, announced to clients under + * `capabilities.extensions` during the initialize handshake. + * + * An extension also contributes the message classes its methods decode + * into and the handlers serving them, if any — see {@see AbstractExtension} + * for extensions that only announce a capability. + * + * @throws InvalidArgumentException if the identifier is not a valid `_meta` prefix + * @throws LogicException if the same extension is enabled more than once, or + * two enabled extensions define the same RPC method + */ + public function enableExtension(ExtensionInterface ...$extensions): self + { + foreach ($extensions as $extension) { + $id = (string) $extension->getId(); + + if (isset($this->extensions[$id])) { + throw new LogicException(\sprintf('Extension "%s" is already enabled.', $id)); + } + + $this->extensions[$id] = $extension->getCapabilities(); + + // Without this the method cannot be decoded at all, so nothing + // downstream ever sees it. + foreach ($extension->getMessages() as $message) { + $method = $message::getMethod(); + + // The message factory resolves a method to whichever class was + // registered first, so a second owner here would silently lose + // the dispatch race while still being named in error messages. + if (isset($this->extensionMethods[$method]) && $this->extensionMethods[$method] !== $id) { + throw new LogicException(\sprintf('Method "%s" is already claimed by extension "%s", so extension "%s" cannot also define it.', $method, $this->extensionMethods[$method], $id)); + } + + $this->extensionMessages[] = $message; + // Recorded even though the handler answers it, so a server with + // the extension *off* can say so instead of "no such method". + $this->extensionMethods[$method] = $id; + } + + foreach ($extension->getRequestHandlers() as $handler) { + $this->requestHandlers[] = $handler; + } + } + + return $this; + } + + /** + * Register a single custom method handler. + * + * @param RequestHandlerInterface $handler + */ + public function addRequestHandler(RequestHandlerInterface $handler): self + { + $this->requestHandlers[] = $handler; + + return $this; + } + + /** + * Register multiple custom method handlers. + * + * @param iterable> $handlers + */ + public function addRequestHandlers(iterable $handlers): self + { + foreach ($handlers as $handler) { + $this->requestHandlers[] = $handler; + } + + return $this; + } + + /** + * Register a single custom notification handler. + */ + public function addNotificationHandler(NotificationHandlerInterface $handler): self + { + $this->notificationHandlers[] = $handler; + + return $this; + } + + /** + * Register multiple custom notification handlers. + * + * @param iterable $handlers + */ + public function addNotificationHandlers(iterable $handlers): self + { + foreach ($handlers as $handler) { + $this->notificationHandlers[] = $handler; + } + + return $this; + } + + public function setRegistry(RegistryInterface $registry): self + { + $this->registry = $registry; + $this->hasCustomRegistry = true; + + return $this; + } + + /** + * Controls when configured loaders (manual elements, discovery, custom loaders) run. + * + * Lazy (the default) defers loading to the first registry read so a persistent runtime does not + * freeze the registry to a source not yet ready at build time. Disable to load eagerly at build. + * A registry supplied via setRegistry() is always loaded eagerly; its own constructor loader, + * if it has one, still runs on the first read. + */ + public function setLazyLoading(bool $lazyLoading = true): self + { + $this->lazyLoading = $lazyLoading; + + return $this; + } + + /** + * Controls whether {@see self::buildStateless()} checks the standard MCP + * request headers (SEP-2243) against the JSON-RPC body. On by default. + * + * Disable for a `StatelessProtocol` served by a transport with no header + * layer — the validator would otherwise reject every request for + * carrying none of the standard headers. + */ + public function setHeaderValidator(bool $headerValidation = true): self + { + $this->headerValidation = $headerValidation; + + return $this; + } + + /** + * Provides a PSR-3 logger instance. Defaults to NullLogger. + */ + public function setLogger(LoggerInterface $logger): self + { + $this->logger = $logger; + + return $this; + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self + { + $this->eventDispatcher = $eventDispatcher; + + return $this; + } + + /** + * Provides a PSR-11 DI container, primarily for resolving user-defined handler classes. + * Defaults to a basic internal container. + */ + public function setContainer(ContainerInterface $container): self + { + $this->container = $container; + + return $this; + } + + public function setSchemaGenerator(SchemaGeneratorInterface $schemaGenerator): self + { + $this->schemaGenerator = $schemaGenerator; + + return $this; + } + + public function setReferenceHandler(ReferenceHandlerInterface $referenceHandler): self + { + $this->referenceHandler = $referenceHandler; + + return $this; + } + + public function setDiscoverer(DiscovererInterface $discoverer): self + { + $this->discoverer = $discoverer; + + return $this; + } + + public function setResourceSubscriptionManager(SubscriptionManagerInterface $subscriptionManager): self + { + $this->subscriptionManager = $subscriptionManager; + + return $this; + } + + /** + * Configures the session layer. + * + * @param int $gcProbability The numerator of the GC probability fraction (like PHP's session.gc_probability). Set to 0 to disable GC. + * @param int $gcDivisor The denominator of the GC probability fraction (like PHP's session.gc_divisor). Probability = gcProbability/gcDivisor. + */ + public function setSession( + ?SessionStoreInterface $sessionStore = null, + ?SessionManagerInterface $sessionManager = null, + int $gcProbability = 1, + int $gcDivisor = 100, + ): self { + $this->sessionStore = $sessionStore; + $this->sessionManager = $sessionManager; + $this->gcProbability = $gcProbability; + $this->gcDivisor = $gcDivisor; + + if (null !== $sessionManager && null !== $sessionStore) { + throw new InvalidArgumentException('Cannot set both SessionStore and SessionManager. Set only one or the other.'); + } + + return $this; + } + + /** + * @param string[] $scanDirs + * @param string[] $excludeDirs + * @param string[] $namePatterns + */ + public function setDiscovery( + string $basePath, + array $scanDirs = ['.', 'src'], + array $excludeDirs = [], + ?CacheInterface $cache = null, + array $namePatterns = DiscovererInterface::DEFAULT_NAME_PATERNS, + ): self { + $this->discoveryBasePath = $basePath; + $this->discoveryScanDirs = $scanDirs; + $this->discoveryExcludeDirs = $excludeDirs; + $this->discoveryCache = $cache; + $this->discoveryNamePatterns = $namePatterns; + + return $this; + } + + public function setProtocolVersion(ProtocolVersion $protocolVersion): self + { + $this->protocolVersion = $protocolVersion; + + return $this; + } + + /** + * Manually registers a tool handler. + * + * @param Handler $handler + * @param ?string $title Optional human-readable title for display in UI + * @param array|null $inputSchema + * @param ?Icon[] $icons + * @param array|null $meta + * @param array|null $outputSchema + */ + public function addTool( + callable|array|string $handler, + ?string $name = null, + ?string $title = null, + ?string $description = null, + ?ToolAnnotations $annotations = null, + ?array $inputSchema = null, + ?array $icons = null, + ?array $meta = null, + ?array $outputSchema = null, + ): self { + $this->tools[] = compact( + 'handler', + 'name', + 'title', + 'description', + 'annotations', + 'inputSchema', + 'icons', + 'meta', + 'outputSchema', + ); + + return $this; + } + + /** + * Manually registers a resource handler. + * + * @param Handler $handler + * @param ?string $title Optional human-readable title for display in UI + * @param ?Icon[] $icons + * @param array|null $meta + */ + public function addResource( + \Closure|array|string $handler, + string $uri, + ?string $name = null, + ?string $title = null, + ?string $description = null, + ?string $mimeType = null, + ?int $size = null, + ?Annotations $annotations = null, + ?array $icons = null, + ?array $meta = null, + ): self { + $this->resources[] = compact( + 'handler', + 'uri', + 'name', + 'title', + 'description', + 'mimeType', + 'size', + 'annotations', + 'icons', + 'meta', + ); + + return $this; + } + + /** + * Manually registers a resource template handler. + * + * @param Handler $handler + * @param ?string $title Optional human-readable title for display in UI + * @param array|null $meta + */ + public function addResourceTemplate( + \Closure|array|string $handler, + string $uriTemplate, + ?string $name = null, + ?string $title = null, + ?string $description = null, + ?string $mimeType = null, + ?Annotations $annotations = null, + ?array $meta = null, + ): self { + $this->resourceTemplates[] = compact( + 'handler', + 'uriTemplate', + 'name', + 'title', + 'description', + 'mimeType', + 'annotations', + 'meta', + ); + + return $this; + } + + /** + * Manually registers a prompt handler. + * + * @param Handler $handler + * @param ?Icon[] $icons + * @param array|null $meta + */ + public function addPrompt( + \Closure|array|string $handler, + ?string $name = null, + ?string $title = null, + ?string $description = null, + ?array $icons = null, + ?array $meta = null, + ): self { + $this->prompts[] = compact('handler', 'name', 'title', 'description', 'icons', 'meta'); + + return $this; + } + + /** + * Registers an element using an explicit schema value object paired with a handler interface. + * + * Use this entry point when an element's name, schema, or description is only known at + * runtime (e.g. config-driven integrations). For statically-known elements, prefer + * `addTool/addResource/addResourceTemplate/addPrompt`, which can derive metadata from + * reflection of the handler. + * + * Mismatched pairings (e.g. a `Tool` with a `PromptHandlerInterface`) raise + * `Mcp\Exception\InvalidArgumentException`. Completion providers are only supported on + * `Prompt` and `ResourceTemplate` definitions; supplying them with `Tool` or + * `ResourceDefinition` raises the same exception. + * + * @param array $completionProviders Keyed by argument/variable name + */ + public function add( + Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, + ElementHandlerInterface $handler, + array $completionProviders = [], + ): self { + if ([] !== $completionProviders && ($definition instanceof Tool || $definition instanceof ResourceDefinition)) { + throw new InvalidArgumentException(\sprintf('Completion providers are only supported on Prompt and ResourceTemplate definitions, got %s.', $definition::class)); + } + + match (true) { + $definition instanceof Tool && $handler instanceof ToolHandlerInterface => $this->explicitTools[] = ['definition' => $definition, 'handler' => $handler], + $definition instanceof ResourceDefinition && $handler instanceof ResourceHandlerInterface => $this->explicitResources[] = ['definition' => $definition, 'handler' => $handler], + $definition instanceof ResourceTemplate && $handler instanceof ResourceTemplateHandlerInterface => $this->explicitResourceTemplates[] = ['definition' => $definition, 'handler' => $handler, 'completionProviders' => $completionProviders], + $definition instanceof Prompt && $handler instanceof PromptHandlerInterface => $this->explicitPrompts[] = ['definition' => $definition, 'handler' => $handler, 'completionProviders' => $completionProviders], + default => throw new InvalidArgumentException(\sprintf('%s definition cannot be paired with %s; expected the matching handler interface.', $definition::class, $handler::class)), + }; + + return $this; + } + + /** + * Register a single custom loader. + */ + public function addLoader(LoaderInterface $loader): self + { + $this->loaders[] = $loader; + + return $this; + } + + /** + * @param iterable $loaders + */ + public function addLoaders(iterable $loaders): self + { + foreach ($loaders as $loader) { + $this->loaders[] = $loader; + } + + return $this; + } + + /** + * Stop serving multi round-trip handlers to handshake-era clients. + * + * A handler that returns an {@see \Mcp\Schema\Result\InputRequiredResult} + * is written for the modern era, where the client answers the embedded + * requests and retries the call. On a handshake-era connection the SDK + * fulfils it instead, by sending those requests over that connection's own + * channel and re-entering the handler with the answers — so one handler + * serves both eras. See {@see InputRequiredShim} for what re-entry costs. + * + * Turn it off to have such a handler fail on a handshake-era connection + * rather than be fulfilled behind your back. + */ + public function withoutInputRequiredShim(): self + { + $this->inputRequiredShim = false; + + return $this; + } + + /** + * Bounds on the shim's loop: how many times a handler may be re-entered for + * one request, and how long one answer is waited for. + * + * The wait holds the originating request open, so on a process-per-request + * runtime it holds a worker too. Size it against your pool, not against a + * user's patience. + */ + public function setInputRequiredLimits(int $maxRounds, int $roundTimeout): self + { + if ($maxRounds < 1) { + throw new InvalidArgumentException('maxRounds must be at least 1.'); + } + + if ($roundTimeout < 1) { + throw new InvalidArgumentException('roundTimeout must be at least 1 second.'); + } + + $this->inputRequiredRounds = $maxRounds; + $this->inputRequiredTimeout = $roundTimeout; + + return $this; + } + + private function requestStateCodec(): ?RequestStateCodec + { + return null !== $this->requestStateKey + ? new RequestStateCodec($this->requestStateKey, $this->requestStateTtl) + : null; + } + + /** + * Serve only the handshake era, refusing modern-era traffic. + * + * The default is to serve both from whatever the server is run on, because + * an endpoint that turns a client away for speaking the newer revision is + * almost never what anyone wants. Call this when it is: a deployment that + * has to stay on the handshake wire, or one whose tools call back into the + * client and would fail the modern half anyway. + */ + public function withoutModernEra(): self + { + $this->modernVersions = []; + + return $this; + } + + /** + * Revisions the modern-era leg answers for. Defaults to every modern + * revision this SDK knows. + * + * @param list $versions + * + * @throws InvalidArgumentException if a version is not one {@see Wire\InboundClassifier} routes to this leg + */ + public function setModernVersions(array $versions): self + { + foreach ($versions as $version) { + if (!$version->isModern()) { + throw new InvalidArgumentException(\sprintf('"%s" is a handshake-era revision; a request claiming it never reaches the modern leg to be served.', $version->value)); + } + } + + $this->modernVersions = $versions; + + return $this; + } + + /** + * Builds the fully configured Server instance. + * + * The result carries a dispatcher for each era. Which one answers is a + * per-request decision the transport makes, so one server object — and one + * endpoint — serves handshake-era and modern-era clients alike. + */ + public function build(): Server + { + $parts = $this->assemble(); + + $protocol = new Protocol( + requestHandlers: $parts['requestHandlers'], + notificationHandlers: $parts['notificationHandlers'], + messageFactory: $parts['messageFactory'], + sessionManager: $parts['sessionManager'], + logger: $parts['logger'], + eventDispatcher: $parts['eventDispatcher'], + inputRequiredShim: $this->inputRequiredShim + ? new InputRequiredShim($this->inputRequiredRounds, $this->inputRequiredTimeout, $parts['logger']) + : null, + requestStateCodec: $this->requestStateCodec(), + ); + + $modernVersions = $this->modernVersions ?? ProtocolVersion::modernVersions(); + + return new Server( + $protocol, + $parts['logger'], + [] === $modernVersions ? null : $this->buildStateless($modernVersions), + ); + } + + /** + * Builds a dispatcher for the modern (SEP-2575) lifecycle on its own. + * + * Tools, prompts, resources and their handlers are era-independent, so one + * builder configuration drives either lifecycle. {@see self::build()} wires + * both together; this is the modern era by itself, for an endpoint that + * serves nothing else. + * + * @param list $supportedVersions revisions this dispatcher will answer for + */ + public function buildStateless(array $supportedVersions = [ProtocolVersion::V2026_07_28]): StatelessProtocol + { + $parts = $this->assemble(); + + return new StatelessProtocol( + requestHandlers: $parts['requestHandlers'], + messageFactory: $parts['messageFactory'], + configuration: $parts['configuration'], + supportedVersions: $supportedVersions, + logger: $parts['logger'], + subscriptionLifetime: $this->subscriptionLifetime, + headerValidator: $this->headerValidation ? new StandardHeaderValidator($parts['registry']) : null, + requestStateCodec: $this->requestStateCodec(), + cachePolicy: $this->cachePolicy, + notificationBus: $this->notificationBus, + extensionMethods: $this->extensionMethods, + ); + } + + /** + * Resolves the builder's configuration into the parts both lifecycles need. + * + * Memoized: the two eras share one registry, one session manager and one + * set of handler instances, so they answer for the same server rather than + * for two that merely started from the same configuration. + * + * @return AssembledParts + */ + private function assemble(): array + { + return $this->parts ??= $this->resolve(); + } + + /** + * @return AssembledParts + */ + private function resolve(): array + { + $logger = $this->logger ?? new NullLogger(); + $container = $this->container ?? new Container(); + + // A configured bus needs the registry's change events, and PSR-14 hands + // the SDK a dispatcher it cannot register listeners on — so it wraps. + $eventDispatcher = null !== $this->notificationBus + ? new PublishingEventDispatcher($this->notificationBus, $this->eventDispatcher) + : $this->eventDispatcher; + $subscriptionManager = $this->subscriptionManager ?? new SessionSubscriptionManager($logger); + $sessionManager = $this->sessionManager ?? new SessionManager( + $this->sessionStore ?? new InMemorySessionStore(), + $logger, + $this->gcProbability, + $this->gcDivisor, + ); + + // ExplicitElementLoader and ReflectedElementLoader run before DiscoveryLoader so manual entries are seen first; + // DiscoveryLoader's identity check then preserves them against same-name discovered entries. + $loaders = [ + ...$this->loaders, + new ExplicitElementLoader( + $this->explicitTools, + $this->explicitResources, + $this->explicitResourceTemplates, + $this->explicitPrompts, + ), + new ReflectedElementLoader($this->tools, $this->resources, $this->resourceTemplates, $this->prompts, $logger, $this->schemaGenerator), + ]; + + if (null !== $this->discoveryBasePath) { + if (null !== $this->discoverer || class_exists(Finder::class)) { + $discoverer = $this->discoverer ?? $this->createDiscoverer($logger); + $loaders[] = new DiscoveryLoader($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs, $discoverer, $this->discoveryNamePatterns, $logger); + } else { + $logger->warning('File-based discovery requires symfony/finder. Skipping automatic discovery. Run: composer require symfony/finder'); + } + } + + $chainLoader = new ChainLoader($loaders); + + if ($this->hasCustomRegistry) { + // Builder can't inject the loader into an already-constructed instance, so load it eagerly. + // Via loadFrom(), which suppresses the change events the load would otherwise dispatch. + $registry = $this->registry; + if ($registry instanceof Registry) { + $registry->loadFrom($chainLoader); + } else { + $chainLoader->load($registry); + } + $eagerlyLoaded = true; + } else { + $registry = new Registry($eventDispatcher, $logger, loader: $chainLoader); + if (!$this->lazyLoading) { + $registry->load(); + } + $eagerlyLoaded = !$this->lazyLoading; + } + + $messageFactory = MessageFactory::make(additional: $this->extensionMessages); + + $capabilities = $this->serverCapabilities ?? $this->detectCapabilities($registry, $eagerlyLoaded, $eventDispatcher); + + // Extensions enabled via enableExtension() are folded into caller-supplied + // capabilities too, so setCapabilities() does not silently drop them. + if (null !== $this->serverCapabilities && [] !== $this->extensions) { + $capabilities = $capabilities->withExtensions($this->extensions); + } + + if (null !== $this->protocolVersion && $this->protocolVersion->isModern()) { + $logger->warning('Configured protocol version cannot be reached through the "initialize" handshake, negotiating the handshake revisions instead.', [ + 'configured' => $this->protocolVersion->value, + 'negotiable' => array_map(static fn (ProtocolVersion $v): string => $v->value, ProtocolVersion::handshakeVersions()), + ]); + } + + $serverInfo = $this->serverInfo ?? new Implementation(); + $configuration = new Configuration($serverInfo, $capabilities, $this->paginationLimit, $this->instructions, $this->protocolVersion); + $referenceHandler = $this->referenceHandler ?? new ReferenceHandler($container); + + $requestHandlers = array_merge($this->requestHandlers, [ + new Handler\Request\CallToolHandler($registry, $referenceHandler, $logger), + new Handler\Request\CompletionCompleteHandler($registry, $container, $logger), + new Handler\Request\GetPromptHandler($registry, $referenceHandler, $logger), + new Handler\Request\InitializeHandler($configuration), + new Handler\Request\ListPromptsHandler($registry, $this->paginationLimit), + new Handler\Request\ListResourcesHandler($registry, $this->paginationLimit), + new Handler\Request\ListResourceTemplatesHandler($registry, $this->paginationLimit), + new Handler\Request\ListToolsHandler($registry, $this->paginationLimit), + new Handler\Request\PingHandler(), + new Handler\Request\ReadResourceHandler($registry, $referenceHandler, $logger), + new Handler\Request\ResourceSubscribeHandler($registry, $subscriptionManager, $logger), + new Handler\Request\ResourceUnsubscribeHandler($registry, $subscriptionManager, $logger), + new Handler\Request\SetLogLevelHandler(), + ]); + + $notificationHandlers = array_merge($this->notificationHandlers, [ + new Handler\Notification\InitializedHandler(), + ]); + + return [ + 'logger' => $logger, + 'eventDispatcher' => $eventDispatcher, + 'registry' => $registry, + 'configuration' => $configuration, + 'messageFactory' => $messageFactory, + 'sessionManager' => $sessionManager, + 'requestHandlers' => $requestHandlers, + 'notificationHandlers' => $notificationHandlers, + ]; + } + + /** + * When loaded, capabilities are read from the registry. When deferred, reading it would force + * the load, so they are advertised from the configured sources instead — opaque sources (custom + * loaders, discovery) advertise all kinds, and over-advertising is harmless per MCP semantics. + */ + private function detectCapabilities(RegistryInterface $registry, bool $eagerlyLoaded, ?EventDispatcherInterface $eventDispatcher): ServerCapabilities + { + // Without a dispatcher the registry announces nothing, so there is no + // list-changed notification to advertise. + $listChanged = $eventDispatcher instanceof EventDispatcherInterface; + + if ($eagerlyLoaded) { + $hasResources = $registry->hasResources() || $registry->hasResourceTemplates(); + + return new ServerCapabilities( + tools: $registry->hasTools(), + toolsListChanged: $listChanged, + resources: $hasResources, + resourcesSubscribe: $hasResources, + resourcesListChanged: $listChanged, + prompts: $registry->hasPrompts(), + promptsListChanged: $listChanged, + logging: true, + completions: true, + extensions: $this->extensions ?: null, + ); + } + + $hasOpaqueSources = [] !== $this->loaders || null !== $this->discoveryBasePath; + $hasResources = [] !== $this->resources || [] !== $this->explicitResources || [] !== $this->resourceTemplates || [] !== $this->explicitResourceTemplates || $hasOpaqueSources; + + return new ServerCapabilities( + tools: [] !== $this->tools || [] !== $this->explicitTools || $hasOpaqueSources, + toolsListChanged: $listChanged, + resources: $hasResources, + resourcesSubscribe: $hasResources, + resourcesListChanged: $listChanged, + prompts: [] !== $this->prompts || [] !== $this->explicitPrompts || $hasOpaqueSources, + promptsListChanged: $listChanged, + logging: true, + completions: true, + extensions: $this->extensions ?: null, + ); + } + + private function createDiscoverer(LoggerInterface $logger): DiscovererInterface + { + $discoverer = new Discoverer($logger, null, $this->schemaGenerator); + + if (null !== $this->discoveryCache) { + return new CachedDiscoverer($discoverer, $this->discoveryCache, $logger); + } + + return $discoverer; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/ClientGateway.php b/admin/vendor/mcp/sdk/src/Server/ClientGateway.php new file mode 100644 index 0000000..a60e539 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/ClientGateway.php @@ -0,0 +1,466 @@ +getClientGateway(); + * // Send progress notification + * $client->notify(new ProgressNotification("Starting analysis...")); + * + * // Request LLM sampling from client + * $result = $client->sample($text); + * + * return $result->content->text; + * } + * ``` + * + * @phpstan-type SampleOptions array{ + * preferences?: ModelPreferences, + * systemPrompt?: string, + * temperature?: float, + * includeContext?: SamplingContext, + * stopSequences?: string[], + * metadata?: array, + * tools?: Tool[], + * toolChoice?: ToolChoice, + * } + * + * @author Kyrian Obikwelu + */ +class ClientGateway +{ + public function __construct( + private readonly SessionInterface $session, + ) { + } + + /** + * Send a notification to the client (fire and forget). + * + * This suspends the Fiber to let the transport flush the notification via SSE, + * then immediately resumes execution. + */ + public function notify(Notification $notification): void + { + \Fiber::suspend([ + 'type' => 'notification', + 'notification' => $notification, + 'session_id' => $this->session->getId()->toRfc4122(), + ]); + } + + /** + * Convenience method to send a logging notification to the client. + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ + public function log(LoggingLevel $level, mixed $data, ?string $logger = null): void + { + trigger_deprecation('mcp/sdk', '0.8', 'MCP logging is deprecated since protocol revision 2026-07-28 (SEP-2577); log to stderr or use OpenTelemetry instead.'); + + $this->notify(new LoggingMessageNotification($level, $data, $logger)); + } + + /** + * Convenience method to send a progress notification to the client. + */ + public function progress(float $progress, ?float $total = null, ?string $message = null): void + { + $meta = $this->session->get(Protocol::SESSION_ACTIVE_REQUEST_META, []); + $progressToken = $meta['progressToken'] ?? null; + + if (null === $progressToken) { + // Per the spec the client never asked for progress, so just bail. + return; + } + + $this->notify(new ProgressNotification($progressToken, $progress, $total, $message)); + } + + /** + * Convenience method for LLM sampling requests. + * + * @param SamplingMessage[]|TextContent|AudioContent|ImageContent|string $message The message for the LLM + * @param int $maxTokens Maximum tokens to generate + * @param int $timeout The timeout in seconds + * @param SampleOptions $options Additional sampling options (temperature, etc.) + * Context values other than `none` require the client's + * sampling.context capability; tools and toolChoice require + * the client's sampling.tools capability. + * + * @return CreateSamplingMessageResult The sampling response + * + * @throws ClientException if the client request results in an error message + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Integrate with an LLM provider's API directly instead. + */ + public function sample(array|Content|string $message, int $maxTokens = 1000, int $timeout = 120, array $options = []): CreateSamplingMessageResult + { + trigger_deprecation('mcp/sdk', '0.8', 'MCP sampling is deprecated since protocol revision 2026-07-28 (SEP-2577); integrate with an LLM provider\'s API directly instead.'); + + $preferences = $options['preferences'] ?? null; + if (null !== $preferences && !$preferences instanceof ModelPreferences) { + throw new InvalidArgumentException('The "preferences" option must be an array or an instance of ModelPreferences.'); + } + + if (\is_string($message)) { + $message = new TextContent($message); + } + if (\is_object($message) && \in_array($message::class, [TextContent::class, AudioContent::class, ImageContent::class], true)) { + $message = [new SamplingMessage(Role::User, $message)]; + } + + $request = new CreateSamplingMessageRequest( + messages: $message, + maxTokens: $maxTokens, + preferences: $preferences, + systemPrompt: $options['systemPrompt'] ?? null, + includeContext: $options['includeContext'] ?? null, + temperature: $options['temperature'] ?? null, + stopSequences: $options['stopSequences'] ?? null, + metadata: $options['metadata'] ?? null, + tools: $options['tools'] ?? null, + toolChoice: $options['toolChoice'] ?? null, + ); + + // Fail here rather than letting the client reject the request with -32602. + $request->validateToolFlow(); + + $response = $this->request($request, $timeout); + + if ($response instanceof Error) { + throw new ClientException($response); + } + + return CreateSamplingMessageResult::fromArray($response->result); + } + + /** + * Convenience method for form-mode elicitation requests. + * + * Requests additional information from the user via the client. The user can + * accept (providing the requested data), decline, or cancel the request. + * + * Serves every revision, by two different mechanics. Where the client can be + * asked while the request is open, it is: an `elicitation/create` goes out and + * this call returns its answer. From 2026-07-28 on there are no + * server-initiated requests, so the ask ends the request instead and the call + * returns once the client re-sends it with the answer — see + * {@see Stateless\ElicitationReplay} for what that costs, namely + * that the handler is entered once per ask. + * + * @param string $message A human-readable message describing what information is needed + * @param ElicitationSchema $requestedSchema The schema defining the fields to elicit from the user + * @param int $timeout The timeout in seconds; unused where the answer arrives on a later request + * @param string|null $key Names this ask, so it keeps resolving to the same answer across the rounds + * a revision without server-initiated requests needs. Defaults to the ask's + * position in the handler, which only a handler asking in a different order + * every time needs to override. + * + * @return ElicitResult The elicitation response containing the user's action and any provided content + * + * @throws ClientException if the client request results in an error message + */ + public function elicit(string $message, ElicitationSchema $requestedSchema, int $timeout = 120, ?string $key = null): ElicitResult + { + return $this->sendElicitation(ElicitRequest::forForm($message, $requestedSchema), $timeout, $key); + } + + /** + * Convenience method for url-mode elicitation requests. + * + * Sends the user to $url to complete the interaction out of band — an OAuth + * consent screen, a checkout, a form hosted elsewhere. The result carries only + * the user's action; unlike form mode there is no content to read back, so + * whatever the user did there has to be picked up through the URL's own channel. + * + * Portable across revisions on the same terms as {@see self::elicit()}. + * + * @param string|null $key names this ask across the rounds of a multi round-trip call + * + * @throws ClientException if the client request results in an error message + * @throws InvalidArgumentException if the client did not declare url-mode elicitation + */ + public function elicitUrl(string $message, string $url, int $timeout = 120, ?string $key = null): ElicitResult + { + // URL mode only exists from 2025-11-25 on, and only for clients declaring it + if (!$this->supportsElicitationUrl()) { + throw new InvalidArgumentException('The client did not declare the "elicitation.url" capability, so it cannot be sent a url-mode elicitation.'); + } + + return $this->sendElicitation(ElicitRequest::forUrl($message, $url), $timeout, $key); + } + + /** + * Request the list of filesystem roots exposed by the client. + * + * Roots are the client's "workspace folders" — the directories or files the + * server is allowed to operate on. The client answers the roots/list request + * with a list of file:// URIs. + * + * @param int $timeout The timeout in seconds + * + * @return ListRootsResult The roots exposed by the client + * + * @throws ClientException if the client request results in an error message + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Pass directories or files through tool arguments, resource URIs or server + * configuration instead. + */ + public function listRoots(int $timeout = 120): ListRootsResult + { + trigger_deprecation('mcp/sdk', '0.8', 'MCP roots are deprecated since protocol revision 2026-07-28 (SEP-2577); pass directories through tool arguments or resource URIs instead.'); + + $request = new ListRootsRequest(); + + $response = $this->request($request, $timeout); + + if ($response instanceof Error) { + throw new ClientException($response); + } + + return ListRootsResult::fromArray($response->result); + } + + /** + * Check if the connected client supports roots. + * + * Roots allow servers to ask the client for the set of directories or files + * it is permitted to operate on. This method checks the client's advertised + * capabilities to determine if roots/list requests are supported. + * + * @return bool True if the client supports roots, false otherwise + */ + public function supportsRoots(): bool + { + $capabilities = (array) $this->session->get('client_capabilities', []); + + // MCP spec: capability presence indicates support (value is typically {} or []) + return \array_key_exists('roots', $capabilities); + } + + /** + * Check if the connected client supports elicitation. + * + * Elicitation allows servers to request additional information from users + * during tool execution. This method checks the client's advertised capabilities + * to determine if elicitation/create requests are supported. + * + * @return bool True if the client supports elicitation, false otherwise + */ + public function supportsElicitation(): bool + { + $capabilities = (array) $this->session->get('client_capabilities', []); + + // MCP spec: capability presence indicates support (value is typically {} or []) + return \array_key_exists('elicitation', $capabilities); + } + + /** + * Check if the connected client supports sampling. + * + * Sampling lets a server borrow the client's model during tool execution. + * This method checks the client's advertised capabilities to determine if + * sampling/createMessage requests are supported. + * + * @return bool True if the client supports sampling, false otherwise + */ + public function supportsSampling(): bool + { + $capabilities = (array) $this->session->get('client_capabilities', []); + + // MCP spec: capability presence indicates support (value is typically {} or []) + return \array_key_exists('sampling', $capabilities); + } + + /** + * Check if the connected client supports tools during sampling. + * + * Per the spec a server MUST NOT put `tools` or `toolChoice` on a + * `sampling/createMessage` request unless the client advertised + * `sampling.tools`, so check this before passing either option to + * {@see self::sample()}. + * + * @return bool True if the client supports tool-enabled sampling, false otherwise + */ + public function supportsSamplingTools(): bool + { + return $this->hasSubCapability('sampling', 'tools'); + } + + /** + * Check if the connected client supports context inclusion during sampling. + * + * The `includeContext` values other than `none` are soft-deprecated and should + * only be sent when the client advertised `sampling.context`. + * + * @return bool True if the client supports sampling context, false otherwise + */ + public function supportsSamplingContext(): bool + { + return $this->hasSubCapability('sampling', 'context'); + } + + /** + * Check if the connected client supports url-mode elicitation. + * + * An `elicitation` capability naming no mode declares form mode — the only shape + * that existed before URL elicitation — so url mode has to be named explicitly. + * + * @return bool True if the client supports url-mode elicitation, false otherwise + */ + public function supportsElicitationUrl(): bool + { + return $this->hasSubCapability('elicitation', 'url'); + } + + /** + * Check if the connected client negotiated the given protocol extension. + * + * Extensions are advertised under `capabilities.extensions` keyed by their + * reverse-DNS id, e.g. `McpApps::EXTENSION_ID` — a server offering MCP Apps + * should check this before pointing a tool at a `ui://` resource, and fall back + * to a text-only result otherwise. + * + * @return bool True if the client advertised the extension, false otherwise + */ + public function supportsExtension(ExtensionIdentifier|string $id): bool + { + return $this->hasSubCapability('extensions', (string) $id); + } + + /** + * Sub-capabilities are declared by the presence of a (possibly empty) object, so + * only the key matters — not whatever it holds. The value arrives as an object on + * a live session and as an array once the session has round-tripped through JSON, + * hence both shapes. + */ + private function hasSubCapability(string $capability, string $name): bool + { + $capabilities = (array) $this->session->get('client_capabilities', []); + $declared = $capabilities[$capability] ?? null; + + if (\is_array($declared)) { + return \array_key_exists($name, $declared); + } + + return \is_object($declared) && property_exists($declared, $name); + } + + /** + * @throws ClientException if the client request results in an error message + */ + private function sendElicitation(ElicitRequest $request, int $timeout, ?string $key = null): ElicitResult + { + $response = $this->suspend($request, $timeout, $key); + + if ($response instanceof Error) { + throw new ClientException($response); + } + + return ElicitResult::fromArray($response->result, $request->mode); + } + + /** + * Send a request to the client and wait for a response (blocking). + * + * This suspends the Fiber and waits for the client to respond. The transport + * handles polling the session for the response and resuming the Fiber when ready. + * + * Public for {@see InputRequiredShim}, which sends the requests a handler + * embedded in an {@see \Mcp\Schema\Result\InputRequiredResult} and knows + * nothing about their kinds. Prefer the typed methods above. + * + * @param Request $request The request to send + * @param int $timeout Maximum time to wait for response (seconds) + * + * @return Response>|Error The client's response message + * + * @throws RuntimeException If Fiber support is not available + * + * @internal + */ + public function request(Request $request, int $timeout = 120): Response|Error + { + return $this->suspend($request, $timeout); + } + + /** + * Hands the request to whatever is driving this fiber and waits for its answer. + * + * @param string|null $key the name an elicitation's answer is filed under when + * the revision serving this call answers by asking + * ({@see Stateless\ElicitationReplay}); + * ignored by every leg that has a live client to ask + * + * @return Response>|Error the peer's answer + */ + private function suspend(Request $request, int $timeout, ?string $key = null): Response|Error + { + $response = \Fiber::suspend([ + 'type' => 'request', + 'request' => $request, + 'session_id' => $this->session->getId()->toRfc4122(), + 'timeout' => $timeout, + 'input_key' => $key, + ]); + + if (!$response instanceof Response && !$response instanceof Error) { + throw new RuntimeException('Transport returned an unexpected payload; expected a Response or Error message.'); + } + + return $response; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Configuration.php b/admin/vendor/mcp/sdk/src/Server/Configuration.php new file mode 100644 index 0000000..2f0bd7f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Configuration.php @@ -0,0 +1,41 @@ + + */ +class Configuration +{ + /** + * @param Implementation $serverInfo info about this MCP server application + * @param ServerCapabilities $capabilities capabilities of this MCP server application + * @param int $paginationLimit maximum number of items to return for list methods + * @param string|null $instructions instructions describing how to use the server and its features + */ + public function __construct( + public readonly Implementation $serverInfo, + public readonly ServerCapabilities $capabilities, + public readonly int $paginationLimit = 50, + public readonly ?string $instructions = null, + public readonly ?ProtocolVersion $protocolVersion = null, + ) { + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/ElementHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/ElementHandlerInterface.php new file mode 100644 index 0000000..6b08955 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/ElementHandlerInterface.php @@ -0,0 +1,23 @@ + + */ +interface ElementHandlerInterface +{ +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Notification/InitializedHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Notification/InitializedHandler.php new file mode 100644 index 0000000..2dccc89 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Notification/InitializedHandler.php @@ -0,0 +1,34 @@ + + */ +final class InitializedHandler implements NotificationHandlerInterface +{ + public function supports(Notification $notification): bool + { + return $notification instanceof InitializedNotification; + } + + public function handle(Notification $notification, SessionInterface $session): void + { + \assert($notification instanceof InitializedNotification); + + $session->set('initialized', true); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Notification/NotificationHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/Notification/NotificationHandlerInterface.php new file mode 100644 index 0000000..8746dc7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Notification/NotificationHandlerInterface.php @@ -0,0 +1,25 @@ + + */ +interface NotificationHandlerInterface +{ + public function supports(Notification $notification): bool; + + public function handle(Notification $notification, SessionInterface $session): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/PromptHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/PromptHandlerInterface.php new file mode 100644 index 0000000..4143c97 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/PromptHandlerInterface.php @@ -0,0 +1,28 @@ + + */ +interface PromptHandlerInterface extends ElementHandlerInterface +{ + /** + * @param array $arguments + */ + public function get(array $arguments, ClientGateway $gateway): mixed; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/CallToolHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/CallToolHandler.php new file mode 100644 index 0000000..72f3c21 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/CallToolHandler.php @@ -0,0 +1,182 @@ + + * + * @author Christopher Hertel + * @author Tobias Nyholm + */ +final class CallToolHandler implements RequestHandlerInterface +{ + private SchemaValidator $schemaValidator; + + public function __construct( + private readonly RegistryInterface $registry, + private readonly ReferenceHandlerInterface $referenceHandler, + private readonly LoggerInterface $logger = new NullLogger(), + ?SchemaValidator $schemaValidator = null, + ) { + $this->schemaValidator = $schemaValidator ?? new SchemaValidator($logger); + } + + public function supports(Request $request): bool + { + return $request instanceof CallToolRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof CallToolRequest); + + $toolName = $request->name; + $arguments = $request->arguments; + + $this->logger->debug('Executing tool', ['name' => $toolName, 'arguments' => $arguments]); + + try { + $reference = $this->registry->getTool($toolName); + } catch (ToolNotFoundException $e) { + $this->logger->error('Tool not found', ['name' => $toolName, 'exception' => $e]); + + // -32601 answers an unknown *method*; tools/call exists, it is the + // name in its params that does not. + return Error::forInvalidParams($e->getMessage(), $request->getId()); + } + + $inputSchema = $reference->tool->inputSchema; + $validationErrors = $this->schemaValidator->validateAgainstJsonSchema($arguments, $inputSchema); + if (!empty($validationErrors)) { + $errorMessages = []; + + foreach ($validationErrors as $errorDetail) { + $pointer = $errorDetail['pointer'] ?? ''; + $message = $errorDetail['message'] ?? 'Unknown validation error'; + $errorMessages[] = ('/' !== $pointer && '' !== $pointer ? "Property '{$pointer}': " : '').$message; + } + + $summaryMessage = "Invalid parameters for tool '{$toolName}': ".implode('; ', \array_slice($errorMessages, 0, 3)); + if (\count($errorMessages) > 3) { + $summaryMessage .= '; ...and more errors.'; + } + + return Error::forInvalidParams($summaryMessage, $request->getId(), ['validation_errors' => $validationErrors]); + } + + $arguments['_session'] = $session; + $arguments['_request'] = $request; + + $context = new RequestContext($session, $request); + + try { + $result = $this->referenceHandler->handle($reference, $arguments); + + // An ask is a result in its own right, not tool output. + if ($result instanceof InputRequiredResult) { + return new Response($request->getId(), $result); + } + + $protocolVersion = $context->getProtocolVersion(); + + $structuredContent = null; + if (!$result instanceof CallToolResult) { + $structuredContent = $reference->extractStructuredContent($result, $protocolVersion); + + if (null === $structuredContent && null !== $reference->tool->outputSchema) { + $this->logger->warning('Tool declares an "outputSchema" but returned a value that cannot be sent as "structuredContent"; the value is only carried in "content".', [ + 'name' => $toolName, + 'result_type' => get_debug_type($result), + ]); + } + + $result = new CallToolResult($reference->formatResult($result), structuredContent: $structuredContent); + } elseif ($protocolVersion->requiresObjectStructuredContent() + && null !== $result->structuredContent + && [] !== $result->structuredContent + && !self::isJsonObject($result->structuredContent) + ) { + // A tool building its own `CallToolResult` bypasses the extraction + // rules on purpose, so the value is sent as it was set — but before + // SEP-2106 only a JSON object is valid here, whether the value is a + // list or a scalar, and clients may reject it. + $this->logger->warning('Tool returned a "CallToolResult" whose "structuredContent" is not a JSON object, which the negotiated protocol revision requires; sending it unchanged.', [ + 'name' => $toolName, + 'protocol_version' => $protocolVersion->value, + 'structured_content_type' => get_debug_type($result->structuredContent), + ]); + } + + $this->logger->debug('Tool executed successfully', [ + 'name' => $toolName, + 'result_type' => \gettype($result), + 'structured_content' => $structuredContent, + ]); + + return new Response($request->getId(), $result); + } catch (MissingRequiredClientCapabilityException $e) { + // Not a tool failure — the request was unservable, and the client + // needs to retry declaring the capability. Rendered as -32021. + throw $e; + } catch (ToolCallException $e) { + $this->logger->debug(\sprintf('Error while executing tool "%s": "%s".', $toolName, $e->getMessage()), [ + 'tool' => $toolName, + 'arguments' => $arguments, + 'exception' => $e, + ]); + + $errorContent = [new TextContent($e->getMessage())]; + + return new Response($request->getId(), CallToolResult::error($errorContent)); + } catch (\Throwable $e) { + $this->logger->error('Unhandled error during tool execution', [ + 'name' => $toolName, + 'exception' => $e, + ]); + + return Error::forInternalError('Error while executing tool', $request->getId()); + } + } + + /** + * Whether a `structuredContent` value encodes as a JSON object — the only shape + * revisions predating SEP-2106 accept. + */ + private static function isJsonObject(mixed $value): bool + { + return \is_array($value) && !array_is_list($value); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/CompletionCompleteHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/CompletionCompleteHandler.php new file mode 100644 index 0000000..704116d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/CompletionCompleteHandler.php @@ -0,0 +1,112 @@ + + * + * @author Kyrian Obikwelu + */ +final class CompletionCompleteHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly ?ContainerInterface $container = null, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof CompletionCompleteRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof CompletionCompleteRequest); + + $name = $request->argument['name'] ?? ''; + $value = $request->argument['value'] ?? ''; + + try { + $providers = match (true) { + $request->ref instanceof PromptReference => $this->registry->getPrompt($request->ref->name)->completionProviders, + $request->ref instanceof ResourceReference => $this->resourceCompletionProviders($request->ref->uri), + }; + + $provider = $providers[$name] ?? null; + if (null === $provider) { + return new Response($request->getId(), new CompletionCompleteResult([])); + } + + if (\is_string($provider)) { + if (!class_exists($provider)) { + return Error::forInternalError('Invalid completion provider', $request->getId()); + } + $provider = $this->container?->has($provider) ? $this->container->get($provider) : new $provider(); + } + + if (!$provider instanceof ProviderInterface) { + return Error::forInternalError('Invalid completion provider type', $request->getId()); + } + + $completions = $provider->getCompletions($value); + $total = \count($completions); + $hasMore = $total > 100; + $paged = \array_slice($completions, 0, 100); + + return new Response($request->getId(), new CompletionCompleteResult($paged, $total, $hasMore)); + } catch (PromptNotFoundException|ResourceNotFoundException $e) { + // The reference names something the server does not have, which is + // a bad parameter rather than a missing resource. + $this->logger->warning(\sprintf('Completion requested for unknown reference: %s', $e->getMessage()), ['exception' => $e]); + + return Error::forInvalidParams($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Error while handling completion request: %s', $e->getMessage()), ['exception' => $e]); + + return Error::forInternalError('Error while handling completion request', $request->getId()); + } + } + + /** + * @return array + */ + private function resourceCompletionProviders(string $uri): array + { + $reference = $this->registry->getResource($uri); + + return $reference instanceof ResourceTemplateReference ? $reference->completionProviders : []; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/GetPromptHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/GetPromptHandler.php new file mode 100644 index 0000000..71d6f8c --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/GetPromptHandler.php @@ -0,0 +1,94 @@ + + * + * @author Tobias Nyholm + */ +final class GetPromptHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly ReferenceHandlerInterface $referenceHandler, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof GetPromptRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof GetPromptRequest); + + $promptName = $request->name; + $arguments = $request->arguments ?? []; + + try { + $reference = $this->registry->getPrompt($promptName); + + $arguments['_session'] = $session; + $arguments['_request'] = $request; + + $result = $this->referenceHandler->handle($reference, $arguments); + + // An ask is a result in its own right, not prompt content. + if ($result instanceof InputRequiredResult) { + return new Response($request->getId(), $result); + } + + $formatted = $reference->formatResult($result); + + return new Response($request->getId(), new GetPromptResult($formatted)); + } catch (MissingRequiredClientCapabilityException $e) { + // Not a handler failure — the request was unservable, and the client + // needs to retry declaring the capability. Rendered as -32021. + throw $e; + } catch (PromptGetException $e) { + $this->logger->error(\sprintf('Error while handling prompt "%s": "%s".', $promptName, $e->getMessage()), ['exception' => $e]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } catch (PromptNotFoundException $e) { + $this->logger->error('Prompt not found', ['prompt_name' => $promptName, 'exception' => $e]); + + // An unknown prompt name is a bad parameter, not a missing + // resource: -32002 was never the code for this. + return Error::forInvalidParams($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Unexpected error while handling prompt "%s": "%s".', $promptName, $e->getMessage()), ['exception' => $e]); + + return Error::forInternalError('Error while handling prompt', $request->getId()); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/InitializeHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/InitializeHandler.php new file mode 100644 index 0000000..4461ae4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/InitializeHandler.php @@ -0,0 +1,106 @@ + + * + * @author Christopher Hertel + */ +final class InitializeHandler implements RequestHandlerInterface +{ + public function __construct( + public readonly ?Configuration $configuration = null, + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof InitializeRequest; + } + + /** + * @return Response + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof InitializeRequest); + + $session->set('client_info', $request->clientInfo->jsonSerialize()); + $session->set('client_capabilities', $request->capabilities->jsonSerialize()); + + $negotiated = $this->negotiate($request->protocolVersion); + $session->set('protocol_version', $negotiated->value); + + return new Response( + $request->getId(), + new InitializeResult( + $this->configuration->capabilities ?? new ServerCapabilities(), + $this->configuration->serverInfo ?? new Implementation(), + $this->configuration?->instructions, + null, + $negotiated, + ), + ); + } + + /** + * Picks the protocol version to answer an `initialize` handshake with. + * + * If the client asked for a version this server supports, the spec requires + * responding with that exact version. Otherwise the server counter-offers + * the newest version it does support and leaves it to the client to decide + * whether it can continue on that revision or must disconnect. + */ + private function negotiate(string $requested): ProtocolVersion + { + $supported = $this->supportedVersions(); + $version = ProtocolVersion::tryFrom($requested); + + if (null !== $version && \in_array($version, $supported, true)) { + return $version; + } + + return $supported[\count($supported) - 1]; + } + + /** + * Versions this server is willing to negotiate over `initialize`. + * + * A version configured on the server pins the handshake to exactly that + * revision. Modern revisions are never offered here: they have no + * `initialize` at all, so a client that reached this handler cannot speak + * one, and answering with it would leave the connection unusable. + * + * @return non-empty-list + */ + private function supportedVersions(): array + { + $configured = $this->configuration?->protocolVersion; + + if (null !== $configured && !$configured->isModern()) { + return [$configured]; + } + + return ProtocolVersion::handshakeVersions(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListPromptsHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListPromptsHandler.php new file mode 100644 index 0000000..ee28756 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListPromptsHandler.php @@ -0,0 +1,56 @@ + + * + * @author Tobias Nyholm + */ +final class ListPromptsHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly int $pageSize = 20, + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ListPromptsRequest; + } + + /** + * @return Response + * + * @throws InvalidCursorException + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof ListPromptsRequest); + + $page = $this->registry->getPrompts($this->pageSize, $request->cursor); + + return new Response( + $request->getId(), + new ListPromptsResult($page->references, $page->nextCursor), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourceTemplatesHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourceTemplatesHandler.php new file mode 100644 index 0000000..5360f75 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourceTemplatesHandler.php @@ -0,0 +1,56 @@ + + * + * @author Christopher Hertel + */ +final class ListResourceTemplatesHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly int $pageSize = 20, + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ListResourceTemplatesRequest; + } + + /** + * @return Response + * + * @throws InvalidCursorException + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof ListResourceTemplatesRequest); + + $page = $this->registry->getResourceTemplates($this->pageSize, $request->cursor); + + return new Response( + $request->getId(), + new ListResourceTemplatesResult($page->references, $page->nextCursor), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourcesHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourcesHandler.php new file mode 100644 index 0000000..d1d6830 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListResourcesHandler.php @@ -0,0 +1,56 @@ + + * + * @author Tobias Nyholm + */ +final class ListResourcesHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly int $pageSize = 20, + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ListResourcesRequest; + } + + /** + * @return Response + * + * @throws InvalidCursorException + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof ListResourcesRequest); + + $page = $this->registry->getResources($this->pageSize, $request->cursor); + + return new Response( + $request->getId(), + new ListResourcesResult($page->references, $page->nextCursor), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListToolsHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListToolsHandler.php new file mode 100644 index 0000000..007bdd5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ListToolsHandler.php @@ -0,0 +1,57 @@ + + * + * @author Christopher Hertel + * @author Tobias Nyholm + */ +final class ListToolsHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly int $pageSize = 20, + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ListToolsRequest; + } + + /** + * @return Response + * + * @throws InvalidCursorException When the cursor is invalid + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof ListToolsRequest); + + $page = $this->registry->getTools($this->pageSize, $request->cursor); + + return new Response( + $request->getId(), + new ListToolsResult($page->references, $page->nextCursor), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/PingHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/PingHandler.php new file mode 100644 index 0000000..507680f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/PingHandler.php @@ -0,0 +1,41 @@ + + * + * @author Christopher Hertel + */ +final class PingHandler implements RequestHandlerInterface +{ + public function supports(Request $request): bool + { + return $request instanceof PingRequest; + } + + /** + * @return Response + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof PingRequest); + + return new Response($request->getId(), new EmptyResult()); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ReadResourceHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ReadResourceHandler.php new file mode 100644 index 0000000..60755f8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ReadResourceHandler.php @@ -0,0 +1,118 @@ + + * + * @author Tobias Nyholm + */ +final class ReadResourceHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $referenceProvider, + private readonly ReferenceHandlerInterface $referenceHandler, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ReadResourceRequest; + } + + /** + * @return Response|Error + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof ReadResourceRequest); + + $uri = $request->uri; + + $this->logger->debug('Reading resource', ['uri' => $uri]); + + try { + $reference = $this->referenceProvider->getResource($uri); + + $arguments = [ + 'uri' => $uri, + '_session' => $session, + '_request' => $request, + ]; + + if ($reference instanceof ResourceTemplateReference) { + $variables = $reference->extractVariables($uri); + $arguments = array_merge($arguments, $variables); + + $result = $this->referenceHandler->handle($reference, $arguments); + + // An ask is a result in its own right, not resource contents; + // and a handler that built the whole result keeps what it + // decided, caching hints included. + if ($result instanceof InputRequiredResult || $result instanceof ReadResourceResult) { + return new Response($request->getId(), $result); + } + + $formatted = $reference->formatResult($result, $uri, $reference->resourceTemplate->mimeType); + } else { + $result = $this->referenceHandler->handle($reference, $arguments); + + if ($result instanceof InputRequiredResult || $result instanceof ReadResourceResult) { + return new Response($request->getId(), $result); + } + + $formatted = $reference->formatResult($result, $uri, $reference->resource->mimeType); + } + + return new Response($request->getId(), new ReadResourceResult($formatted)); + } catch (MissingRequiredClientCapabilityException $e) { + // Not a handler failure — the request was unservable, and the client + // needs to retry declaring the capability. Rendered as -32021. + throw $e; + } catch (ResourceReadException $e) { + $this->logger->error(\sprintf('Error while reading resource "%s": "%s".', $uri, $e->getMessage()), ['exception' => $e]); + + return Error::forInternalError($e->getMessage(), $request->getId()); + } catch (ResourceNotFoundException $e) { + $this->logger->error('Resource not found', ['uri' => $uri, 'exception' => $e]); + + // SEP-2164 retired -32002 in favour of the JSON-RPC code that + // already meant this. Older peers still expect the old one, so the + // revision answering the request decides. + return (new RequestContext($session, $request))->getProtocolVersion()->usesInvalidParamsForResourceNotFound() + ? Error::forInvalidParams($e->getMessage(), $request->getId(), ['uri' => $uri]) + : Error::forResourceNotFound($e->getMessage(), $request->getId()); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Unexpected error while reading resource "%s": "%s".', $uri, $e->getMessage()), ['exception' => $e]); + + return Error::forInternalError('Error while reading resource', $request->getId()); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/RequestHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/RequestHandlerInterface.php new file mode 100644 index 0000000..9a9c013 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/RequestHandlerInterface.php @@ -0,0 +1,36 @@ + + */ +interface RequestHandlerInterface +{ + public function supports(Request $request): bool; + + /** + * @return Response|Error + */ + public function handle(Request $request, SessionInterface $session): Response|Error; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceSubscribeHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceSubscribeHandler.php new file mode 100644 index 0000000..1e8147b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceSubscribeHandler.php @@ -0,0 +1,75 @@ + + * + * @author Larry Sule-balogun + */ +final class ResourceSubscribeHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly SubscriptionManagerInterface $subscriptionManager, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ResourceSubscribeRequest; + } + + /** + * @throws InvalidArgumentException + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof ResourceSubscribeRequest); + + $uri = $request->uri; + + try { + $this->registry->getResource($uri); + } catch (ResourceNotFoundException $e) { + $this->logger->error('Resource not found', ['uri' => $uri, 'exception' => $e]); + + return (new RequestContext($session, $request))->getProtocolVersion()->usesInvalidParamsForResourceNotFound() + ? Error::forInvalidParams($e->getMessage(), $request->getId(), ['uri' => $uri]) + : Error::forResourceNotFound($e->getMessage(), $request->getId()); + } + + $this->logger->debug('Subscribing to resource', ['uri' => $uri]); + + $this->subscriptionManager->subscribe($session, $uri); + + return new Response( + $request->getId(), + new EmptyResult(), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceUnsubscribeHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceUnsubscribeHandler.php new file mode 100644 index 0000000..522c512 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/ResourceUnsubscribeHandler.php @@ -0,0 +1,75 @@ + + * + * @author Larry Sule-balogun + */ +final class ResourceUnsubscribeHandler implements RequestHandlerInterface +{ + public function __construct( + private readonly RegistryInterface $registry, + private readonly SubscriptionManagerInterface $subscriptionManager, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function supports(Request $request): bool + { + return $request instanceof ResourceUnsubscribeRequest; + } + + /** + * @throws InvalidArgumentException + */ + public function handle(Request $request, SessionInterface $session): Response|Error + { + \assert($request instanceof ResourceUnsubscribeRequest); + + $uri = $request->uri; + + try { + $this->registry->getResource($uri); + } catch (ResourceNotFoundException $e) { + $this->logger->error('Resource not found', ['uri' => $uri, 'exception' => $e]); + + return (new RequestContext($session, $request))->getProtocolVersion()->usesInvalidParamsForResourceNotFound() + ? Error::forInvalidParams($e->getMessage(), $request->getId(), ['uri' => $uri]) + : Error::forResourceNotFound($e->getMessage(), $request->getId()); + } + + $this->logger->debug('Unsubscribing from resource', ['uri' => $uri]); + + $this->subscriptionManager->unsubscribe($session, $uri); + + return new Response( + $request->getId(), + new EmptyResult(), + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/Request/SetLogLevelHandler.php b/admin/vendor/mcp/sdk/src/Server/Handler/Request/SetLogLevelHandler.php new file mode 100644 index 0000000..a04d22b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/Request/SetLogLevelHandler.php @@ -0,0 +1,52 @@ + + * + * @author Adam Jamiu + * + * @deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. + * Log to stderr (stdio) or use OpenTelemetry instead. + */ +final class SetLogLevelHandler implements RequestHandlerInterface +{ + public function supports(Request $request): bool + { + return $request instanceof SetLogLevelRequest; + } + + /** + * @return Response + */ + public function handle(Request $request, SessionInterface $session): Response + { + \assert($request instanceof SetLogLevelRequest); + + $session->set(Protocol::SESSION_LOGGING_LEVEL, $request->level->value); + + return new Response($request->getId(), new EmptyResult()); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/ResourceHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/ResourceHandlerInterface.php new file mode 100644 index 0000000..7ca533a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/ResourceHandlerInterface.php @@ -0,0 +1,25 @@ + + */ +interface ResourceHandlerInterface extends ElementHandlerInterface +{ + public function read(string $uri, ClientGateway $gateway): mixed; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/ResourceTemplateHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/ResourceTemplateHandlerInterface.php new file mode 100644 index 0000000..892e439 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/ResourceTemplateHandlerInterface.php @@ -0,0 +1,28 @@ + + */ +interface ResourceTemplateHandlerInterface extends ElementHandlerInterface +{ + /** + * @param array $variables + */ + public function read(string $uri, array $variables, ClientGateway $gateway): mixed; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Handler/ToolHandlerInterface.php b/admin/vendor/mcp/sdk/src/Server/Handler/ToolHandlerInterface.php new file mode 100644 index 0000000..4f2ee3e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Handler/ToolHandlerInterface.php @@ -0,0 +1,28 @@ + + */ +interface ToolHandlerInterface extends ElementHandlerInterface +{ + /** + * @param array $arguments + */ + public function execute(array $arguments, ClientGateway $gateway): mixed; +} diff --git a/admin/vendor/mcp/sdk/src/Server/InputRequiredShim.php b/admin/vendor/mcp/sdk/src/Server/InputRequiredShim.php new file mode 100644 index 0000000..9dff786 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/InputRequiredShim.php @@ -0,0 +1,228 @@ + + */ +final class InputRequiredShim +{ + /** + * Re-entries per originating request. Deliberately below the modern + * client driver's allowance: this loop holds a live request open. + */ + public const DEFAULT_MAX_ROUNDS = 8; + + /** Seconds to wait for one answer. Legs are human-paced, so the protocol's 120s default is wrong here. */ + public const DEFAULT_ROUND_TIMEOUT = 600; + + public function __construct( + private readonly int $maxRounds = self::DEFAULT_MAX_ROUNDS, + private readonly int $roundTimeout = self::DEFAULT_ROUND_TIMEOUT, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * Runs a handler to a result the client can be given. + * + * Returns whatever the handler returned when it asks for nothing, which is + * every call that is not multi round-trip. + * + * @param Response|Error $result what the handler returned on its first entry + * @param RequestHandlerInterface> $handler the handler that produced it + * + * @return Response|Error + */ + public function fulfill( + Response|Error $result, + RequestHandlerInterface $handler, + Request $request, + SessionInterface $session, + ?RequestStateCodec $codec, + ): Response|Error { + $round = 0; + + while (($ask = self::askOf($result)) instanceof InputRequiredResult) { + if (++$round > $this->maxRounds) { + $this->logger->warning('A handler kept asking for input past the round limit; the call was failed instead.', [ + 'method' => $request::getMethod(), + 'rounds' => $this->maxRounds, + ]); + + return Error::forInternalError( + \sprintf('The server asked for input more than %d times without reaching a result.', $this->maxRounds), + $request->getId(), + ); + } + + if (null !== $refusal = $this->refuseUndeclared($ask, $request, $session)) { + return $refusal; + } + + try { + $session->set(InputContext::class, new InputContext( + $this->collect($ask, $session), + self::payloadOf($ask, $codec), + )); + } catch (RequestStateException $e) { + $this->logger->error('A handler minted a requestState this server cannot verify.', ['exception' => $e]); + + return Error::forInternalError('The server could not carry its own state across a round of input.', $request->getId()); + } + + $result = $handler->handle($request, $session); + } + + return $result; + } + + /** + * Sends each embedded request and keeps the answer under the key it was + * asked under. + * + * Answers are stored as the raw result arrays {@see InputContext} parses, + * so this needs to know nothing about the kinds it is carrying — which is + * also why an extension's future kind rides through unchanged. + * + * @return array + */ + private function collect(InputRequiredResult $ask, SessionInterface $session): array + { + $gateway = new ClientGateway($session); + $responses = []; + + foreach ($ask->inputRequests as $key => $embedded) { + $answer = $gateway->request($embedded, $this->roundTimeout); + + if ($answer instanceof Error) { + // Not fatal to the call: a client that refuses one ask has + // answered it, and the handler decides what that means. + $this->logger->info('The client failed an input request; the handler is re-entered without it.', [ + 'key' => $key, + 'error' => $answer->message, + ]); + + continue; + } + + $responses[$key] = $answer->result; + } + + return $responses; + } + + /** + * Refuses an ask the client never said it could answer, the way the modern + * era does — rather than sending a request that can only come back as an + * error. + */ + private function refuseUndeclared(InputRequiredResult $ask, Request $request, SessionInterface $session): ?Error + { + $declared = ClientCapabilities::fromArray((array) $session->get('client_capabilities', [])); + $missing = InputRequestCapabilities::missing($ask, $declared); + + if (null === $missing) { + return null; + } + + $this->logger->warning('A handler asked for input the client did not declare it could provide; the ask was replaced with -32021.', [ + 'method' => $request::getMethod(), + 'required' => $missing->jsonSerialize(), + ]); + + return Error::forMissingRequiredClientCapability( + 'The server needs input this client did not declare it can provide.', + $missing, + $request->getId(), + ); + } + + /** + * The ask a handler returned, if it returned one. + * + * @param Response|Error $result + */ + private static function askOf(Response|Error $result): ?InputRequiredResult + { + return $result instanceof Response && $result->result instanceof InputRequiredResult + ? $result->result + : null; + } + + /** + * The state the handler sealed last round, verified. + * + * Verified rather than trusted even though it never left this process: the + * handler reads it back through the same accessor either era, so it has to + * have been through the same check. + * + * @return array + * + * @throws RequestStateException when a state is present but does not verify + */ + private static function payloadOf(InputRequiredResult $ask, ?RequestStateCodec $codec): array + { + if (null === $ask->requestState) { + return []; + } + + if (null === $codec) { + throw new RequestStateException('mac'); + } + + return $codec->verify($ask->requestState); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/NativeClock.php b/admin/vendor/mcp/sdk/src/Server/NativeClock.php new file mode 100644 index 0000000..fc18e4e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/NativeClock.php @@ -0,0 +1,22 @@ + + * @author Kyrian Obikwelu + */ +class Protocol +{ + /** Session key for request ID counter */ + private const SESSION_REQUEST_ID_COUNTER = '_mcp.request_id_counter'; + + /** Session key for pending outgoing requests */ + private const SESSION_PENDING_REQUESTS = '_mcp.pending_requests'; + + /** Session key for incoming client responses */ + private const SESSION_RESPONSES = '_mcp.responses'; + + /** Session key for outgoing message queue */ + private const SESSION_OUTGOING_QUEUE = '_mcp.outgoing_queue'; + + /** Session key for active request meta */ + public const SESSION_ACTIVE_REQUEST_META = '_mcp.active_request_meta'; + + public const SESSION_LOGGING_LEVEL = '_mcp.logging_level'; + + /** + * Deliberately generic: unexpected throwables carry internal details such as file paths, class + * names and argument types, which must not be handed to the peer. The full exception is logged. + */ + private const INTERNAL_ERROR_MESSAGE = 'Internal server error.'; + + /** + * @param array>> $requestHandlers + * @param array $notificationHandlers + */ + public function __construct( + private readonly array $requestHandlers, + private readonly array $notificationHandlers, + private readonly MessageFactory $messageFactory, + private readonly SessionManagerInterface $sessionManager, + private readonly LoggerInterface $logger = new NullLogger(), + private readonly ?EventDispatcherInterface $eventDispatcher = null, + private readonly ?InputRequiredShim $inputRequiredShim = null, + private readonly ?RequestStateCodec $requestStateCodec = null, + ) { + } + + /** + * Connect this protocol to transport. + * + * The protocol takes ownership of the transport and sets up all callbacks. + * + * @param TransportInterface $transport + */ + public function connect(TransportInterface $transport): void + { + $transport->onMessage($this->processInput(...)); + + $transport->onSessionEnd($this->destroySession(...)); + + $transport->setOutgoingMessagesProvider($this->consumeOutgoingMessages(...)); + + $transport->setPendingRequestsProvider($this->getPendingRequests(...)); + + $transport->setResponseFinder($this->checkResponse(...)); + + $transport->setFiberYieldHandler($this->handleFiberYield(...)); + + $this->logger->info('Protocol connected to transport', ['transport' => $transport::class]); + } + + /** + * Handle an incoming message from the transport. + * + * This is called by the transport whenever ANY message arrives. + * + * @param TransportInterface $transport + */ + public function processInput(TransportInterface $transport, string $input, ?Uuid $sessionId): void + { + // Last line of defense: a malformed message must never escape as a PHP error and take the + // server process down. + try { + $this->doProcessInput($transport, $input, $sessionId); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Uncaught exception while processing input: %s', $e->getMessage()), ['exception' => $e]); + + // Only a request may be answered. Replying to a notification would violate JSON-RPC, + // and the failure has already been logged. + if (null === $id = self::findResponseId($input)) { + return; + } + + try { + $this->sendResponse($transport, Error::forInternalError(self::INTERNAL_ERROR_MESSAGE, $id), null); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Failed to send internal error response: %s', $e->getMessage()), ['exception' => $e]); + } + } + } + + /** + * Determines the id an unprocessable input has to be answered under. + * + * Returns null when the input carries no request at all, in which case it consists of + * notifications only and JSON-RPC forbids answering it. A batch resolves to the empty id + * because its failure cannot be attributed to one of its requests. + */ + private static function findResponseId(string $input): string|int|null + { + try { + $data = json_decode($input, true, flags: \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + return null; + } + + if (!\is_array($data)) { + return null; + } + + if (!array_is_list($data)) { + $id = $data['id'] ?? null; + + return \is_string($id) || \is_int($id) ? $id : null; + } + + foreach ($data as $message) { + if (\is_array($message) && isset($message['id'])) { + return ''; + } + } + + return null; + } + + /** + * @param TransportInterface $transport + */ + private function doProcessInput(TransportInterface $transport, string $input, ?Uuid $sessionId): void + { + $this->logger->info('Received message to process.', ['message' => $input]); + + $this->sessionManager->gc(); + + try { + $messages = $this->messageFactory->create($input); + } catch (\JsonException $e) { + $this->logger->warning('Failed to decode json message.', ['exception' => $e]); + $error = Error::forParseError($e->getMessage()); + $this->sendResponse($transport, $error, null); + + return; + } + + $session = $this->resolveSession($transport, $sessionId, $messages); + if (null === $session) { + return; + } + + foreach ($messages as $message) { + // Guarded per message so one faulty message cannot suppress the rest of a batch. + try { + if ($message instanceof InvalidInputMessageException) { + $this->handleInvalidMessage($transport, $message, $session); + } elseif ($message instanceof Request) { + $this->handleRequest($transport, $message, $session); + } elseif ($message instanceof Response || $message instanceof Error) { + $this->handleResponse($message, $session); + } elseif ($message instanceof Notification) { + $this->handleNotification($message, $session); + } + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Uncaught exception while handling message: %s', $e->getMessage()), ['exception' => $e]); + + // Only a request may be answered; a notification or a response must not produce one. + if ($message instanceof Request) { + $error = Error::forInternalError(self::INTERNAL_ERROR_MESSAGE, $message->getId()); + $this->sendResponse($transport, $error, $session); + } + } + } + + $session->save(); + } + + /** + * Handle an invalid message from the transport. + * + * @param TransportInterface $transport + */ + private function handleInvalidMessage(TransportInterface $transport, InvalidInputMessageException $exception, SessionInterface $session): void + { + $this->logger->warning('Failed to create message.', ['exception' => $exception]); + + $error = Error::forInvalidRequest($exception->getMessage(), $exception->getRequestId()); + $this->sendResponse($transport, $error, $session); + } + + /** + * Dispatches an event through the event dispatcher if available. + * + * @template T of object + * + * @param T $event + * + * @return T + */ + private function dispatchEvent(object $event): object + { + return $this->eventDispatcher?->dispatch($event) ?? $event; + } + + /** + * Handle a request from the transport. + * + * @param TransportInterface $transport + */ + private function handleRequest(TransportInterface $transport, Request $request, SessionInterface $session): void + { + $this->logger->info('Handling request.', ['request' => $request]); + + $session->set(self::SESSION_ACTIVE_REQUEST_META, $request->getMeta()); + + // A request starts with nothing behind it: the shim fills this in as it + // collects answers, and clearing it here is what keeps one request's + // round from being read as another's. + $session->set(InputContext::class, null); + + if (null !== $this->requestStateCodec) { + $session->set(RequestStateCodec::class, $this->requestStateCodec); + } + + $event = $this->dispatchEvent(new RequestEvent($request, $session)); + $request = $event->getRequest(); + + $handlerFound = false; + + foreach ($this->requestHandlers as $handler) { + if (!$handler->supports($request)) { + continue; + } + + $handlerFound = true; + + try { + $shim = $this->inputRequiredShim; + $codec = $this->requestStateCodec; + + // One fiber for the whole exchange: with the shim, the handler + // re-enters inside it each round rather than needing a new one. + /** @var McpFiber $fiber */ + $fiber = new \Fiber(static function () use ($handler, $request, $session, $shim, $codec): Response|Error { + $result = $handler->handle($request, $session); + + return $shim?->fulfill($result, $handler, $request, $session, $codec) ?? $result; + }); + + $result = $fiber->start(); + + if ($fiber->isSuspended()) { + if (\is_array($result) && isset($result['type'])) { + if ('notification' === $result['type']) { + $notification = $result['notification']; + $this->sendNotification($notification, $session); + } elseif ('request' === $result['type']) { + $request = $result['request']; + $timeout = $result['timeout'] ?? 120; + $this->sendRequest($request, $timeout, $session); + } + } + + $transport->attachFiberToSession($fiber, $session->getId()); + + return; + } + $finalResult = $fiber->getReturn(); + + if ($finalResult instanceof Response) { + $responseEvent = $this->dispatchEvent(new ResponseEvent($finalResult, $request, $session)); + $finalResult = $responseEvent->getResponse(); + } elseif ($finalResult instanceof Error) { + $errorEvent = $this->dispatchEvent(new ErrorEvent($finalResult, $request, $session, null)); + $finalResult = $errorEvent->getError(); + } + + $this->sendResponse($transport, $finalResult, $session); + } catch (\InvalidArgumentException $e) { + $this->logger->warning(\sprintf('Invalid argument: %s', $e->getMessage()), ['exception' => $e]); + + $error = Error::forInvalidParams($e->getMessage(), $request->getId()); + $errorEvent = $this->dispatchEvent(new ErrorEvent($error, $request, $session, $e)); + $error = $errorEvent->getError(); + + $this->sendResponse($transport, $error, $session); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Uncaught exception: %s', $e->getMessage()), ['exception' => $e]); + + $error = Error::forInternalError(self::INTERNAL_ERROR_MESSAGE, $request->getId()); + $errorEvent = $this->dispatchEvent(new ErrorEvent($error, $request, $session, $e)); + $error = $errorEvent->getError(); + + $this->sendResponse($transport, $error, $session); + } + + break; + } + + if (!$handlerFound) { + $error = Error::forMethodNotFound(\sprintf('No handler found for method "%s".', $request::getMethod()), $request->getId()); + $errorEvent = $this->dispatchEvent(new ErrorEvent($error, $request, $session, null)); + $error = $errorEvent->getError(); + + $this->sendResponse($transport, $error, $session); + } + } + + /** + * @param Response>|Error $response + */ + private function handleResponse(Response|Error $response, SessionInterface $session): void + { + $this->logger->info('Handling response from client.', ['response' => $response]); + + $messageId = $response->getId(); + + if (null === $messageId) { + $this->logger->warning('Received an id-less error response from client; cannot correlate it to a pending request.', ['response' => $response->jsonSerialize()]); + + return; + } + + $session->set(self::SESSION_RESPONSES.".{$messageId}", $response->jsonSerialize()); + $session->forget(self::SESSION_ACTIVE_REQUEST_META); + + $this->logger->info('Client response stored in session', [ + 'message_id' => $messageId, + ]); + } + + private function handleNotification(Notification $notification, SessionInterface $session): void + { + $this->logger->info('Handling notification.', ['notification' => $notification]); + + $event = $this->dispatchEvent(new NotificationEvent($notification, $session)); + $notification = $event->getNotification(); + + foreach ($this->notificationHandlers as $handler) { + if (!$handler->supports($notification)) { + continue; + } + + try { + $handler->handle($notification, $session); + } catch (\Throwable $e) { + $this->logger->error(\sprintf('Error while handling notification: %s', $e->getMessage()), ['exception' => $e]); + } + } + } + + /** + * Sends a request to the client and returns the request ID. + */ + public function sendRequest(Request $request, int $timeout, SessionInterface $session): int + { + $counter = $session->get(self::SESSION_REQUEST_ID_COUNTER, 1000); + $requestId = $counter++; + $session->set(self::SESSION_REQUEST_ID_COUNTER, $counter); + + $requestWithId = $request->withId($requestId); + + $this->logger->info('Queueing server request to client', [ + 'request_id' => $requestId, + 'method' => $request::getMethod(), + ]); + + $pending = $session->get(self::SESSION_PENDING_REQUESTS, []); + $pending[$requestId] = [ + 'request_id' => $requestId, + 'timeout' => $timeout, + 'timestamp' => time(), + ]; + $session->set(self::SESSION_PENDING_REQUESTS, $pending); + + $this->queueOutgoing($requestWithId, ['type' => 'request'], $session); + + return $requestId; + } + + /** + * Queues a notification for later delivery. + */ + public function sendNotification(Notification $notification, SessionInterface $session): void + { + $this->logger->info('Queueing server notification to client', [ + 'method' => $notification::getMethod(), + ]); + + $this->queueOutgoing($notification, ['type' => 'notification'], $session); + } + + /** + * Sends a response either immediately or queued for later delivery. + * + * @param TransportInterface $transport + * @param Response>|Error $response + * @param array $context + */ + private function sendResponse(TransportInterface $transport, Response|Error $response, ?SessionInterface $session, array $context = []): void + { + if (null === $session) { + $this->logger->info('Sending immediate response', [ + 'response_id' => $response->getId(), + ]); + + try { + $encoded = json_encode($response, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->logger->error('Failed to encode response to JSON.', [ + 'message_id' => $response->getId(), + 'exception' => $e, + ]); + + $fallbackError = new Error( + id: $response->getId(), + code: Error::INTERNAL_ERROR, + message: 'Response could not be encoded to JSON' + ); + + $encoded = json_encode($fallbackError, \JSON_THROW_ON_ERROR); + } + + $context['type'] = 'response'; + $transport->send($encoded, $context); + } else { + $this->logger->info('Queueing server response', [ + 'response_id' => $response->getId(), + ]); + + $this->queueOutgoing($response, ['type' => 'response'], $session); + } + } + + /** + * Helper to queue outgoing messages in session. + * + * @param Request|Notification|Response>|Error $message + * @param array $context + */ + private function queueOutgoing(Request|Notification|Response|Error $message, array $context, SessionInterface $session): void + { + try { + $encoded = json_encode($message, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->logger->error('Failed to encode message to JSON.', [ + 'exception' => $e, + ]); + + return; + } + + $queue = $session->get(self::SESSION_OUTGOING_QUEUE, []); + $queue[] = [ + 'message' => $encoded, + 'context' => $context, + ]; + $session->set(self::SESSION_OUTGOING_QUEUE, $queue); + } + + /** + * Consume (get and clear) all outgoing messages for a session. + * + * @return array}> + */ + public function consumeOutgoingMessages(Uuid $sessionId): array + { + $session = $this->sessionManager->createWithId($sessionId); + $queue = $session->get(self::SESSION_OUTGOING_QUEUE, []); + $session->set(self::SESSION_OUTGOING_QUEUE, []); + $session->save(); + + return $queue; + } + + /** + * Check for a response to a specific request ID. + * + * When a response is found, it is removed from the session, and the + * corresponding pending request is also cleared. + */ + /** + * @return Response>|Error|null + */ + public function checkResponse(int $requestId, Uuid $sessionId): Response|Error|null + { + $session = $this->sessionManager->createWithId($sessionId); + $responseData = $session->get(self::SESSION_RESPONSES.".{$requestId}"); + + if (null === $responseData) { + return null; + } + + $this->logger->debug('Found and consuming client response.', [ + 'request_id' => $requestId, + 'session_id' => $sessionId->toRfc4122(), + ]); + + $session->set(self::SESSION_RESPONSES.".{$requestId}", null); + $pending = $session->get(self::SESSION_PENDING_REQUESTS, []); + unset($pending[$requestId]); + $session->set(self::SESSION_PENDING_REQUESTS, $pending); + $session->save(); + + try { + if (isset($responseData['error'])) { + return Error::fromArray($responseData); + } + + return Response::fromArray($responseData); + } catch (\Throwable $e) { + $this->logger->error('Failed to reconstruct client response from session.', [ + 'request_id' => $requestId, + 'exception' => $e, + 'response_data' => $responseData, + ]); + + return null; + } + } + + /** + * Get pending requests for a session. + * + * @return array The pending requests + */ + public function getPendingRequests(Uuid $sessionId): array + { + $session = $this->sessionManager->createWithId($sessionId); + + return $session->get(self::SESSION_PENDING_REQUESTS, []); + } + + /** + * Handle values yielded by Fibers during transport-managed resumes. + * + * @param FiberSuspend|null $yieldedValue + */ + public function handleFiberYield(mixed $yieldedValue, ?Uuid $sessionId): void + { + if (!$sessionId) { + $this->logger->warning('Fiber yielded value without associated session context.'); + + return; + } + + if (!\is_array($yieldedValue) || !isset($yieldedValue['type'])) { + $this->logger->warning('Fiber yielded unexpected payload.', [ + 'payload' => $yieldedValue, + 'session_id' => $sessionId->toRfc4122(), + ]); + + return; + } + + $session = $this->sessionManager->createWithId($sessionId); + + $payloadSessionId = $yieldedValue['session_id'] ?? null; + if (\is_string($payloadSessionId) && $payloadSessionId !== $sessionId->toRfc4122()) { + $this->logger->warning('Fiber yielded payload with mismatched session ID.', [ + 'payload_session_id' => $payloadSessionId, + 'expected_session_id' => $sessionId->toRfc4122(), + ]); + } + + try { + if ('notification' === $yieldedValue['type']) { + $notification = $yieldedValue['notification'] ?? null; + if (!$notification instanceof Notification) { + $this->logger->warning('Fiber yielded notification without Notification instance.', [ + 'payload' => $yieldedValue, + ]); + + return; + } + + $this->sendNotification($notification, $session); + } elseif ('request' === $yieldedValue['type']) { + $request = $yieldedValue['request'] ?? null; + if (!$request instanceof Request) { + $this->logger->warning('Fiber yielded request without Request instance.', [ + 'payload' => $yieldedValue, + ]); + + return; + } + + $timeout = isset($yieldedValue['timeout']) ? (int) $yieldedValue['timeout'] : 120; + $this->sendRequest($request, $timeout, $session); + } else { + $this->logger->warning('Fiber yielded unknown operation type.', [ + 'type' => $yieldedValue['type'], + ]); + } + } finally { + $session->save(); + } + } + + /** + * @param array $messages + */ + private function hasInitializeRequest(array $messages): bool + { + foreach ($messages as $message) { + if ($message instanceof InitializeRequest) { + return true; + } + } + + return false; + } + + /** + * Resolves and validates the session based on the request context. + * + * @param TransportInterface $transport + * @param Uuid|null $sessionId The session ID from the transport + * @param array $messages The parsed messages + */ + private function resolveSession(TransportInterface $transport, ?Uuid $sessionId, array $messages): ?SessionInterface + { + if ($this->hasInitializeRequest($messages)) { + // Spec: An initialize request must not be part of a batch. + if (\count($messages) > 1) { + $error = Error::forInvalidRequest('The "initialize" request MUST NOT be part of a batch.'); + $this->sendResponse($transport, $error, null); + + return null; + } + + // Spec: An initialize request must not have a session ID. + if ($sessionId) { + $error = Error::forInvalidRequest('A session ID MUST NOT be sent with an "initialize" request.'); + $this->sendResponse($transport, $error, null); + + return null; + } + + $session = $this->sessionManager->create(); + $this->logger->debug('Created new session for initialize', [ + 'session_id' => $session->getId()->toRfc4122(), + ]); + + $transport->setSessionId($session->getId()); + + return $session; + } + + if (!$sessionId) { + $error = Error::forInvalidRequest('A valid session id is REQUIRED for non-initialize requests.'); + $this->sendResponse($transport, $error, null, ['status_code' => 400]); + + return null; + } + + if (!$this->sessionManager->exists($sessionId)) { + $error = Error::forInvalidRequest('Session not found or has expired.'); + $this->sendResponse($transport, $error, null, ['status_code' => 404]); + + return null; + } + + return $this->sessionManager->createWithId($sessionId); + } + + /** + * Destroy a specific session. + */ + public function destroySession(Uuid $sessionId): void + { + $this->sessionManager->destroy($sessionId); + $this->logger->info('Session destroyed.', ['session_id' => $sessionId->toRfc4122()]); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/RequestContext.php b/admin/vendor/mcp/sdk/src/Server/RequestContext.php new file mode 100644 index 0000000..f322a39 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/RequestContext.php @@ -0,0 +1,157 @@ + + */ +final class RequestContext +{ + /** + * `_meta` key carrying the protocol revision of a single request, introduced + * with the modern era that replaced the `initialize` handshake. + * + * @see https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning + */ + private const PROTOCOL_VERSION_META_KEY = 'io.modelcontextprotocol/protocolVersion'; + + private ?ClientGateway $clientGateway = null; + private ?ClientLogger $clientLogger = null; + + public function __construct( + private readonly SessionInterface $session, + private readonly Request $request, + ) { + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getSession(): SessionInterface + { + return $this->session; + } + + /** + * The protocol revision this request is served under. + * + * Modern revisions declare it per request in `_meta`, handshake ones negotiate + * it once and keep it on the session. Neither is guaranteed to be present — a + * transport may skip `initialize` entirely — so this falls back to the newest + * handshake revision, whose rules hold for every revision below it too. + */ + public function getProtocolVersion(): ProtocolVersion + { + $requested = $this->request->getMeta()[self::PROTOCOL_VERSION_META_KEY] + ?? $this->session->get('protocol_version'); + + if (!\is_string($requested)) { + return ProtocolVersion::latestHandshake(); + } + + return ProtocolVersion::tryFrom($requested) ?? ProtocolVersion::latestHandshake(); + } + + public function getClientGateway(): ClientGateway + { + if (null == $this->clientGateway) { + $this->clientGateway = new ClientGateway($this->session); + } + + return $this->clientGateway; + } + + /** + * What a multi round-trip retry carried back, or null on a first call — + * which is the signal to return an + * {@see \Mcp\Schema\Result\InputRequiredResult} instead of an answer. + */ + public function getInputContext(): ?InputContext + { + $context = $this->session->get(InputContext::class); + + return $context instanceof InputContext ? $context : null; + } + + /** + * What the client declared on this request. A server MUST NOT ask for + * input the client cannot supply. Null in the handshake era, where + * capabilities are connection state. + */ + public function getClientCapabilities(): ?ClientCapabilities + { + $meta = $this->session->get(RequestMeta::class); + + return $meta instanceof RequestMeta ? $meta->clientCapabilities : null; + } + + /** + * The W3C trace context this request carried, if any. + * + * Values are passed through exactly as they arrived — validating or + * regenerating them is the tracing library's job, not the protocol's. The + * SDK echoes them onto the notifications a handler emits so a span stays + * joined across the stream. + * + * @return array keyed by `traceparent`, `tracestate`, `baggage` + */ + public function getTraceContext(): array + { + $meta = $this->session->get(RequestMeta::class); + + return $meta instanceof RequestMeta ? $meta->traceContext : []; + } + + /** + * Seals handler context into the string an + * {@see \Mcp\Schema\Result\InputRequiredResult} carries to the client. + * Signed, not encrypted: nothing secret belongs in the payload. + * + * @param array $payload + */ + public function mintRequestState(array $payload): string + { + $codec = $this->session->get(RequestStateCodec::class); + + if (!$codec instanceof RequestStateCodec) { + throw new LogicException('No requestState signing key is configured; call Builder::setRequestState() before returning state from a handler.'); + } + + return $codec->mint($payload); + } + + public function getClientLogger(): ClientLogger + { + if (null === $this->clientLogger) { + $this->clientLogger = new ClientLogger($this->getClientGateway(), $this->session); + } + + return $this->clientLogger; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Resource/SessionSubscriptionManager.php b/admin/vendor/mcp/sdk/src/Server/Resource/SessionSubscriptionManager.php new file mode 100644 index 0000000..f3ea4d5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Resource/SessionSubscriptionManager.php @@ -0,0 +1,94 @@ + + */ +final class SessionSubscriptionManager implements SubscriptionManagerInterface +{ + public function __construct( + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + /** + * @throws InvalidArgumentException + */ + public function subscribe(SessionInterface $session, string $uri): void + { + $subscriptions = $session->get('resource_subscriptions', []); + $subscriptions[$uri] = true; + $session->set('resource_subscriptions', $subscriptions); + $session->save(); + } + + /** + * @throws InvalidArgumentException + */ + public function unsubscribe(SessionInterface $session, string $uri): void + { + $subscriptions = $session->get('resource_subscriptions', []); + unset($subscriptions[$uri]); + $session->set('resource_subscriptions', $subscriptions); + $session->save(); + } + + /** + * @throws InvalidArgumentException + */ + public function isSubscribed(SessionInterface $session, string $uri): bool + { + $subscriptions = $session->get('resource_subscriptions', []); + + return isset($subscriptions[$uri]); + } + + /** + * @throws InvalidArgumentException + */ + public function notifyResourceChanged(Protocol $protocol, SessionInterface $session, string $uri): void + { + $activeSession = $this->isSubscribed($session, $uri); + if (!$activeSession) { + return; + } + + try { + $protocol->sendNotification( + new ResourceUpdatedNotification($uri), + $session + ); + } catch (InvalidArgumentException $e) { + $this->logger->error('Error sending resource notification to session', [ + 'session_id' => $session->getId()->toRfc4122(), + 'uri' => $uri, + 'exception' => $e, + ]); + + throw $e; + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Resource/SubscriptionManagerInterface.php b/admin/vendor/mcp/sdk/src/Server/Resource/SubscriptionManagerInterface.php new file mode 100644 index 0000000..b31f3d7 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Resource/SubscriptionManagerInterface.php @@ -0,0 +1,53 @@ + + */ +interface SubscriptionManagerInterface +{ + /** + * Subscribes a session to a specific resource URI. + * + * @throws InvalidArgumentException + */ + public function subscribe(SessionInterface $session, string $uri): void; + + /** + * Unsubscribes a session from a specific resource URI. + * + * @throws InvalidArgumentException + */ + public function unsubscribe(SessionInterface $session, string $uri): void; + + /** + * Check if a session is subscribed to a resource URI. + * + * @throws InvalidArgumentException + */ + public function isSubscribed(SessionInterface $session, string $uri): bool; + + /** + * Notifies all sessions subscribed to the given resource URI that the + * resource has changed. Sends a ResourceUpdatedNotification for each subscriber. + * + * @throws InvalidArgumentException + */ + public function notifyResourceChanged(Protocol $protocol, SessionInterface $session, string $uri): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/FileSessionStore.php b/admin/vendor/mcp/sdk/src/Server/Session/FileSessionStore.php new file mode 100644 index 0000000..e218e75 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/FileSessionStore.php @@ -0,0 +1,157 @@ +directory)) { + @mkdir($this->directory, 0775, true); + } + + if (!is_dir($this->directory) || !is_writable($this->directory)) { + throw new RuntimeException(\sprintf('Session directory "%s" is not writable.', $this->directory)); + } + } + + public function exists(Uuid $id): bool + { + $path = $this->pathFor($id); + + if (!is_file($path)) { + return false; + } + + $mtime = @filemtime($path) ?: 0; + + return ($this->clock->now()->getTimestamp() - $mtime) <= $this->ttl; + } + + public function read(Uuid $id): string|false + { + $path = $this->pathFor($id); + + if (!is_file($path)) { + return false; + } + + $mtime = @filemtime($path) ?: 0; + if (($this->clock->now()->getTimestamp() - $mtime) > $this->ttl) { + @unlink($path); + + return false; + } + + $data = @file_get_contents($path); + if (false === $data) { + return false; + } + + return $data; + } + + public function write(Uuid $id, string $data): bool + { + $path = $this->pathFor($id); + + $tmp = $path.'.tmp'; + if (false === @file_put_contents($tmp, $data, \LOCK_EX)) { + return false; + } + + // Atomic move + if (!@rename($tmp, $path)) { + // Fallback if rename fails cross-device + if (false === @copy($tmp, $path)) { + @unlink($tmp); + + return false; + } + @unlink($tmp); + } + + @touch($path, $this->clock->now()->getTimestamp()); + + return true; + } + + public function destroy(Uuid $id): bool + { + $path = $this->pathFor($id); + + if (is_file($path)) { + @unlink($path); + } + + return true; + } + + /** + * Remove sessions older than the configured TTL. + * Returns an array of deleted session IDs (UUID instances). + */ + public function gc(): array + { + $deleted = []; + $now = $this->clock->now()->getTimestamp(); + + $dir = @opendir($this->directory); + if (false === $dir) { + return $deleted; + } + + while (($entry = readdir($dir)) !== false) { + // Skip dot entries + if ('.' === $entry || '..' === $entry) { + continue; + } + + // Only delete files this store owns: sessions are named by their RFC 4122 UUID + if (!Uuid::isValid($entry)) { + continue; + } + + $path = $this->directory.\DIRECTORY_SEPARATOR.$entry; + if (!is_file($path)) { + continue; + } + + $mtime = @filemtime($path) ?: 0; + if (($now - $mtime) > $this->ttl) { + @unlink($path); + $deleted[] = Uuid::fromString($entry); + } + } + + closedir($dir); + + return $deleted; + } + + private function pathFor(Uuid $id): string + { + return $this->directory.\DIRECTORY_SEPARATOR.$id->toRfc4122(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/InMemorySessionStore.php b/admin/vendor/mcp/sdk/src/Server/Session/InMemorySessionStore.php new file mode 100644 index 0000000..0477980 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/InMemorySessionStore.php @@ -0,0 +1,88 @@ + + */ + protected array $store = []; + + public function __construct( + protected readonly int $ttl = 3600, + protected readonly ClockInterface $clock = new NativeClock(), + ) { + } + + public function exists(Uuid $id): bool + { + return isset($this->store[$id->toRfc4122()]); + } + + public function read(Uuid $id): string|false + { + $session = $this->store[$id->toRfc4122()] ?? ''; + if ('' === $session) { + return false; + } + + $currentTimestamp = $this->clock->now()->getTimestamp(); + + if ($currentTimestamp - $session['timestamp'] > $this->ttl) { + unset($this->store[$id->toRfc4122()]); + + return false; + } + + return $session['data']; + } + + public function write(Uuid $id, string $data): bool + { + $this->store[$id->toRfc4122()] = [ + 'data' => $data, + 'timestamp' => $this->clock->now()->getTimestamp(), + ]; + + return true; + } + + public function destroy(Uuid $id): bool + { + if (isset($this->store[$id->toRfc4122()])) { + unset($this->store[$id->toRfc4122()]); + } + + return true; + } + + public function gc(): array + { + $currentTimestamp = $this->clock->now()->getTimestamp(); + $deletedSessions = []; + + foreach ($this->store as $sessionId => $session) { + $sessionId = Uuid::fromString($sessionId); + if ($currentTimestamp - $session['timestamp'] > $this->ttl) { + unset($this->store[$sessionId->toRfc4122()]); + $deletedSessions[] = $sessionId; + } + } + + return $deletedSessions; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/Psr16SessionStore.php b/admin/vendor/mcp/sdk/src/Server/Session/Psr16SessionStore.php new file mode 100644 index 0000000..29ae8ac --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/Psr16SessionStore.php @@ -0,0 +1,79 @@ + + */ +class Psr16SessionStore implements SessionStoreInterface +{ + public function __construct( + private readonly CacheInterface $cache, + private readonly string $prefix = 'mcp-', + private readonly int $ttl = 3600, + ) { + } + + public function exists(Uuid $id): bool + { + try { + return $this->cache->has($this->getKey($id)); + } catch (\Throwable) { + return false; + } + } + + public function read(Uuid $id): string|false + { + try { + return $this->cache->get($this->getKey($id), false); + } catch (\Throwable) { + return false; + } + } + + public function write(Uuid $id, string $data): bool + { + try { + return $this->cache->set($this->getKey($id), $data, $this->ttl); + } catch (\Throwable) { + return false; + } + } + + public function destroy(Uuid $id): bool + { + try { + return $this->cache->delete($this->getKey($id)); + } catch (\Throwable) { + return false; + } + } + + public function gc(): array + { + return []; + } + + private function getKey(Uuid $id): string + { + return $this->prefix.$id; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/Session.php b/admin/vendor/mcp/sdk/src/Server/Session/Session.php new file mode 100644 index 0000000..6eee72d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/Session.php @@ -0,0 +1,176 @@ + + */ +class Session implements SessionInterface +{ + /** + * Official keys are: + * - initialized: bool + * - client_info: array|null + * - client_capabilities: array|null + * - protocol_version: string|null + * - log_level: string|null + * + * @var array + */ + private array $data; + + public function __construct( + private SessionStoreInterface $store, + private Uuid $id = new UuidV4(), + ) { + } + + public function getId(): Uuid + { + return $this->id; + } + + public function save(): bool + { + return $this->store->write($this->id, json_encode($this->readData(), \JSON_THROW_ON_ERROR)); + } + + public function get(string $key, mixed $default = null): mixed + { + $key = explode('.', $key); + $data = $this->readData(); + + foreach ($key as $segment) { + if (\is_array($data) && \array_key_exists($segment, $data)) { + $data = $data[$segment]; + } else { + return $default; + } + } + + return $data; + } + + public function set(string $key, mixed $value, bool $overwrite = true): void + { + $segments = explode('.', $key); + $this->readData(); + $data = &$this->data; + + while (\count($segments) > 1) { + $segment = array_shift($segments); + if (!isset($data[$segment]) || !\is_array($data[$segment])) { + $data[$segment] = []; + } + $data = &$data[$segment]; + } + + $lastKey = array_shift($segments); + if ($overwrite || !isset($data[$lastKey])) { + $data[$lastKey] = $value; + } + } + + public function has(string $key): bool + { + $key = explode('.', $key); + $data = $this->readData(); + + foreach ($key as $segment) { + if (\is_array($data) && \array_key_exists($segment, $data)) { + $data = $data[$segment]; + } elseif (\is_object($data) && isset($data->{$segment})) { + $data = $data->{$segment}; + } else { + return false; + } + } + + return true; + } + + public function forget(string $key): void + { + $segments = explode('.', $key); + $this->readData(); + $data = &$this->data; + + while (\count($segments) > 1) { + $segment = array_shift($segments); + if (!isset($data[$segment]) || !\is_array($data[$segment])) { + $data[$segment] = []; + } + $data = &$data[$segment]; + } + + $lastKey = array_shift($segments); + if (isset($data[$lastKey])) { + unset($data[$lastKey]); + } + } + + public function clear(): void + { + $this->data = []; + } + + public function pull(string $key, mixed $default = null): mixed + { + $value = $this->get($key, $default); + $this->forget($key); + + return $value; + } + + public function all(): array + { + return $this->readData(); + } + + public function hydrate(array $attributes): void + { + $this->data = $attributes; + } + + /** @return array */ + public function jsonSerialize(): array + { + return $this->all(); + } + + /** + * @return array + */ + private function readData(): array + { + if (isset($this->data)) { + return $this->data; + } + + $rawData = $this->store->read($this->id); + + if (false === $rawData) { + return $this->data = []; + } + + $decoded = json_decode($rawData, true, flags: \JSON_THROW_ON_ERROR); + + if (!\is_array($decoded)) { + return $this->data = []; + } + + return $this->data = $decoded; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/SessionInterface.php b/admin/vendor/mcp/sdk/src/Server/Session/SessionInterface.php new file mode 100644 index 0000000..e14d2c6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/SessionInterface.php @@ -0,0 +1,80 @@ + + */ +interface SessionInterface extends \JsonSerializable +{ + /** + * Get the session ID. + */ + public function getId(): Uuid; + + /** + * Save the session. + */ + public function save(): bool; + + /** + * Get a specific attribute from the session. + * Supports dot notation for nested access. + */ + public function get(string $key, mixed $default = null): mixed; + + /** + * Set a specific attribute in the session. + * Supports dot notation for nested access. + */ + public function set(string $key, mixed $value, bool $overwrite = true): void; + + /** + * Check if an attribute exists in the session. + * Supports dot notation for nested access. + */ + public function has(string $key): bool; + + /** + * Remove an attribute from the session. + * Supports dot notation for nested access. + */ + public function forget(string $key): void; + + /** + * Remove all attributes from the session. + */ + public function clear(): void; + + /** + * Get an attribute's value and then remove it from the session. + * Supports dot notation for nested access. + */ + public function pull(string $key, mixed $default = null): mixed; + + /** + * Get all attributes of the session. + * + * @return array + */ + public function all(): array; + + /** + * Set all attributes of the session, typically for hydration. + * This will overwrite existing attributes. + * + * @param array $attributes + */ + public function hydrate(array $attributes): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/SessionManager.php b/admin/vendor/mcp/sdk/src/Server/Session/SessionManager.php new file mode 100644 index 0000000..2a08f7e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/SessionManager.php @@ -0,0 +1,86 @@ + + */ +class SessionManager implements SessionManagerInterface +{ + /** + * @param int $gcProbability The probability (numerator) that GC will run on any given request. Combined with $gcDivisor to calculate the actual probability. Set to 0 to disable GC. Similar to PHP's session.gc_probability. + * @param int $gcDivisor The divisor used with $gcProbability to calculate GC probability. The probability is gcProbability/gcDivisor (e.g. 1/100 = 1%). Similar to PHP's session.gc_divisor. + */ + public function __construct( + private readonly SessionStoreInterface $store, + private readonly LoggerInterface $logger = new NullLogger(), + private readonly int $gcProbability = 1, + private readonly int $gcDivisor = 100, + ) { + if ($gcProbability < 0) { + throw new InvalidArgumentException('gcProbability must be greater than or equal to 0.'); + } + if ($gcDivisor < 1) { + throw new InvalidArgumentException('gcDivisor must be greater than or equal to 1.'); + } + } + + public function create(): SessionInterface + { + return new Session($this->store, Uuid::v4()); + } + + public function createWithId(Uuid $id): SessionInterface + { + return new Session($this->store, $id); + } + + public function exists(Uuid $id): bool + { + return $this->store->exists($id); + } + + public function destroy(Uuid $id): bool + { + return $this->store->destroy($id); + } + + /** + * Run garbage collection on expired sessions. + * Uses the session store's internal TTL configuration. + */ + public function gc(): void + { + if (0 === $this->gcProbability) { + return; + } + + if (random_int(1, $this->gcDivisor) > $this->gcProbability) { + return; + } + + $deletedSessions = $this->store->gc(); + if (!empty($deletedSessions)) { + $this->logger->debug('Garbage collected expired sessions.', [ + 'count' => \count($deletedSessions), + 'session_ids' => array_map(static fn (Uuid $id) => $id->toRfc4122(), $deletedSessions), + ]); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/SessionManagerInterface.php b/admin/vendor/mcp/sdk/src/Server/Session/SessionManagerInterface.php new file mode 100644 index 0000000..dc601f4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/SessionManagerInterface.php @@ -0,0 +1,47 @@ + + */ +interface SessionManagerInterface +{ + /** + * Creates a new session with an auto-generated UUID. + * This is the standard factory method for creating sessions. + */ + public function create(): SessionInterface; + + /** + * Creates a session with a specific UUID. + * Use this when you need to reconstruct a session with a known ID. + */ + public function createWithId(Uuid $id): SessionInterface; + + /** + * Checks if a session with the given UUID exists. + */ + public function exists(Uuid $id): bool; + + /** + * Destroys the session with the given UUID. + */ + public function destroy(Uuid $id): bool; + + public function gc(): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Session/SessionStoreInterface.php b/admin/vendor/mcp/sdk/src/Server/Session/SessionStoreInterface.php new file mode 100644 index 0000000..13f5f16 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Session/SessionStoreInterface.php @@ -0,0 +1,64 @@ + + */ +interface SessionStoreInterface +{ + /** + * Check if a session exists. + * + * @param Uuid $id the session id + * + * @return bool true if the session exists, false otherwise + */ + public function exists(Uuid $id): bool; + + /** + * Read session data. + * + * Returns an encoded string of the read data. + * If nothing was read, it must return false. + * + * @param Uuid $id the session id to read data for + */ + public function read(Uuid $id): string|false; + + /** + * Write session data. + * + * @param Uuid $id the session id + * @param string $data the encoded session data + */ + public function write(Uuid $id, string $data): bool; + + /** + * Destroy a session. + * + * @param Uuid $id The session ID being destroyed. + * The return value (usually TRUE on success, FALSE on failure). + */ + public function destroy(Uuid $id): bool; + + /** + * Cleanup old sessions + * Sessions that have not updated for + * the configured TTL will be removed. + * + * @return Uuid[] + */ + public function gc(): array; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/ElicitationReplay.php b/admin/vendor/mcp/sdk/src/Server/Stateless/ElicitationReplay.php new file mode 100644 index 0000000..fe93627 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/ElicitationReplay.php @@ -0,0 +1,169 @@ + + */ +final class ElicitationReplay +{ + /** + * The `requestState` member carrying answers from earlier rounds. Reserved, + * like every `_mcp.` key: a handler's own payload travels beside it. + */ + public const CARRIED_ANSWERS = '_mcp.answers'; + + /** @var array> raw answers, keyed as they were asked */ + private array $answers = []; + + /** @var array the verified state this round arrived with */ + private array $payload; + + /** @var array keys whose answer this run could not read */ + private array $rejected = []; + + private int $asked = 0; + + public function __construct(?InputContext $input, private readonly ?RequestStateCodec $codec = null) + { + $this->payload = $input?->requestState() ?? []; + + $carried = $this->payload[self::CARRIED_ANSWERS] ?? []; + + if (\is_array($carried)) { + $this->answers = array_filter($carried, \is_array(...)); + } + + // Lifted out, so what is sealed again is what this run could still use + // rather than whatever the last round happened to carry. + unset($this->payload[self::CARRIED_ANSWERS]); + + // What this round answered wins over what an earlier one did: a key is + // only re-asked because its old answer was not usable. + foreach ($input?->all() ?? [] as $key => $answer) { + if (\is_array($answer)) { + $this->answers[$key] = $answer; + } + } + } + + /** + * The key an ask is filed under: the one the handler named, or its position + * among this run's asks. + * + * Positional keys hold across rounds only because the handler reaches its + * asks in the same order every time — which it does whenever it is + * re-enterable at all. A handler whose asks depend on a coin flip should + * name them. + */ + public function key(?string $key): string + { + ++$this->asked; + + return $key ?? 'elicitation_'.$this->asked; + } + + /** + * The answer to `$key`, or null when there is none to give the handler. + * + * Null covers an answer that does not parse as well as one that never + * arrived: the specification says a server SHOULD ask again for what it + * still needs, and a malformed answer left the server still needing it. + * + * @return array|null + */ + public function answer(string $key, ElicitationMode $mode = ElicitationMode::Form): ?array + { + $answer = $this->answers[$key] ?? null; + + if (null === $answer) { + return null; + } + + try { + ElicitResult::fromArray($answer, $mode); + } catch (\Throwable) { + // Noted rather than dropped: `answer()` only reads, and what makes + // this one unreadable is the mode it was read under. Marking it + // keeps it out of the state, since it is about to be asked again. + $this->rejected[$key] = true; + + return null; + } + + unset($this->rejected[$key]); + + return $answer; + } + + /** + * The result that ends this request by asking, carrying everything already + * answered so the next round does not have to ask for it again. + * + * @throws LogicException when there is state to carry and no key to sign it with + */ + public function ask(string $key, ElicitRequest $request): InputRequiredResult + { + return new InputRequiredResult([$key => $request], $this->state()); + } + + private function state(): ?string + { + $payload = $this->payload; + $answers = array_diff_key($this->answers, $this->rejected); + + if ([] !== $answers) { + $payload[self::CARRIED_ANSWERS] = $answers; + } + + if ([] === $payload) { + return null; + } + + if (null === $this->codec) { + throw new LogicException('Carrying an answer to the next round of a multi round-trip call needs a signing key; call Builder::setRequestState() to configure one.'); + } + + return $this->codec->mint($payload); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/InputContext.php b/admin/vendor/mcp/sdk/src/Server/Stateless/InputContext.php new file mode 100644 index 0000000..d91197b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/InputContext.php @@ -0,0 +1,125 @@ + + */ +final class InputContext +{ + /** + * @param array $responses client answers, keyed as the inputRequests were + * @param array $requestState the verified payload the server sealed last round + */ + public function __construct( + private readonly array $responses = [], + private readonly array $requestState = [], + ) { + } + + /** + * The client's answer for `$key`, or null when it did not provide one. + */ + public function response(string $key): mixed + { + return $this->responses[$key] ?? null; + } + + /** + * The client's answer to an `elicitation/create` ask, typed. + * + * Null when the key is absent or the answer does not parse — which the + * caller should read as "not answered" and ask again, rather than as an + * error: the specification says a server SHOULD re-ask for information it + * still needs instead of failing the request. + * + * @param ElicitationMode $mode the mode of the ask this answers; url-mode + * answers carry no content, so an accepted one + * is complete without it + */ + public function elicitResult(string $key, ElicitationMode $mode = ElicitationMode::Form): ?ElicitResult + { + return $this->parse($key, static fn (array $data): ElicitResult => ElicitResult::fromArray($data, $mode)); + } + + /** + * The client's answer to a `sampling/createMessage` ask, typed. + */ + public function samplingResult(string $key): ?CreateSamplingMessageResult + { + return $this->parse($key, CreateSamplingMessageResult::fromArray(...)); + } + + /** + * The client's answer to a `roots/list` ask, typed. + */ + public function rootsResult(string $key): ?ListRootsResult + { + return $this->parse($key, ListRootsResult::fromArray(...)); + } + + /** + * @template T of ResultInterface + * + * @param callable(array): T $factory + * + * @return T|null + */ + private function parse(string $key, callable $factory): ?ResultInterface + { + $response = $this->responses[$key] ?? null; + + if (!\is_array($response)) { + return null; + } + + try { + return $factory($response); + } catch (\Throwable) { + // A malformed answer is one the client did not really give. + return null; + } + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->responses); + } + + /** + * @return array + */ + public function all(): array + { + return $this->responses; + } + + /** + * @return array + */ + public function requestState(): array + { + return $this->requestState; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/InputRequestCapabilities.php b/admin/vendor/mcp/sdk/src/Server/Stateless/InputRequestCapabilities.php new file mode 100644 index 0000000..1f4287f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/InputRequestCapabilities.php @@ -0,0 +1,96 @@ + + */ +final class InputRequestCapabilities +{ + /** + * The capabilities $result needs that $declared does not offer, or null + * when the client can answer everything it is being asked. + */ + public static function missing(InputRequiredResult $result, ClientCapabilities $declared): ?ClientCapabilities + { + $roots = false; + $sampling = false; + $samplingTools = false; + $samplingContext = false; + $elicitationForm = false; + $elicitationUrl = false; + + foreach ($result->inputRequests as $request) { + if ($request instanceof ListRootsRequest) { + $roots = $roots || true !== $declared->roots; + + continue; + } + + if ($request instanceof CreateSamplingMessageRequest) { + $sampling = $sampling || true !== $declared->sampling; + + if (null !== $request->tools || null !== $request->toolChoice) { + $samplingTools = $samplingTools || true !== $declared->samplingTools; + } + + // `none` is the default and needs nothing; the other two are + // deprecated values that only a declaring client understands. + if (null !== $request->includeContext && SamplingContext::NONE !== $request->includeContext) { + $samplingContext = $samplingContext || true !== $declared->samplingContext; + } + + continue; + } + + if ($request instanceof ElicitRequest) { + if (ElicitationMode::Url === $request->mode) { + $elicitationUrl = $elicitationUrl || true !== $declared->elicitationUrl; + } else { + $elicitationForm = $elicitationForm || true !== $declared->elicitationForm; + } + } + } + + if (!$roots && !$sampling && !$samplingTools && !$samplingContext && !$elicitationForm && !$elicitationUrl) { + return null; + } + + return new ClientCapabilities( + roots: $roots, + sampling: $sampling ?: null, + elicitation: ($elicitationForm || $elicitationUrl) ?: null, + samplingContext: $samplingContext ?: null, + samplingTools: $samplingTools ?: null, + elicitationForm: $elicitationForm ?: null, + elicitationUrl: $elicitationUrl ?: null, + ); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/NotificationFilter.php b/admin/vendor/mcp/sdk/src/Server/Stateless/NotificationFilter.php new file mode 100644 index 0000000..4649126 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/NotificationFilter.php @@ -0,0 +1,115 @@ + + */ +final class NotificationFilter +{ + /** + * @param list $resourceSubscriptions resource URIs to report updates for + */ + public function __construct( + public readonly bool $toolsListChanged = false, + public readonly bool $promptsListChanged = false, + public readonly bool $resourcesListChanged = false, + public readonly array $resourceSubscriptions = [], + ) { + } + + /** + * @param array|null $notifications the request's `params.notifications` member + */ + public static function fromParams(?array $notifications): self + { + $notifications ??= []; + + $uris = $notifications['resourceSubscriptions'] ?? []; + + return new self( + true === ($notifications['toolsListChanged'] ?? false), + true === ($notifications['promptsListChanged'] ?? false), + true === ($notifications['resourcesListChanged'] ?? false), + \is_array($uris) ? array_values(array_filter($uris, \is_string(...))) : [], + ); + } + + /** + * Narrows the filter to what this server can deliver, so the acknowledgment + * reflects the subset it agreed to honour rather than promising silence. + */ + public function intersect(ServerCapabilities $capabilities): self + { + return new self( + $this->toolsListChanged && true === $capabilities->toolsListChanged, + $this->promptsListChanged && true === $capabilities->promptsListChanged, + $this->resourcesListChanged && true === $capabilities->resourcesListChanged, + true === $capabilities->resourcesSubscribe ? $this->resourceSubscriptions : [], + ); + } + + /** + * Whether this filter admits $notification onto the stream. + * + * An allow-list decision, so an unrecognized notification is declined: the + * server MUST NOT send a type the client did not ask for, and "did not ask + * for" includes types it has never heard of. + */ + public function carries(Notification $notification): bool + { + return match (true) { + $notification instanceof ToolListChangedNotification => $this->toolsListChanged, + $notification instanceof PromptListChangedNotification => $this->promptsListChanged, + $notification instanceof ResourceListChangedNotification => $this->resourcesListChanged, + $notification instanceof ResourceUpdatedNotification => \in_array($notification->uri, $this->resourceSubscriptions, true), + default => false, + }; + } + + /** + * Agreed types only; declined ones are omitted rather than sent as `false`. + * + * @return array + */ + public function toAcknowledgedArray(): array + { + $data = []; + + if ($this->toolsListChanged) { + $data['toolsListChanged'] = true; + } + if ($this->promptsListChanged) { + $data['promptsListChanged'] = true; + } + if ($this->resourcesListChanged) { + $data['resourcesListChanged'] = true; + } + if ([] !== $this->resourceSubscriptions) { + $data['resourceSubscriptions'] = $this->resourceSubscriptions; + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/RequestMeta.php b/admin/vendor/mcp/sdk/src/Server/Stateless/RequestMeta.php new file mode 100644 index 0000000..3744d92 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/RequestMeta.php @@ -0,0 +1,133 @@ + + */ +final class RequestMeta +{ + public const PROTOCOL_VERSION = 'io.modelcontextprotocol/protocolVersion'; + public const CLIENT_INFO = 'io.modelcontextprotocol/clientInfo'; + public const CLIENT_CAPABILITIES = 'io.modelcontextprotocol/clientCapabilities'; + public const LOG_LEVEL = 'io.modelcontextprotocol/logLevel'; + public const SERVER_INFO = 'io.modelcontextprotocol/serverInfo'; + public const SUBSCRIPTION_ID = 'io.modelcontextprotocol/subscriptionId'; + + /** + * OpenTelemetry trace context, exempt from the prefix rule by name so it + * matches what the wider ecosystem already puts on the wire. + * + * @see https://www.w3.org/TR/trace-context/ + */ + public const TRACE_KEYS = ['traceparent', 'tracestate', 'baggage']; + + /** + * @param array $traceContext W3C trace context carried through from the request + */ + public function __construct( + public readonly string $protocolVersion, + public readonly ClientCapabilities $clientCapabilities, + public readonly ?Implementation $clientInfo = null, + public readonly ?LoggingLevel $logLevel = null, + public readonly array $traceContext = [], + ) { + } + + /** + * @param array|null $params the request's `params` member, if any + * @param array $headers request headers, case-insensitively matched; a + * transport without a header layer (stdio) passes none + * + * @throws MissingRequestMetaException when a structurally required member is absent or malformed + */ + public static function fromParams(?array $params, array $headers = []): self + { + $meta = $params['_meta'] ?? null; + + if (!\is_array($meta)) { + throw new MissingRequestMetaException('Request is missing the required "params._meta" member.'); + } + + $version = $meta[self::PROTOCOL_VERSION] ?? null; + if (!\is_string($version) || '' === $version) { + throw new MissingRequestMetaException(\sprintf('Request "_meta" is missing the required "%s" member.', self::PROTOCOL_VERSION)); + } + + // An empty object is valid — a client with no optional capabilities. + $capabilities = $meta[self::CLIENT_CAPABILITIES] ?? null; + if (!\is_array($capabilities) && !$capabilities instanceof \stdClass) { + throw new MissingRequestMetaException(\sprintf('Request "_meta" is missing the required "%s" member.', self::CLIENT_CAPABILITIES)); + } + + $clientInfo = $meta[self::CLIENT_INFO] ?? null; + + return new self( + $version, + ClientCapabilities::fromArray((array) $capabilities), + \is_array($clientInfo) ? Implementation::fromArray($clientInfo) : null, + self::parseLogLevel($meta[self::LOG_LEVEL] ?? null), + self::parseTraceContext($meta, $headers), + ); + } + + /** + * An unparseable level means "none requested": failing the caller's actual + * work over a diagnostic preference would be worse. + */ + private static function parseLogLevel(mixed $level): ?LoggingLevel + { + return \is_string($level) ? LoggingLevel::tryFrom($level) : null; + } + + /** + * Carried through opaquely: the values are the tracing ecosystem's to + * interpret, and a malformed one is not this server's to reject. + * + * `_meta` wins over the native W3C header of the same name when both are + * present — it is the more specific of the two, scoped to this one call + * rather than the whole HTTP request, and a caller that put it there did + * so deliberately. + * + * @param array $meta + * @param array $headers + * + * @return array + */ + private static function parseTraceContext(array $meta, array $headers): array + { + $context = []; + + foreach (self::TRACE_KEYS as $key) { + if (\is_string($meta[$key] ?? null)) { + $context[$key] = $meta[$key]; + } elseif (null !== $header = InboundClassifier::header($headers, $key)) { + $context[$key] = $header; + } + } + + return $context; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/RequestStateCodec.php b/admin/vendor/mcp/sdk/src/Server/Stateless/RequestStateCodec.php new file mode 100644 index 0000000..9dcf477 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/RequestStateCodec.php @@ -0,0 +1,125 @@ + + */ +final class RequestStateCodec +{ + /** Below this the MAC — the only thing making the blob trustworthy — is forgeable. */ + public const MINIMUM_KEY_BYTES = 32; + + private const ALGORITHM = 'sha256'; + + public function __construct( + private readonly string $key, + private readonly int $ttlSeconds = 600, + ) { + if (\strlen($this->key) < self::MINIMUM_KEY_BYTES) { + throw new InvalidArgumentException(\sprintf('The requestState signing key must be at least %d bytes, got %d.', self::MINIMUM_KEY_BYTES, \strlen($this->key))); + } + + if ($this->ttlSeconds < 1) { + throw new InvalidArgumentException(\sprintf('The requestState TTL must be at least one second, got %d.', $this->ttlSeconds)); + } + } + + /** + * @param array $payload server context to carry to the retry — never secrets + */ + public function mint(array $payload, ?int $now = null): string + { + $body = self::encode(json_encode([ + 'exp' => ($now ?? time()) + $this->ttlSeconds, + 'data' => $payload, + ], \JSON_THROW_ON_ERROR)); + + return $body.'.'.self::encode($this->sign($body)); + } + + /** + * @return array the payload that was sealed + * + * @throws RequestStateException when the value is malformed, unsigned by this key, or expired + */ + public function verify(string $state, ?int $now = null): array + { + $parts = explode('.', $state); + + if (2 !== \count($parts)) { + throw new RequestStateException('malformed'); + } + + [$body, $mac] = $parts; + + $expected = $this->sign($body); + $actual = self::decode($mac); + + // Constant-time: a short-circuiting compare is a byte-at-a-time oracle. + if (null === $actual || !hash_equals($expected, $actual)) { + throw new RequestStateException('mac'); + } + + $decoded = self::decode($body); + + if (null === $decoded) { + throw new RequestStateException('malformed'); + } + + try { + /** @var array $claims */ + $claims = json_decode($decoded, true, flags: \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + throw new RequestStateException('malformed'); + } + + if (!\is_array($claims) || !\is_int($claims['exp'] ?? null) || !\is_array($claims['data'] ?? null)) { + throw new RequestStateException('malformed'); + } + + // Bounds replay; does not make the state single-use. + if ($claims['exp'] < ($now ?? time())) { + throw new RequestStateException('expired'); + } + + return $claims['data']; + } + + private function sign(string $body): string + { + return hash_hmac(self::ALGORITHM, $body, $this->key, true); + } + + private static function encode(string $raw): string + { + return rtrim(strtr(base64_encode($raw), '+/', '-_'), '='); + } + + private static function decode(string $encoded): ?string + { + $decoded = base64_decode(strtr($encoded, '-_', '+/'), true); + + return false === $decoded ? null : $decoded; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/StandardHeaderValidator.php b/admin/vendor/mcp/sdk/src/Server/Stateless/StandardHeaderValidator.php new file mode 100644 index 0000000..0d54873 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/StandardHeaderValidator.php @@ -0,0 +1,299 @@ + + */ +final class StandardHeaderValidator +{ + public const METHOD_HEADER = McpHeader::METHOD; + public const NAME_HEADER = McpHeader::NAME; + public const PARAM_HEADER_PREFIX = McpHeader::PARAM_PREFIX; + + public function __construct( + private readonly ?RegistryInterface $registry = null, + ) { + } + + /** + * @param array|null $params + * @param array $headers + * + * @return string|null the reason to reject, or null when the request is consistent + */ + public function validate(string $method, ?array $params, array $headers): ?string + { + return $this->checkMethod($method, $headers) + ?? $this->checkName($method, $params, $headers) + ?? $this->checkParams($method, $params, $headers); + } + + /** + * @param array $headers + */ + private function checkMethod(string $method, array $headers): ?string + { + $declared = $this->header($headers, self::METHOD_HEADER); + + if (null === $declared) { + return \sprintf('Missing required %s header (body method is "%s").', self::METHOD_HEADER, $method); + } + + if ($declared !== $method) { + return \sprintf('%s header "%s" does not match the body method "%s".', self::METHOD_HEADER, $declared, $method); + } + + return null; + } + + /** + * When the body carries a name the header must repeat it; when it does not, + * the server must not demand one. + * + * @param array|null $params + * @param array $headers + */ + private function checkName(string $method, ?array $params, array $headers): ?string + { + $expected = self::nameFor($method, $params); + $declared = $this->header($headers, self::NAME_HEADER); + + if (null === $expected) { + return null; + } + + if (null === $declared) { + return \sprintf('Missing required %s header (body carries "%s").', self::NAME_HEADER, $expected); + } + + // Tool and prompt names are only SHOULD-constrained to header-safe + // characters and a resource URI is not constrained at all, so the + // client wraps anything unsafe — decode before comparing or every + // conformant client carrying a non-ASCII subject is refused. + $decoded = self::decode($declared); + + if (null === $decoded) { + return \sprintf('%s header is not a well-formed Base64 wrapper.', self::NAME_HEADER); + } + + if ($decoded !== $expected) { + return \sprintf('%s header "%s" does not match the body value "%s".', self::NAME_HEADER, $decoded, $expected); + } + + return null; + } + + /** + * The subject of a request, per method. Anything unlisted is exempt. + * + * @param array|null $params + */ + public static function nameFor(string $method, ?array $params): ?string + { + return McpHeader::nameFor($method, $params); + } + + /** + * Only headers the tool itself declares are checked: an unrecognized + * `Mcp-Param-*` belongs to somebody else in the chain, and intermediaries + * are meant to forward what they do not understand. + * + * @param array|null $params + * @param array $headers + */ + private function checkParams(string $method, ?array $params, array $headers): ?string + { + if ('tools/call' !== $method || null === $this->registry) { + return null; + } + + $toolName = $params['name'] ?? null; + if (!\is_string($toolName)) { + return null; + } + + try { + $tool = $this->registry->getTool($toolName)->tool; + } catch (ToolNotFoundException) { + // The handler reports this properly; a header complaint would not. + return null; + } + + $arguments = \is_array($params['arguments'] ?? null) ? $params['arguments'] : []; + + foreach (self::mirroredProperties($tool->inputSchema) as $name => $path) { + $error = $this->checkParam( + self::PARAM_HEADER_PREFIX.$name, + $headers, + self::valueAt($arguments, $path), + ); + + if (null !== $error) { + return $error; + } + } + + return null; + } + + /** + * Every `x-mcp-header` annotation in $schema, as header name to the property + * path it mirrors. + * + * Only statically reachable properties count: the chain from the root must + * be `properties` keys the whole way. A chain through `items`, a + * composition keyword, `if`/`then`/`else` or a `$ref` is not extractable + * without evaluating the instance, so the specification puts an annotation + * there out of bounds — and this walk simply never reaches one. + * + * @param array $schema + * @param list $path + * + * @return array> + */ + public static function mirroredProperties(array $schema, array $path = []): array + { + $properties = $schema['properties'] ?? null; + + if (!\is_array($properties)) { + return []; + } + + $found = []; + + foreach ($properties as $property => $definition) { + if (!\is_array($definition)) { + continue; + } + + $here = [...$path, (string) $property]; + + if (\is_string($definition['x-mcp-header'] ?? null)) { + $found[$definition['x-mcp-header']] = $here; + } + + $found = [...$found, ...self::mirroredProperties($definition, $here)]; + } + + return $found; + } + + /** + * Reads the instance value at an exact property path, or null when the path + * is not present — which the specification reads as "no header expected". + * + * @param array $arguments + * @param list $path + */ + private static function valueAt(array $arguments, array $path): mixed + { + $node = $arguments; + + foreach ($path as $segment) { + if (!\is_array($node) || !\array_key_exists($segment, $node)) { + return null; + } + + $node = $node[$segment]; + } + + return $node; + } + + /** + * @param array $headers + */ + private function checkParam(string $headerName, array $headers, mixed $argument): ?string + { + $declared = $this->header($headers, $headerName); + + // An omitted argument means an omitted header. + if (null === $argument) { + return null; + } + + if (null === $declared) { + return \sprintf('Missing required %s header (body carries the mirrored argument).', $headerName); + } + + $decoded = self::decode($declared); + + if (null === $decoded) { + return \sprintf('%s header is not a well-formed Base64 wrapper.', $headerName); + } + + // Numbers travel as decimal strings, booleans as "true"/"false". + $expected = match (true) { + \is_bool($argument) => $argument ? 'true' : 'false', + \is_scalar($argument) => (string) $argument, + default => null, + }; + + // A non-scalar cannot be mirrored at all, so the annotation on it is + // the tool definition's problem rather than this request's. + if (null === $expected) { + return null; + } + + // Numerically for numbers, so "42.0" and "42" agree — the spec asks for + // this, and a client's JSON writer is free to pick either. Gated on the + // argument's actual type (not is_numeric, which a numeric-looking + // string like "042" would also satisfy) and a decimal header (not + // "4e1"), so a string argument keeps its exact-match comparison. + if ((\is_int($argument) || \is_float($argument)) && 1 === preg_match('/^-?\d+(?:\.\d+)?$/', $decoded)) { + return (float) $decoded === (float) $argument + ? null + : \sprintf('%s header "%s" does not match the body argument "%s".', $headerName, $decoded, $expected); + } + + if ($decoded !== $expected) { + return \sprintf('%s header "%s" does not match the body argument "%s".', $headerName, $decoded, $expected); + } + + return null; + } + + /** + * Unwraps a `=?base64?…?=` value, or returns a plain value unchanged. + */ + public static function decode(string $value): ?string + { + return McpHeader::decode($value); + } + + /** + * Case-insensitive name, whitespace-trimmed value (RFC 9110 §5.5). + * + * @param array $headers + */ + private function header(array $headers, string $name): ?string + { + foreach ($headers as $key => $value) { + if (0 === strcasecmp($key, $name)) { + return trim($value); + } + } + + return null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessProtocol.php b/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessProtocol.php new file mode 100644 index 0000000..a179a21 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessProtocol.php @@ -0,0 +1,874 @@ + + */ +final class StatelessProtocol +{ + private readonly WireCodecInterface $codec; + + /** + * Methods the modern era deleted. Answered as unknown methods, which is + * what they are to a modern server. + * + * A deny-list rather than an allow-list on purpose: extensions add methods + * this class has never heard of, so an unlisted method has to reach + * dispatch. Every removal named in the 2026-07-28 changelog belongs here — + * the handlers behind them stay registered for the handshake era, which is + * why the era guard, and not the registration, is what turns them off. + */ + public const REMOVED_METHODS = [ + 'initialize', + 'notifications/initialized', + 'ping', + 'logging/setLevel', + // Replaced by the `resourceSubscriptions` filter of subscriptions/listen. + 'resources/subscribe', + 'resources/unsubscribe', + 'notifications/roots/list_changed', + ]; + + public const DISCOVER_METHOD = 'server/discover'; + public const LISTEN_METHOD = 'subscriptions/listen'; + public const ACKNOWLEDGED_NOTIFICATION = 'notifications/subscriptions/acknowledged'; + + /** + * What a client is told when a handler fails in a way nothing anticipated. + * + * Deliberately generic rather than {@see \Throwable::getMessage()}: the + * real message is logged, not returned, so an internal detail (a + * connection string, a file path, another library's error text) never + * reaches the client that triggered it. + */ + private const INTERNAL_ERROR_MESSAGE = 'Internal server error.'; + + /** + * @param iterable> $requestHandlers + * @param list $supportedVersions + * @param array $extensionMethods RPC method to the extension identifier defining it + */ + public function __construct( + private readonly iterable $requestHandlers, + private readonly MessageFactory $messageFactory, + private readonly Configuration $configuration, + private readonly array $supportedVersions = [ProtocolVersion::V2026_07_28], + private readonly LoggerInterface $logger = new NullLogger(), + private readonly float $subscriptionLifetime = 30.0, + ?WireCodecInterface $codec = null, + private readonly ?StandardHeaderValidator $headerValidator = null, + private readonly ?RequestStateCodec $requestStateCodec = null, + ?CachePolicy $cachePolicy = null, + private readonly ?NotificationBusInterface $notificationBus = null, + private readonly array $extensionMethods = [], + ) { + $this->codec = $codec ?? new Rev2026Codec($configuration->serverInfo, $cachePolicy); + + if (null === $this->headerValidator) { + // Not fatal: a transport without a header layer — stdio — has + // nothing to validate. But on HTTP the headers are REQUIRED for + // compliance, so an absent validator there is a silently + // non-conformant server and worth saying out loud once. + $this->logger->warning('No StandardHeaderValidator configured; the SEP-2243 request headers will not be enforced. This is correct only for a transport without a header layer.'); + } + } + + /** + * The modern revisions this dispatcher answers for. + * + * @return list + */ + public function supportedVersions(): array + { + return $this->supportedVersions; + } + + /** + * Whether the transport carrying this dispatcher has a header layer whose + * required members must be present. + * + * The validator's presence is the signal: it is what a header-bearing + * transport installs, and stdio carries its metadata inline instead + * (see the stdio binding's "Request Metadata"). + */ + private function requiresTransportHeaders(): bool + { + return null !== $this->headerValidator; + } + + /** + * Answers one JSON-RPC request read from an HTTP request body. + * + * @param array $headers request headers, case-insensitively matched + */ + public function handle(string $body, array $headers = []): StatelessResult + { + try { + /** @var array|null $decoded */ + $decoded = json_decode($body, true, flags: \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + return StatelessResult::error(Error::forParseError($e->getMessage()), 400); + } + + if (!\is_array($decoded)) { + return StatelessResult::error(Error::forInvalidRequest('A JSON-RPC message must be a JSON object.'), 400); + } + + // The id is read before anything is validated so that every error below + // can echo it when there is one to echo. A missing or malformed id is + // left as null rather than coerced to "" — Error::jsonSerialize() + // already knows to omit a null id instead of fabricating one, and + // "id": "" would falsely claim the sender issued a request with an + // empty-string id. + // + // An absent id and an unreadable one are different messages: the first + // is a notification, the second a malformed request. JSON-RPC 2.0 + // writes "no id" as an explicit null, so that counts as absent too. + $isNotification = !\array_key_exists('id', $decoded) || null === $decoded['id']; + $id = $decoded['id'] ?? null; + + if (!\is_string($id) && !\is_int($id)) { + $id = null; + } + + $method = $decoded['method'] ?? null; + if (!\is_string($method) || '' === $method) { + return StatelessResult::error(Error::forInvalidRequest('A JSON-RPC message must carry a "method".', $id), 400); + } + + $params = \is_array($decoded['params'] ?? null) ? $decoded['params'] : null; + + // No id is a notification. It gets an acknowledgment, never a response + // — answering one with a JSON-RPC message would invent a correlation + // the client has no request to match it against. Checked before the + // `_meta` parse: notification params carry `NotificationMetaObject`, + // which has none of a request's required members. + if ($isNotification) { + return $this->acknowledge($method); + } + + if (null === $id) { + return StatelessResult::error(Error::forInvalidRequest('A JSON-RPC request id must be a string or a number.'), 400); + } + + try { + $meta = RequestMeta::fromParams($params, $headers); + } catch (MissingRequestMetaException $e) { + return StatelessResult::error(Error::forInvalidParams($e->getMessage(), $id), 400); + } + + if (null !== $versionError = $this->checkVersion($meta, $headers, $id)) { + return $versionError; + } + + // After the version check: a peer on the wrong revision has a more + // fundamental problem than headers that disagree with its body. + if (null !== $headerError = $this->headerValidator?->validate($method, $params, $headers)) { + return StatelessResult::error(Error::forHeaderMismatch($headerError, $id), 400); + } + + if (self::DISCOVER_METHOD === $method || self::LISTEN_METHOD === $method) { + if (self::DISCOVER_METHOD === $method) { + return $this->encode($method, $id, $this->discover()); + } + + return $this->listen($params, $id); + } + + if (\in_array($method, self::REMOVED_METHODS, true)) { + return StatelessResult::error( + Error::forMethodNotFound(\sprintf('Method "%s" does not exist in protocol version %s.', $method, $meta->protocolVersion), $id), + 404, + ); + } + + return $this->dispatch($method, $decoded, $meta, $id, self::acceptsEventStream($headers)); + } + + /** + * Answers a notification. + * + * This revision's core defines no client-to-server notification over HTTP — + * `notifications/cancelled` is stdio-only, since closing the response + * stream is the cancellation signal here — so anything arriving is either + * an extension's or a client still speaking an older revision. Accepting + * the former and refusing the latter both come out as a status with no + * body; what must not happen is a JSON-RPC response. + */ + private function acknowledge(string $method): StatelessResult + { + if (\in_array($method, self::REMOVED_METHODS, true)) { + $this->logger->debug('Refused a notification this revision removed.', ['method' => $method]); + + return StatelessResult::empty(400); + } + + $this->logger->debug('Accepted a notification with no handler to run.', ['method' => $method]); + + return StatelessResult::empty(202); + } + + /** + * Header and `_meta` must agree before the version can be judged supported: + * when they disagree the server cannot know which the client meant, so a + * mismatch outranks an unsupported version. + * + * @param array $headers + */ + private function checkVersion(RequestMeta $meta, array $headers, string|int|null $id): ?StatelessResult + { + $headerVersion = $this->header($headers, 'MCP-Protocol-Version'); + + // REQUIRED on every POST. The 2025-03-26 fallback for a header-less + // request exists only for servers choosing to serve pre-2025-06-18 + // clients, which a modern-only endpoint is not. + if (null === $headerVersion && $this->requiresTransportHeaders()) { + return StatelessResult::error( + Error::forHeaderMismatch( + \sprintf('Missing required MCP-Protocol-Version header (_meta declares "%s").', $meta->protocolVersion), + $id, + ), + 400, + ); + } + + // The same check the HTTP entry runs before routing, so the edge and + // this dispatcher cannot disagree about what a request claims. + if (null !== $mismatch = InboundClassifier::crossCheckVersion($headerVersion, $meta->protocolVersion)) { + return StatelessResult::error(Error::forHeaderMismatch($mismatch, $id), 400); + } + + $version = ProtocolVersion::tryFrom($meta->protocolVersion); + + if (null === $version || !\in_array($version, $this->supportedVersions, true)) { + return StatelessResult::error( + Error::forUnsupportedProtocolVersion($meta->protocolVersion, $this->supportedVersions, $id), + 400, + ); + } + + return null; + } + + /** + * Opens a `subscriptions/listen` stream. The subscription id is the + * JSON-RPC id of this request, so there is none to mint. + * + * @param array|null $params + */ + private function listen(?array $params, string|int $id): StatelessResult + { + $notifications = \is_array($params['notifications'] ?? null) ? $params['notifications'] : null; + $agreed = NotificationFilter::fromParams($notifications)->intersect($this->configuration->capabilities); + + $lifetime = $this->subscriptionLifetime; + $bus = $this->notificationBus; + $codec = $this->codec; + + return StatelessResult::stream(static function () use ($agreed, $id, $lifetime, $bus, $codec): \Generator { + // MUST be the first message carrying this subscription's id, and + // MUST precede any notification on it. + yield [ + 'jsonrpc' => '2.0', + 'method' => self::ACKNOWLEDGED_NOTIFICATION, + 'params' => [ + '_meta' => [RequestMeta::SUBSCRIPTION_ID => $id], + 'notifications' => (object) $agreed->toAcknowledgedArray(), + ], + ]; + + // From now, not from the beginning: a subscriber wants what happens + // next, not a replay of the server's history. + $cursor = $bus?->cursor() ?? 0; + + // The tick is not optional: PHP spots a dropped peer by writing, + // and a sleeping loop would pin an FPM worker for the full lifetime. + $deadline = 0.0 >= $lifetime ? \INF : microtime(true) + $lifetime; + + while (microtime(true) < $deadline) { + if (null !== $bus) { + [$notifications, $cursor] = $bus->since($cursor); + + foreach ($notifications as $notification) { + if (!$agreed->carries($notification)) { + continue; + } + + yield self::tagWithSubscription($notification, $id); + } + } + + yield null; + + if (connection_aborted()) { + return; + } + + usleep(250_000); + } + + // Graceful closure (SHOULD), so the client can tell this from a + // dropped transport. + yield [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'result' => $codec->encodeResult(self::LISTEN_METHOD, [ + 'resultType' => 'complete', + '_meta' => [RequestMeta::SUBSCRIPTION_ID => $id], + ], false), + ]; + }); + } + + /** + * Every message on a listen stream carries the id of the subscription it + * belongs to, which is how a client demultiplexes them on stdio — where + * they all share one channel. + * + * @return array + */ + private static function tagWithSubscription(Notification $notification, string|int $id): array + { + /** @var array $frame */ + $frame = $notification->jsonSerialize(); + + $params = \is_array($frame['params'] ?? null) ? $frame['params'] : []; + $meta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; + + $meta[RequestMeta::SUBSCRIPTION_ID] = $id; + $params['_meta'] = $meta; + $frame['params'] = $params; + + return $frame; + } + + private function discover(): DiscoverResult + { + return new DiscoverResult( + $this->supportedVersions, + $this->configuration->capabilities, + $this->configuration->instructions, + ); + } + + /** + * @param array $decoded + */ + private function dispatch(string $method, array $decoded, RequestMeta $meta, string|int|null $id, bool $wantsStream = false): StatelessResult + { + try { + $messages = $this->messageFactory->create(json_encode($decoded, \JSON_THROW_ON_ERROR)); + } catch (\Throwable $e) { + $this->logger->warning('Rejected an unparseable modern-era request.', ['method' => $method, 'exception' => $e]); + + return StatelessResult::error($this->unknownMethod($method, $id), 404); + } + + $request = $messages[0] ?? null; + + // The factory hands back an exception object rather than throwing one, + // distinguishing a genuinely unknown method (-32601, the client should + // stop asking) from a known method the message could not otherwise be + // parsed into (-32600, the request itself is malformed). + if ($request instanceof InvalidInputMessageException) { + $unknownMethod = \sprintf('Unknown method "%s".', $method) === $request->getMessage(); + + return StatelessResult::error( + $unknownMethod + ? $this->unknownMethod($method, $id) + : Error::forInvalidRequest($request->getMessage(), $id), + $unknownMethod ? 404 : 400, + ); + } + + if (!$request instanceof Request) { + // Reachable only for a well-formed Response/Error object posted to + // this endpoint — decodable by the factory, but not a request this + // server can answer. + return StatelessResult::error(Error::forInvalidRequest(\sprintf('"%s" is not a request this server can answer.', $method), $id), 400); + } + + // Request::fromArray() already rejected a missing/invalid id (as an + // InvalidInputMessageException, handled above), so this request's id + // is always present here — reusing it narrows $id for the rest of this + // method instead of trusting the raw, still-nullable value from above. + $id = $request->getId(); + + $session = new Session(new InMemorySessionStore()); + $session->set(RequestMeta::class, $meta); + + // Under the same keys the handshake era writes, so everything reading + // connection state — ClientGateway's capability probes above all — sees + // this request's declaration instead of an empty session. + $session->set('client_capabilities', $meta->clientCapabilities->jsonSerialize()); + $session->set('protocol_version', $meta->protocolVersion); + + try { + $input = $this->liftInputContext($decoded['params'] ?? null); + } catch (RequestStateException $e) { + // Invalid params, not an authorization failure: the client only + // echoes what it was given, and the reason stays out of the answer. + $this->logger->warning('Rejected a requestState that failed verification.', ['method' => $method, 'reason' => $e->getMessage()]); + + return StatelessResult::error(Error::forInvalidParams('The supplied requestState failed verification.', $id), 400); + } + + if (null !== $input) { + $session->set(InputContext::class, $input); + } + + if (null !== $this->requestStateCodec) { + $session->set(RequestStateCodec::class, $this->requestStateCodec); + } + + // What ClientGateway::progress() reads to find the progress token, and + // the handshake era sets under the same key. + $session->set(Protocol::SESSION_ACTIVE_REQUEST_META, $request->getMeta()); + + foreach ($this->requestHandlers as $handler) { + if (!$handler->supports($request)) { + continue; + } + + $run = $this->run($handler, $request, $session, $meta); + + try { + // Runs the handler up to its first notification, or to the end + // if it emits none. Deciding here and not earlier is what keeps + // the status codes honest: a request that turns out to need + // -32021 has said nothing yet, so it can still be answered + // with 400 rather than an error frame under a 200. + $run->rewind(); + } catch (\Throwable $e) { + return $this->toErrorResult($method, $id, $e); + } + + if ($run->valid() && $wantsStream) { + return StatelessResult::stream(fn (): \Generator => $this->streamFrames($run, $meta, $method, $id, null === $input)); + } + + try { + // Stepped rather than foreach()ed: rewind() already advanced it, + // and a generator will not be traversed a second time. + while ($run->valid()) { + $this->logger->debug('Dropped a notification: the client did not accept a response stream.', [ + 'method' => $method, + 'notification' => $run->current()::getMethod(), + ]); + + $run->next(); + } + + $result = $run->getReturn(); + } catch (\Throwable $e) { + return $this->toErrorResult($method, $id, $e); + } + + if ($result instanceof Error) { + return StatelessResult::error($result, 400); + } + + if (null !== $capabilityError = $this->checkInputRequests($result->result, $meta, $method, $id)) { + return $capabilityError; + } + + return $this->encode($method, $id, $result->result, null === $input); + } + + return StatelessResult::error($this->unknownMethod($method, $id), 404); + } + + /** + * A method with no handler, said as precisely as the server can. + * + * An extension's method is still `-32601` when the extension is off — the + * server genuinely does not implement it — but naming the extension turns + * an opaque refusal into something the caller can act on. + */ + private function unknownMethod(string $method, string|int $id): Error + { + $extension = $this->extensionMethods[$method] ?? null; + + if (null !== $extension) { + return Error::forMethodNotFound( + \sprintf('Method "%s" belongs to the "%s" extension, which this server does not serve.', $method, $extension), + $id, + ); + } + + return Error::forMethodNotFound(\sprintf('No handler found for method "%s".', $method), $id); + } + + /** + * Runs a handler, yielding the notifications it emits as it emits them and + * returning its result. + * + * The fiber is what makes a handler's `$gateway->progress(...)` look + * synchronous while the caller decides where the notification goes. Server + * -to-client *requests* are never forwarded: this revision carries what it + * needs in the result (MRTR), and putting a request on a response stream is + * something the transport binding forbids outright. + * + * An elicitation is answered here rather than refused. Already answered, it + * resumes the fiber and the handler runs on; not yet, and the ask becomes + * the result — abandoning the fiber, since this request has nothing left to + * say and the client will re-send it. Abandoning unwinds it, so a handler's + * `finally` still runs; what does not run is everything after the ask. + * + * That is what lets one handler serve both eras through + * {@see \Mcp\Server\ClientGateway::elicit()}; see {@see ElicitationReplay} + * for what it costs. + * + * @param RequestHandlerInterface $handler + * + * @return \Generator|Error> + */ + private function run(RequestHandlerInterface $handler, Request $request, Session $session, RequestMeta $meta): \Generator + { + $fiber = new \Fiber(static fn (): mixed => $handler->handle($request, $session)); + $input = $session->get(InputContext::class); + $replay = new ElicitationReplay($input instanceof InputContext ? $input : null, $this->requestStateCodec); + + $suspended = $fiber->start(); + + while (!$fiber->isTerminated()) { + if (null !== $elicitation = self::readElicitation($suspended)) { + [$named, $elicit] = $elicitation; + $key = $replay->key($named); + + if (null === $answer = $replay->answer($key, $elicit->mode)) { + try { + return new Response($request->getId(), $replay->ask($key, $elicit)); + } catch (LogicException $e) { + $this->logger->error('A handler asked for input across rounds on a server with no requestState signing key.', ['exception' => $e]); + + return Error::forInternalError('The server could not carry its own state across a round of input.', $request->getId()); + } + } + + $suspended = $fiber->resume(new Response($request->getId(), $answer)); + + continue; + } + + $notification = $this->readNotification($suspended, $meta); + + if (null !== $notification) { + yield $notification; + } + + $suspended = $fiber->resume(null); + } + + /** @var Response|Error $return */ + $return = $fiber->getReturn(); + + return $return; + } + + /** + * Reads one fiber suspension, or null when it carries nothing to send. + * + * @param mixed $suspended the payload {@see \Mcp\Server\ClientGateway} suspended with + */ + private function readNotification(mixed $suspended, RequestMeta $meta): ?Notification + { + if (!\is_array($suspended) || 'notification' !== ($suspended['type'] ?? null)) { + if (\is_array($suspended) && 'request' === ($suspended['type'] ?? null)) { + // Elicitation never reaches here — it is answered in run(). What + // is left are the kinds this revision removed outright, and no + // multi round-trip shape brings them back. + throw new LogicException('This protocol revision has no server-initiated requests: sampling and roots were removed with it, so take what you need through tool arguments, resource URIs or server configuration instead. Elicitation is the one ask that survived, as a multi round-trip request.'); + } + + return null; + } + + $notification = $suspended['notification'] ?? null; + + if (!$notification instanceof Notification) { + return null; + } + + // The client opts into logs per request; with no level named the server + // MUST NOT send any, which is why an absent level drops rather than + // defaults. + if ($notification instanceof LoggingMessageNotification) { + if (null === $meta->logLevel || !$notification->level->isAtLeast($meta->logLevel)) { + return null; + } + } + + return $notification; + } + + /** + * One fiber suspension read as an elicitation, or null when it is not one. + * + * @param mixed $suspended the payload {@see \Mcp\Server\ClientGateway} suspended with + * + * @return array{0: string|null, 1: ElicitRequest}|null the name the handler gave the ask, and the ask + */ + private static function readElicitation(mixed $suspended): ?array + { + if (!\is_array($suspended) || 'request' !== ($suspended['type'] ?? null)) { + return null; + } + + $request = $suspended['request'] ?? null; + + if (!$request instanceof ElicitRequest) { + return null; + } + + $key = $suspended['input_key'] ?? null; + + return [\is_string($key) ? $key : null, $request]; + } + + /** + * The frames of a request-scoped response stream: the notifications the + * handler emits, then the response that ends it. + * + * @param \Generator|Error> $run + * + * @return \Generator + */ + private function streamFrames(\Generator $run, RequestMeta $meta, string $method, string|int $id, bool $cacheable): \Generator + { + try { + while ($run->valid()) { + yield self::withTraceContext($run->current()->jsonSerialize(), $meta->traceContext); + + $run->next(); + } + + $result = $run->getReturn(); + } catch (\Throwable $e) { + // Headers left long ago, so the status is already 200 and the only + // way left to report this is a frame. + yield $this->toErrorResult($method, $id, $e)->message?->jsonSerialize(); + + return; + } + + if (!$result instanceof Error && null !== $capabilityError = $this->checkInputRequests($result->result, $meta, $method, $id)) { + yield $capabilityError->message?->jsonSerialize(); + + return; + } + + yield $result instanceof Error + ? $result->jsonSerialize() + : ['jsonrpc' => '2.0', 'id' => $id, 'result' => $this->codec->encodeResult($method, (array) $result->result->jsonSerialize(), $cacheable)]; + } + + /** + * Refuses to send an ask the client cannot answer. + * + * The handler's mistake rather than the client's, but the client is the one + * that has to hear about it, and `-32021` is precisely the code for + * "processing this needs a capability you did not declare" — so it is + * reported as that, and logged as the server-side bug it is. + */ + private function checkInputRequests(ResultInterface $result, RequestMeta $meta, string $method, string|int $id): ?StatelessResult + { + if (!$result instanceof InputRequiredResult) { + return null; + } + + $missing = InputRequestCapabilities::missing($result, $meta->clientCapabilities); + + if (null === $missing) { + return null; + } + + $this->logger->warning('A handler asked for input the client did not declare it could provide; the ask was replaced with -32021.', [ + 'method' => $method, + 'required' => $missing->jsonSerialize(), + ]); + + return StatelessResult::error( + Error::forMissingRequiredClientCapability( + 'The server needs input this client did not declare it can provide.', + $missing, + $id, + ), + 400, + ); + } + + /** + * Puts the request's trace context back onto a notification it caused, so a + * collector can join the two without the handler carrying it by hand. + * + * @param array $frame + * @param array $traceContext + * + * @return array + */ + private static function withTraceContext(array $frame, array $traceContext): array + { + if ([] === $traceContext) { + return $frame; + } + + $params = \is_array($frame['params'] ?? null) ? $frame['params'] : []; + $frameMeta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : []; + + // Anything the notification set itself wins: it knows its own span. + $params['_meta'] = [...$traceContext, ...$frameMeta]; + $frame['params'] = $params; + + return $frame; + } + + /** + * The one place a handler's exception becomes an answer, so the streaming + * and non-streaming paths cannot disagree about which code it earns. + */ + private function toErrorResult(string $method, string|int $id, \Throwable $e): StatelessResult + { + if ($e instanceof MissingRequiredClientCapabilityException) { + return StatelessResult::error( + Error::forMissingRequiredClientCapability($e->getMessage(), $e->requiredCapabilities, $id), + 400, + ); + } + + if ($e instanceof \InvalidArgumentException) { + return StatelessResult::error(Error::forInvalidParams($e->getMessage(), $id), 400); + } + + if ($e instanceof LogicException) { + // Guidance for the tool author, not a detail leaked from their + // code or a dependency's — safe to echo back verbatim. + return StatelessResult::error(Error::forInternalError($e->getMessage(), $id), 500); + } + + $this->logger->error('Uncaught exception handling a modern-era request.', ['method' => $method, 'exception' => $e]); + + return StatelessResult::error(Error::forInternalError(self::INTERNAL_ERROR_MESSAGE, $id), 500); + } + + /** + * Reads the multi round-trip material off a retry, verifying the state + * before any of it reaches a handler. Neither member means a first call, + * which is what a handler tests to decide whether it still needs to ask. + * + * @param array|null $params + * + * @throws RequestStateException when a state is present but does not verify + */ + private function liftInputContext(?array $params): ?InputContext + { + $responses = \is_array($params['inputResponses'] ?? null) ? $params['inputResponses'] : null; + $state = \is_string($params['requestState'] ?? null) ? $params['requestState'] : null; + + if (null === $responses && null === $state) { + return null; + } + + // Answers are result objects; dropping non-objects leaves the handler + // to ask again rather than read a malformed retry as satisfied. + if (null !== $responses) { + $responses = array_filter($responses, static fn (mixed $response): bool => \is_array($response)); + } + + $payload = []; + + if (null !== $state) { + // A server with no codec never minted a state, so this one cannot + // have come from here. + if (null === $this->requestStateCodec) { + throw new RequestStateException('mac'); + } + + $payload = $this->requestStateCodec->verify($state); + } + + return new InputContext($responses ?? [], $payload); + } + + /** + * Runs a result through the wire codec. Passed as-is rather than via a + * json round trip, which would turn a nested `{}` into `[]`. + */ + private function encode(string $method, string|int $id, ResultInterface $result, bool $cacheable = true): StatelessResult + { + return StatelessResult::ok($id, $this->codec->encodeResult($method, (array) $result->jsonSerialize(), $cacheable)); + } + + /** + * Whether the client will read a response stream. + * + * Clients MUST offer both content types, so this is normally true; a client + * that does not gets its notifications dropped rather than a stream it + * cannot parse. + * + * @param array $headers + */ + private static function acceptsEventStream(array $headers): bool + { + foreach ($headers as $key => $value) { + if (0 === strcasecmp($key, 'Accept')) { + return str_contains(strtolower($value), 'text/event-stream'); + } + } + + return false; + } + + /** + * @param array $headers + */ + private function header(array $headers, string $name): ?string + { + return InboundClassifier::header($headers, $name); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessResult.php b/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessResult.php new file mode 100644 index 0000000..9e2b630 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Stateless/StatelessResult.php @@ -0,0 +1,113 @@ + + */ +final class StatelessResult +{ + /** + * @param (\Closure(): \Generator)|null $frames set instead of $message when the answer is a stream + * @param array|null $body a result body already through the wire codec + */ + private function __construct( + public readonly ?MessageInterface $message, + public readonly int $httpStatus, + public readonly ?\Closure $frames = null, + private readonly ?array $body = null, + private readonly string|int|null $id = null, + private readonly bool $bodyless = false, + ) { + } + + /** + * A successful answer whose body the wire codec has already stamped — a + * plain array, since no result class models the fields it added. + * + * @param array $body + */ + public static function ok(string|int $id, array $body): self + { + return new self(null, 200, null, $body, $id); + } + + public static function error(Error $error, int $httpStatus): self + { + return new self($error, $httpStatus); + } + + /** + * A status with no body — what a notification gets, since it has no id to + * correlate a JSON-RPC message against. + */ + public static function empty(int $httpStatus): self + { + return new self(null, $httpStatus, bodyless: true); + } + + /** + * A long-lived answer delivered as frames — today only + * `subscriptions/listen`. Deferred, since the frames are produced over the + * life of the connection. + * + * @param \Closure(): \Generator $frames + */ + public static function stream(\Closure $frames): self + { + return new self(null, 200, $frames); + } + + public function isStream(): bool + { + return null !== $this->frames; + } + + public function isEmpty(): bool + { + return $this->bodyless; + } + + public function isError(): bool + { + return $this->message instanceof Error; + } + + public function toJson(): string + { + if (null !== $this->body) { + return json_encode([ + 'jsonrpc' => MessageInterface::JSONRPC_VERSION, + 'id' => $this->id, + 'result' => $this->body, + ], \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES); + } + + if ($this->bodyless) { + throw new \LogicException('This result carries no body; send its status alone.'); + } + + if (null === $this->message) { + throw new \LogicException('A streaming or empty result has no single JSON body; check isStream()/isEmpty() first.'); + } + + return json_encode($this->message, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Subscription/InMemoryNotificationBus.php b/admin/vendor/mcp/sdk/src/Server/Subscription/InMemoryNotificationBus.php new file mode 100644 index 0000000..af93cef --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Subscription/InMemoryNotificationBus.php @@ -0,0 +1,74 @@ + + */ +final class InMemoryNotificationBus implements NotificationBusInterface +{ + /** @var array */ + private array $entries = []; + + private int $next = 0; + + /** + * @param int $backlog how many notifications to keep before dropping the oldest + */ + public function __construct( + private readonly int $backlog = 256, + ) { + if ($this->backlog < 1) { + throw new InvalidArgumentException(\sprintf('The notification backlog must be at least one entry, got %d.', $this->backlog)); + } + } + + public function publish(Notification $notification): void + { + $this->entries[$this->next] = $notification; + ++$this->next; + + // A stream that went away must not make this grow forever. + if (\count($this->entries) > $this->backlog) { + $this->entries = \array_slice($this->entries, -$this->backlog, preserve_keys: true); + } + } + + public function cursor(): int + { + return $this->next; + } + + public function since(int $cursor): array + { + $found = []; + + foreach ($this->entries as $sequence => $notification) { + if ($sequence >= $cursor) { + $found[] = $notification; + } + } + + return [$found, $this->next]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Subscription/NotificationBusInterface.php b/admin/vendor/mcp/sdk/src/Server/Subscription/NotificationBusInterface.php new file mode 100644 index 0000000..8569366 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Subscription/NotificationBusInterface.php @@ -0,0 +1,53 @@ + + */ +interface NotificationBusInterface +{ + /** + * Publishes a notification to every stream currently reading forward. + */ + public function publish(Notification $notification): void; + + /** + * The cursor a stream opening now should start from. + * + * Deliberately "now" and not "the beginning": a client that subscribes + * wants what happens next, not a replay of everything the server has done. + */ + public function cursor(): int; + + /** + * Notifications published after $cursor, and the cursor to read from next. + * + * @return array{list, int} + */ + public function since(int $cursor): array; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Subscription/Psr16NotificationBus.php b/admin/vendor/mcp/sdk/src/Server/Subscription/Psr16NotificationBus.php new file mode 100644 index 0000000..2f9b63a --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Subscription/Psr16NotificationBus.php @@ -0,0 +1,113 @@ + + */ +final class Psr16NotificationBus implements NotificationBusInterface +{ + private const CURSOR_KEY = 'cursor'; + + private readonly MessageFactory $messageFactory; + + /** + * @param string $prefix namespace for this bus's keys, so one cache can carry several + * @param int $ttl how long an entry stays readable, in seconds + * @param int $backlog how many entries a reader will look back over + */ + public function __construct( + private readonly CacheInterface $cache, + private readonly string $prefix = 'mcp.notifications.', + private readonly int $ttl = 120, + private readonly int $backlog = 256, + private readonly LoggerInterface $logger = new NullLogger(), + ?MessageFactory $messageFactory = null, + ) { + if ($this->backlog < 1) { + throw new InvalidArgumentException(\sprintf('The notification backlog must be at least one entry, got %d.', $this->backlog)); + } + + $this->messageFactory = $messageFactory ?? MessageFactory::make(); + } + + public function publish(Notification $notification): void + { + $sequence = $this->cursor(); + + $this->cache->set($this->key((string) $sequence), json_encode($notification, \JSON_THROW_ON_ERROR), $this->ttl); + $this->cache->set($this->key(self::CURSOR_KEY), $sequence + 1, $this->ttl); + } + + public function cursor(): int + { + $cursor = $this->cache->get($this->key(self::CURSOR_KEY), 0); + + return \is_int($cursor) ? $cursor : 0; + } + + public function since(int $cursor): array + { + $head = $this->cursor(); + + // A reader that fell far behind reads what is still there, not + // everything it missed. + $from = max($cursor, $head - $this->backlog); + + $found = []; + + for ($sequence = $from; $sequence < $head; ++$sequence) { + $raw = $this->cache->get($this->key((string) $sequence)); + + if (!\is_string($raw)) { + // Expired, or lost to a concurrent publisher taking the same + // number. Neither is worth failing the stream over. + continue; + } + + try { + foreach ($this->messageFactory->create($raw) as $message) { + if ($message instanceof Notification) { + $found[] = $message; + } + } + } catch (\Throwable $e) { + $this->logger->warning('Dropped an unreadable notification from the bus.', ['sequence' => $sequence, 'exception' => $e]); + } + } + + return [$found, $head]; + } + + private function key(string $suffix): string + { + return $this->prefix.$suffix; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Subscription/PublishingEventDispatcher.php b/admin/vendor/mcp/sdk/src/Server/Subscription/PublishingEventDispatcher.php new file mode 100644 index 0000000..a1c4e68 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Subscription/PublishingEventDispatcher.php @@ -0,0 +1,53 @@ + + */ +final class PublishingEventDispatcher implements EventDispatcherInterface +{ + /** @var array */ + private readonly array $listeners; + + public function __construct( + NotificationBusInterface $bus, + private readonly ?EventDispatcherInterface $inner = null, + ) { + $this->listeners = (new RegistryChangePublisher($bus))->listeners(); + } + + public function dispatch(object $event): object + { + $listener = $this->listeners[$event::class] ?? null; + + if (null !== $listener) { + $listener($event); + } + + return $this->inner?->dispatch($event) ?? $event; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Subscription/RegistryChangePublisher.php b/admin/vendor/mcp/sdk/src/Server/Subscription/RegistryChangePublisher.php new file mode 100644 index 0000000..f54a356 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Subscription/RegistryChangePublisher.php @@ -0,0 +1,81 @@ + + */ +final class RegistryChangePublisher +{ + public function __construct( + private readonly NotificationBusInterface $bus, + ) { + } + + public function onToolListChanged(ToolListChangedEvent $event): void + { + $this->bus->publish(new ToolListChangedNotification()); + } + + public function onPromptListChanged(PromptListChangedEvent $event): void + { + $this->bus->publish(new PromptListChangedNotification()); + } + + public function onResourceListChanged(ResourceListChangedEvent $event): void + { + $this->bus->publish(new ResourceListChangedNotification()); + } + + public function onResourceTemplateListChanged(ResourceTemplateListChangedEvent $event): void + { + $this->bus->publish(new ResourceListChangedNotification()); + } + + /** + * Every event this publisher handles, keyed by event class. + * + * Shaped for a dispatcher that wants a map; a framework's own subscriber + * conventions can read it too rather than restating the list. + * + * @return array + */ + public function listeners(): array + { + return [ + ToolListChangedEvent::class => $this->onToolListChanged(...), + PromptListChangedEvent::class => $this->onPromptListChanged(...), + ResourceListChangedEvent::class => $this->onResourceListChanged(...), + ResourceTemplateListChangedEvent::class => $this->onResourceTemplateListChanged(...), + ]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/BaseTransport.php b/admin/vendor/mcp/sdk/src/Server/Transport/BaseTransport.php new file mode 100644 index 0000000..5817235 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/BaseTransport.php @@ -0,0 +1,143 @@ + + * + * @author Kyrian Obikwelu + */ +abstract class BaseTransport implements TransportInterface +{ + use ManagesTransportCallbacks; + + protected ?Uuid $sessionId = null; + + /** + * @var McpFiber|null + */ + protected ?\Fiber $sessionFiber = null; + + protected LoggerInterface $logger; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + } + + public function initialize(): void + { + } + + public function close(): void + { + } + + public function setSessionId(?Uuid $sessionId): void + { + $this->sessionId = $sessionId; + } + + /** + * @param McpFiber $fiber + */ + public function attachFiberToSession(\Fiber $fiber, Uuid $sessionId): void + { + $this->sessionFiber = $fiber; + $this->sessionId = $sessionId; + } + + /** + * @return array}> + */ + protected function getOutgoingMessages(?Uuid $sessionId): array + { + if ($sessionId && \is_callable($this->outgoingMessagesProvider)) { + return ($this->outgoingMessagesProvider)($sessionId); + } + + return []; + } + + /** + * @return array> + */ + protected function getPendingRequests(?Uuid $sessionId): array + { + if ($sessionId && \is_callable($this->pendingRequestsProvider)) { + return ($this->pendingRequestsProvider)($sessionId); + } + + return []; + } + + /** + * @phpstan-return FiberResume + */ + protected function checkForResponse(int $requestId, ?Uuid $sessionId): Response|Error|null + { + if ($sessionId && \is_callable($this->responseFinder)) { + return ($this->responseFinder)($requestId, $sessionId); + } + + return null; + } + + /** + * @param FiberSuspend|null $yielded + */ + protected function handleFiberYield(mixed $yielded, ?Uuid $sessionId): void + { + if (null === $yielded || !\is_callable($this->fiberYieldHandler)) { + return; + } + + try { + ($this->fiberYieldHandler)($yielded, $sessionId); + } catch (\Throwable $e) { + $this->logger->error('Fiber yield handler failed.', [ + 'exception' => $e, + 'sessionId' => $sessionId?->toRfc4122(), + ]); + } + } + + protected function handleMessage(string $payload, ?Uuid $sessionId): void + { + if (\is_callable($this->messageListener)) { + ($this->messageListener)($this, $payload, $sessionId); + } + } + + protected function handleSessionEnd(?Uuid $sessionId): void + { + if ($sessionId && \is_callable($this->sessionEndListener)) { + ($this->sessionEndListener)($sessionId); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/CallbackStream.php b/admin/vendor/mcp/sdk/src/Server/Transport/CallbackStream.php new file mode 100644 index 0000000..72d74b6 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/CallbackStream.php @@ -0,0 +1,157 @@ + + */ +final class CallbackStream implements \Stringable, StreamInterface +{ + private bool $called = false; + + private ?\Throwable $exception = null; + + /** + * @param callable(): void $callback The callback to execute when stream is read + */ + public function __construct(private $callback, private LoggerInterface $logger = new NullLogger()) + { + } + + public function __toString(): string + { + try { + $this->invoke(); + } catch (\Throwable $e) { + $this->exception = $e; + $this->logger->error( + \sprintf('CallbackStream execution failed: %s', $e->getMessage()), + ['exception' => $e] + ); + } + + return ''; + } + + public function read($length): string + { + $this->invoke(); + + if (null !== $this->exception) { + throw $this->exception; + } + + return ''; + } + + public function getContents(): string + { + $this->invoke(); + + if (null !== $this->exception) { + throw $this->exception; + } + + return ''; + } + + public function eof(): bool + { + return $this->called; + } + + public function close(): void + { + // No-op - callback-based stream doesn't need closing + } + + public function detach() + { + return null; + } + + public function getSize(): ?int + { + return null; // Unknown size for callback streams + } + + public function tell(): int + { + return 0; + } + + public function isSeekable(): bool + { + return false; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + throw new RuntimeException('Stream is not seekable'); + } + + public function rewind(): void + { + throw new RuntimeException('Stream is not rewindable'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new RuntimeException('Stream is not writable'); + } + + public function isReadable(): bool + { + return !$this->called; + } + + private function invoke(): void + { + if ($this->called) { + return; + } + + $this->called = true; + $this->exception = null; + ($this->callback)(); + } + + public function getMetadata($key = null) + { + return null === $key ? [] : null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/JsonRpcErrorResponse.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/JsonRpcErrorResponse.php new file mode 100644 index 0000000..592d179 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/JsonRpcErrorResponse.php @@ -0,0 +1,41 @@ +createResponse($statusCode) + ->withHeader('Content-Type', 'application/json') + ->withBody($streamFactory->createStream($body)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/AuthorizationMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/AuthorizationMiddleware.php new file mode 100644 index 0000000..d417598 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/AuthorizationMiddleware.php @@ -0,0 +1,182 @@ + + */ +final class AuthorizationMiddleware implements MiddlewareInterface +{ + private ResponseFactoryInterface $responseFactory; + + /** + * @param AuthorizationTokenValidatorInterface $validator Token validator implementation + * @param ProtectedResourceMetadata $resourceMetadata Protected resource metadata object used for challenge hints + * @param ResponseFactoryInterface|null $responseFactory PSR-17 response factory (auto-discovered if null) + */ + public function __construct( + private AuthorizationTokenValidatorInterface $validator, + private ProtectedResourceMetadata $resourceMetadata, + ?ResponseFactoryInterface $responseFactory = null, + ) { + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $authorization = $request->getHeaderLine('Authorization'); + if ('' === $authorization) { + return $this->buildErrorResponse($request, AuthorizationResult::unauthorized()); + } + + $accessToken = $this->parseBearerToken($authorization); + if (null === $accessToken) { + return $this->buildErrorResponse( + $request, + AuthorizationResult::badRequest('invalid_request', 'Malformed Authorization header.'), + ); + } + + $result = $this->validator->validate($accessToken); + if (!$result->isAllowed()) { + return $this->buildErrorResponse($request, $result); + } + + return $handler->handle($this->applyAttributes($request, $result->getAttributes())); + } + + private function buildErrorResponse(ServerRequestInterface $request, AuthorizationResult $result): ResponseInterface + { + $response = $this->responseFactory->createResponse($result->getStatusCode()); + $header = $this->buildAuthenticateHeader($request, $result); + + $response = $response->withHeader('WWW-Authenticate', $header); + + return $response; + } + + private function buildAuthenticateHeader(ServerRequestInterface $request, AuthorizationResult $result): string + { + $parts = []; + + $parts[] = 'resource_metadata="'.$this->escapeHeaderValue($this->resolveResourceMetadataUrl($request)).'"'; + + $scopes = $this->resolveScopes($result); + if (null !== $scopes) { + $parts[] = 'scope="'.$this->escapeHeaderValue(implode(' ', $scopes)).'"'; + } + + if (null !== $result->getError()) { + $parts[] = 'error="'.$this->escapeHeaderValue($result->getError()).'"'; + } + + if (null !== $result->getErrorDescription()) { + $parts[] = 'error_description="'.$this->escapeHeaderValue($result->getErrorDescription()).'"'; + } + + return 'Bearer '.implode(', ', $parts); + } + + /** + * @return list|null + */ + private function resolveScopes(AuthorizationResult $result): ?array + { + $scopes = $this->normalizeScopes($result->getScopes()); + if (null !== $scopes) { + return $scopes; + } + + return $this->normalizeScopes($this->resourceMetadata->getScopesSupported()); + } + + /** + * @param list|null $scopes + * + * @return list|null + */ + private function normalizeScopes(?array $scopes): ?array + { + if (null === $scopes) { + return null; + } + + $normalized = array_values(array_filter(array_map('trim', $scopes), static function (string $scope): bool { + return '' !== $scope; + })); + + return [] === $normalized ? null : $normalized; + } + + private function resolveResourceMetadataUrl(ServerRequestInterface $request): string + { + $metadataPath = $this->resourceMetadata->getPrimaryMetadataPath(); + + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $authority = $uri->getAuthority(); + + if ('' === $scheme || '' === $authority) { + throw new RuntimeException('Cannot resolve resource metadata URL: request URI must have scheme and authority'); + } + + return $scheme.'://'.$authority.$metadataPath; + } + + /** + * @param array $attributes + */ + private function applyAttributes(ServerRequestInterface $request, array $attributes): ServerRequestInterface + { + foreach ($attributes as $name => $value) { + $request = $request->withAttribute($name, $value); + } + + return $request; + } + + private function parseBearerToken(string $authorization): ?string + { + if (!preg_match('/^Bearer\\s+(.+)$/i', $authorization, $matches)) { + return null; + } + + $token = trim($matches[1]); + + return '' === $token ? null : $token; + } + + private function escapeHeaderValue(string $value): string + { + return str_replace(['\\', '"'], ['\\\\', '\\"'], $value); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ClientRegistrationMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ClientRegistrationMiddleware.php new file mode 100644 index 0000000..6b94e30 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ClientRegistrationMiddleware.php @@ -0,0 +1,177 @@ +responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $path = $request->getUri()->getPath(); + + if ('POST' === $request->getMethod() && self::REGISTRATION_PATH === $path) { + return $this->handleRegistration($request); + } + + $response = $handler->handle($request); + + if ('GET' === $request->getMethod() && '/.well-known/oauth-authorization-server' === $path) { + return $this->enrichAuthServerMetadata($response); + } + + return $response; + } + + private function handleRegistration(ServerRequestInterface $request): ResponseInterface + { + $contentType = $request->getHeaderLine('Content-Type'); + if (!str_starts_with(strtolower($contentType), 'application/json')) { + return $this->jsonResponse(400, [ + 'error' => 'invalid_client_metadata', + 'error_description' => 'Content-Type must be application/json.', + ], ['Cache-Control' => 'no-store']); + } + + $body = $request->getBody()->__toString(); + + try { + $decoded = json_decode($body, false, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + return $this->jsonResponse(400, [ + 'error' => 'invalid_client_metadata', + 'error_description' => 'Request body must be valid JSON.', + ], ['Cache-Control' => 'no-store']); + } + + if (!$decoded instanceof \stdClass) { + return $this->jsonResponse(400, [ + 'error' => 'invalid_client_metadata', + 'error_description' => 'Request body must be a JSON object.', + ], ['Cache-Control' => 'no-store']); + } + + // Re-decode with assoc=true so nested objects become arrays (safe — already validated above) + /** @var array $data */ + $data = json_decode($body, true, 512, \JSON_THROW_ON_ERROR); + + try { + $result = $this->registrar->register($data); + } catch (ClientRegistrationException $e) { + return $this->jsonResponse(400, [ + 'error' => $e->errorCode, + 'error_description' => $e->getMessage(), + ], ['Cache-Control' => 'no-store']); + } + + return $this->jsonResponse(201, $result, [ + 'Cache-Control' => 'no-store', + ]); + } + + private function enrichAuthServerMetadata(ResponseInterface $response): ResponseInterface + { + if (200 !== $response->getStatusCode()) { + return $response; + } + + $stream = $response->getBody(); + + if ($stream->isSeekable()) { + $stream->rewind(); + } + + try { + $metadata = json_decode($stream->__toString(), true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + if ($stream->isSeekable()) { + $stream->rewind(); + } + + return $response; + } + + if (!\is_array($metadata) || ([] !== $metadata && array_is_list($metadata))) { + if ($stream->isSeekable()) { + $stream->rewind(); + } + + return $response; + } + + $metadata['registration_endpoint'] = rtrim($this->localBaseUrl, '/').self::REGISTRATION_PATH; + + return $response + ->withBody($this->streamFactory->createStream( + json_encode($metadata, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES), + )) + ->withHeader('Content-Type', 'application/json') + ->withoutHeader('Content-Length'); + } + + /** + * @param array $data + * @param array $extraHeaders + */ + private function jsonResponse(int $status, array $data, array $extraHeaders = []): ResponseInterface + { + $response = $this->responseFactory + ->createResponse($status) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream( + json_encode($data, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES), + )); + + foreach ($extraHeaders as $name => $value) { + if ('' !== $value) { + $response = $response->withHeader($name, $value); + } + } + + return $response; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/CorsMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/CorsMiddleware.php new file mode 100644 index 0000000..a32d4ca --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/CorsMiddleware.php @@ -0,0 +1,152 @@ + + */ +final class CorsMiddleware implements MiddlewareInterface +{ + private readonly bool $isWildcard; + private readonly bool $varyOnOrigin; + private readonly string $allowedMethodsHeader; + private readonly string $allowedHeadersHeader; + private readonly ?string $exposedHeadersHeader; + + /** + * @param list $allowedOrigins Origins permitted for cross-origin requests. Empty disables `Access-Control-Allow-Origin`. Use `['*']` to allow any origin. + * @param list $allowedMethods Methods advertised via `Access-Control-Allow-Methods` (preflight only) + * @param list $allowedHeaders Headers advertised via `Access-Control-Allow-Headers` (preflight only) + * @param list $exposedHeaders Headers advertised via `Access-Control-Expose-Headers` + * @param bool $allowCredentials Whether to emit `Access-Control-Allow-Credentials: true`. Incompatible with `allowedOrigins: ['*']` — combining them throws. + */ + public function __construct( + private readonly array $allowedOrigins = [], + array $allowedMethods = ['GET', 'POST', 'DELETE'], + array $allowedHeaders = [ + 'Accept', + 'Authorization', + 'Content-Type', + 'Last-Event-ID', + StreamableHttpTransport::PROTOCOL_VERSION_HEADER, + StreamableHttpTransport::SESSION_HEADER, + ], + array $exposedHeaders = [StreamableHttpTransport::SESSION_HEADER], + private readonly bool $allowCredentials = false, + ) { + $this->isWildcard = \in_array('*', $allowedOrigins, true); + + if ($this->isWildcard && $allowCredentials) { + throw new InvalidArgumentException('Access-Control-Allow-Origin: * is incompatible with Access-Control-Allow-Credentials: true. Configure an explicit allowedOrigins list when credentialed requests are required.'); + } + + $this->varyOnOrigin = [] !== $allowedOrigins && !$this->isWildcard; + $this->allowedMethodsHeader = implode(', ', $allowedMethods); + $this->allowedHeadersHeader = implode(', ', $allowedHeaders); + $this->exposedHeadersHeader = [] === $exposedHeaders ? null : implode(', ', $exposedHeaders); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + + $allowedOrigin = $this->resolveAllowedOrigin($request->getHeaderLine('Origin')); + if (null !== $allowedOrigin && !$response->hasHeader('Access-Control-Allow-Origin')) { + $response = $response->withHeader('Access-Control-Allow-Origin', $allowedOrigin); + } + + if ($this->allowCredentials && null !== $allowedOrigin && !$response->hasHeader('Access-Control-Allow-Credentials')) { + $response = $response->withHeader('Access-Control-Allow-Credentials', 'true'); + } + + if ($this->varyOnOrigin) { + $response = $this->ensureVaryOrigin($response); + } + + if ($this->isPreflight($request)) { + if (!$response->hasHeader('Access-Control-Allow-Methods')) { + $response = $response->withHeader('Access-Control-Allow-Methods', $this->allowedMethodsHeader); + } + + if (!$response->hasHeader('Access-Control-Allow-Headers')) { + $response = $response->withHeader('Access-Control-Allow-Headers', $this->allowedHeadersHeader); + } + } + + if (null !== $this->exposedHeadersHeader && !$response->hasHeader('Access-Control-Expose-Headers')) { + $response = $response->withHeader('Access-Control-Expose-Headers', $this->exposedHeadersHeader); + } + + return $response; + } + + private function isPreflight(ServerRequestInterface $request): bool + { + return 'OPTIONS' === $request->getMethod() + && $request->hasHeader('Access-Control-Request-Method'); + } + + private function resolveAllowedOrigin(string $origin): ?string + { + if ([] === $this->allowedOrigins) { + return null; + } + + if ($this->isWildcard) { + return '*'; + } + + if ('' !== $origin && \in_array($origin, $this->allowedOrigins, true)) { + return $origin; + } + + return null; + } + + private function ensureVaryOrigin(ResponseInterface $response): ResponseInterface + { + $current = $response->getHeaderLine('Vary'); + + if ('' === $current) { + return $response->withHeader('Vary', 'Origin'); + } + + if ('*' === trim($current)) { + return $response; + } + + $tokens = array_map('strtolower', array_map('trim', explode(',', $current))); + if (\in_array('origin', $tokens, true)) { + return $response; + } + + return $response->withHeader('Vary', $current.', Origin'); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/DnsRebindingProtectionMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/DnsRebindingProtectionMiddleware.php new file mode 100644 index 0000000..490aa37 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/DnsRebindingProtectionMiddleware.php @@ -0,0 +1,111 @@ + + */ +final class DnsRebindingProtectionMiddleware implements MiddlewareInterface +{ + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + /** @var list */ + private readonly array $allowedHosts; + + /** + * @param list $allowedHosts Hostnames (without port) that are permitted. Defaults to localhost variants. + * IPv6 addresses must be bracketed (e.g. `[::1]`) — that is the canonical form returned by `parse_url`. + * @param ResponseFactoryInterface|null $responseFactory PSR-17 response factory (auto-discovered if null) + * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory (auto-discovered if null) + */ + public function __construct( + array $allowedHosts = ['localhost', '127.0.0.1', '[::1]'], + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ) { + $this->allowedHosts = array_values(array_map('strtolower', $allowedHosts)); + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $origin = $request->getHeaderLine('Origin'); + if ('' !== $origin) { + if (!$this->isAllowedOrigin($origin)) { + return $this->createForbiddenResponse('Forbidden: Invalid Origin header.'); + } + + return $handler->handle($request); + } + + $host = $request->getHeaderLine('Host'); + if ('' !== $host && !$this->isAllowedHost($host)) { + return $this->createForbiddenResponse('Forbidden: Invalid Host header.'); + } + + return $handler->handle($request); + } + + private function isAllowedOrigin(string $origin): bool + { + $host = parse_url($origin, \PHP_URL_HOST); + if (!\is_string($host) || '' === $host) { + return false; + } + + return \in_array(strtolower($host), $this->allowedHosts, true); + } + + private function isAllowedHost(string $host): bool + { + if (str_starts_with($host, '[')) { + $closingBracket = strpos($host, ']'); + if (false === $closingBracket) { + return false; + } + $hostname = substr($host, 0, $closingBracket + 1); + } else { + $hostname = explode(':', $host, 2)[0]; + } + + return \in_array(strtolower($hostname), $this->allowedHosts, true); + } + + private function createForbiddenResponse(string $message): ResponseInterface + { + return $this->responseFactory + ->createResponse(403) + ->withHeader('Content-Type', 'text/plain') + ->withBody($this->streamFactory->createStream($message)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthProxyMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthProxyMiddleware.php new file mode 100644 index 0000000..f59a6dc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthProxyMiddleware.php @@ -0,0 +1,268 @@ + + */ +final class OAuthProxyMiddleware implements MiddlewareInterface +{ + private const CLIENT_SECRET_BASIC = 'client_secret_basic'; + private const CLIENT_SECRET_POST = 'client_secret_post'; + + private ClientInterface $httpClient; + private RequestFactoryInterface $requestFactory; + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + /** + * @param string $upstreamIssuer The issuer URL of the upstream OAuth provider + * @param string $localBaseUrl The base URL of this MCP server (e.g., http://localhost:8000) + * @param string|null $clientSecret Optional client secret for confidential clients + * @param OidcDiscoveryInterface $discovery OIDC discovery provider for upstream metadata + */ + public function __construct( + private readonly string $upstreamIssuer, + private readonly string $localBaseUrl, + private readonly OidcDiscoveryInterface $discovery, + private readonly ?string $clientSecret = null, + ?ClientInterface $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ) { + $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); + $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $path = $request->getUri()->getPath(); + + if ('GET' === $request->getMethod() && '/.well-known/oauth-authorization-server' === $path) { + return $this->createAuthServerMetadataResponse(); + } + + if ('GET' === $request->getMethod() && '/authorize' === $path) { + return $this->handleAuthorize($request); + } + + if ('POST' === $request->getMethod() && '/token' === $path) { + return $this->handleToken($request); + } + + return $handler->handle($request); + } + + private function handleAuthorize(ServerRequestInterface $request): ResponseInterface + { + try { + $authorizationEndpoint = $this->discovery->getAuthorizationEndpoint($this->upstreamIssuer); + } catch (RuntimeException) { + return $this->createErrorResponse(500, 'Upstream authorization endpoint not found'); + } + + $rawQueryString = $request->getUri()->getQuery(); + $upstreamUrl = $authorizationEndpoint; + if ('' !== $rawQueryString) { + $upstreamUrl .= '?'.$rawQueryString; + } + + return $this->responseFactory + ->createResponse(302) + ->withHeader('Location', $upstreamUrl) + ->withHeader('Cache-Control', 'no-store'); + } + + private function handleToken(ServerRequestInterface $request): ResponseInterface + { + try { + $tokenEndpoint = $this->discovery->getTokenEndpoint($this->upstreamIssuer); + } catch (RuntimeException) { + return $this->createErrorResponse(500, 'Upstream token endpoint not found'); + } + + $body = $request->getBody()->__toString(); + parse_str($body, $params); + + $upstreamAuthorization = trim($request->getHeaderLine('Authorization')); + if ('' === $upstreamAuthorization) { + $upstreamAuthorization = null; + } + + if (null !== $this->clientSecret && !isset($params['client_secret']) && null === $upstreamAuthorization) { + $authMethod = $this->resolveTokenEndpointAuthMethod(); + + if (self::CLIENT_SECRET_BASIC === $authMethod) { + $clientId = $params['client_id'] ?? null; + + if (\is_string($clientId) && '' !== trim($clientId)) { + $upstreamAuthorization = 'Basic '.base64_encode(trim($clientId).':'.$this->clientSecret); + } else { + $params['client_secret'] = $this->clientSecret; + } + } else { + $params['client_secret'] = $this->clientSecret; + } + } + + $body = http_build_query($params); + + $upstreamRequest = $this->requestFactory + ->createRequest('POST', $tokenEndpoint) + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($this->streamFactory->createStream($body)); + + if (null !== $upstreamAuthorization) { + $upstreamRequest = $upstreamRequest->withHeader('Authorization', $upstreamAuthorization); + } + + try { + $upstreamResponse = $this->httpClient->sendRequest($upstreamRequest); + $responseBody = $upstreamResponse->getBody()->__toString(); + + return $this->responseFactory + ->createResponse($upstreamResponse->getStatusCode()) + ->withHeader('Content-Type', $upstreamResponse->getHeaderLine('Content-Type')) + ->withHeader('Cache-Control', 'no-store') + ->withBody($this->streamFactory->createStream($responseBody)); + } catch (ClientExceptionInterface $e) { + return $this->createErrorResponse(502, 'Failed to contact upstream token endpoint: '.$e->getMessage()); + } + } + + private function createAuthServerMetadataResponse(): ResponseInterface + { + try { + $upstreamMetadata = $this->discovery->discover($this->upstreamIssuer); + } catch (RuntimeException) { + return $this->createErrorResponse(500, 'Failed to discover upstream server metadata'); + } + + $localBaseUrl = rtrim($this->localBaseUrl, '/'); + $localMetadata = [ + 'issuer' => $localBaseUrl, + 'authorization_endpoint' => $localBaseUrl.'/authorize', + 'token_endpoint' => $localBaseUrl.'/token', + 'response_types_supported' => $upstreamMetadata['response_types_supported'] ?? ['code'], + 'grant_types_supported' => $upstreamMetadata['grant_types_supported'] ?? ['authorization_code', 'refresh_token'], + 'code_challenge_methods_supported' => $upstreamMetadata['code_challenge_methods_supported'] ?? ['S256'], + ]; + + $copyFields = [ + 'scopes_supported', + 'token_endpoint_auth_methods_supported', + 'jwks_uri', + ]; + + foreach ($copyFields as $field) { + if (isset($upstreamMetadata[$field])) { + $localMetadata[$field] = $upstreamMetadata[$field]; + } + } + + return $this->responseFactory + ->createResponse(200) + ->withHeader('Content-Type', 'application/json') + ->withHeader('Cache-Control', 'max-age=3600') + ->withBody($this->streamFactory->createStream(json_encode($localMetadata, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES))); + } + + private function createErrorResponse(int $status, string $message): ResponseInterface + { + $body = json_encode(['error' => 'server_error', 'error_description' => $message]); + + return $this->responseFactory + ->createResponse($status) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream($body)); + } + + private function resolveTokenEndpointAuthMethod(): string + { + $supportedMethods = $this->getTokenEndpointAuthMethods(); + + if (\in_array(self::CLIENT_SECRET_BASIC, $supportedMethods, true)) { + return self::CLIENT_SECRET_BASIC; + } + + if (\in_array(self::CLIENT_SECRET_POST, $supportedMethods, true)) { + return self::CLIENT_SECRET_POST; + } + + return self::CLIENT_SECRET_POST; + } + + /** + * @return list + */ + private function getTokenEndpointAuthMethods(): array + { + try { + $metadata = $this->discovery->discover($this->upstreamIssuer); + } catch (RuntimeException) { + return []; + } + + $methods = $metadata['token_endpoint_auth_methods_supported'] ?? null; + if (!\is_array($methods)) { + return []; + } + + $normalized = []; + foreach ($methods as $method) { + if (!\is_string($method)) { + continue; + } + + $method = trim($method); + if ('' === $method) { + continue; + } + + $normalized[] = $method; + } + + return array_values(array_unique($normalized)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthRequestMetaMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthRequestMetaMiddleware.php new file mode 100644 index 0000000..d1595a5 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/OAuthRequestMetaMiddleware.php @@ -0,0 +1,146 @@ + + */ +final class OAuthRequestMetaMiddleware implements MiddlewareInterface +{ + private StreamFactoryInterface $streamFactory; + + public function __construct(?StreamFactoryInterface $streamFactory = null) + { + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ('POST' !== $request->getMethod()) { + return $handler->handle($request); + } + + $oauthMeta = $this->extractOAuthAttributes($request); + if ([] === $oauthMeta) { + return $handler->handle($request); + } + + $body = $request->getBody()->__toString(); + if ('' === trim($body)) { + return $handler->handle($request); + } + + try { + $payload = json_decode($body, true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + return $handler->handle($request); + } + + $updatedPayload = $this->injectOauthMeta($payload, $oauthMeta); + if (null === $updatedPayload) { + return $handler->handle($request); + } + + try { + $updatedBody = json_encode($updatedPayload, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES); + } catch (\JsonException) { + return $handler->handle($request); + } + + $request = $request->withBody($this->streamFactory->createStream($updatedBody)); + + return $handler->handle($request); + } + + /** + * @return array + */ + private function extractOAuthAttributes(ServerRequestInterface $request): array + { + $result = []; + foreach ($request->getAttributes() as $key => $value) { + if (\is_string($key) && str_starts_with($key, 'oauth.')) { + $result[$key] = $value; + } + } + + return $result; + } + + /** + * @param array $oauthMeta + */ + private function injectOauthMeta(mixed $payload, array $oauthMeta): mixed + { + if (!\is_array($payload)) { + return null; + } + + if (array_is_list($payload)) { + $updated = []; + foreach ($payload as $entry) { + if (!\is_array($entry)) { + $updated[] = $entry; + continue; + } + + $updated[] = $this->injectIntoMessage($entry, $oauthMeta); + } + + return $updated; + } + + return $this->injectIntoMessage($payload, $oauthMeta); + } + + /** + * @param array $message + * @param array $oauthMeta + * + * @return array + */ + private function injectIntoMessage(array $message, array $oauthMeta): array + { + $params = $message['params'] ?? []; + if (!\is_array($params)) { + return $message; + } + + $meta = $params['_meta'] ?? []; + if (!\is_array($meta)) { + $meta = []; + } + + $existingOAuth = $meta['oauth'] ?? []; + if (!\is_array($existingOAuth)) { + $existingOAuth = []; + } + + $meta['oauth'] = array_merge($existingOAuth, $oauthMeta); + $params['_meta'] = $meta; + $message['params'] = $params; + + return $message; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddleware.php new file mode 100644 index 0000000..67a0cdc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtectedResourceMetadataMiddleware.php @@ -0,0 +1,64 @@ + + */ +final class ProtectedResourceMetadataMiddleware implements MiddlewareInterface +{ + private ProtectedResourceMetadataHandler $metadataHandler; + + public function __construct( + private readonly ProtectedResourceMetadata $metadata, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ) { + $this->metadataHandler = new ProtectedResourceMetadataHandler($metadata, $responseFactory, $streamFactory); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (!$this->isMetadataRequest($request)) { + return $handler->handle($request); + } + + return $this->metadataHandler->handle($request); + } + + private function isMetadataRequest(ServerRequestInterface $request): bool + { + if ('GET' !== $request->getMethod()) { + return false; + } + + return \in_array($request->getUri()->getPath(), $this->metadata->getMetadataPaths(), true); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtocolVersionMiddleware.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtocolVersionMiddleware.php new file mode 100644 index 0000000..e4ec015 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/Middleware/ProtocolVersionMiddleware.php @@ -0,0 +1,96 @@ + + */ +final class ProtocolVersionMiddleware implements MiddlewareInterface +{ + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + /** @var list */ + private readonly array $supportedVersions; + + /** @var list */ + private readonly array $supported; + + /** + * @param list|null $supportedVersions Versions the server accepts. Defaults to {@see ProtocolVersion::handshakeVersions()}. {@see StreamableHttpTransport::handshakeMiddleware()} always uses that default: it runs only on traffic already classified as handshake-era, so this middleware never needs to know about modern revisions there. + * @param ResponseFactoryInterface|null $responseFactory PSR-17 response factory (auto-discovered if null) + * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory (auto-discovered if null) + */ + public function __construct( + ?array $supportedVersions = null, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ) { + $versions = $supportedVersions ?? ProtocolVersion::handshakeVersions(); + $this->supported = array_values($versions); + $this->supportedVersions = array_map(static fn (ProtocolVersion $v): string => $v->value, $this->supported); + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $headerValue = $request->getHeaderLine(StreamableHttpTransport::PROTOCOL_VERSION_HEADER); + + // Spec backwards-compat: when the header is absent, the server SHOULD assume + // protocol version 2025-03-26 — the release in which Streamable HTTP and the + // header itself were introduced. This is deliberately lower than the SDK's + // own default so clients predating the header convention still get a + // deterministic protocol version applied. Servers that whitelist only newer + // versions in $supportedVersions will reject such requests with 400. + $version = '' === $headerValue ? ProtocolVersion::DEFAULT_HEADER_VERSION->value : $headerValue; + + if (\in_array($version, $this->supportedVersions, true)) { + return $handler->handle($request); + } + + // The client is expected to pick a mutually supported version from the + // error payload and retry, so the supported set travels as structured + // data rather than only inside the message. + $error = Error::forUnsupportedProtocolVersion($version, $this->supported); + + return JsonRpcErrorResponse::create($this->responseFactory, $this->streamFactory, 400, $error); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/MiddlewareRequestHandler.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/MiddlewareRequestHandler.php new file mode 100644 index 0000000..32ad372 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/MiddlewareRequestHandler.php @@ -0,0 +1,48 @@ + + * + * @internal + */ +final class MiddlewareRequestHandler implements RequestHandlerInterface +{ + private int $index = 0; + + /** + * @param list $middleware + */ + public function __construct( + private readonly array $middleware, + private readonly \Closure $application, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if (!isset($this->middleware[$this->index])) { + return ($this->application)($request); + } + + return $this->middleware[$this->index++]->process($request, $this); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationResult.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationResult.php new file mode 100644 index 0000000..4db13f4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationResult.php @@ -0,0 +1,135 @@ + + */ +final class AuthorizationResult +{ + /** + * @param list|null $scopes Scopes to include in WWW-Authenticate challenge + * @param array $attributes Attributes to attach to the request on success + */ + private function __construct( + private readonly bool $allowed, + private readonly int $statusCode, + private readonly ?string $error, + private readonly ?string $errorDescription, + private readonly ?array $scopes, + private readonly array $attributes, + ) { + } + + /** + * Creates a result indicating access is allowed. + * + * @param array $attributes Attributes to attach to the request (e.g., user_id, scopes) + */ + public static function allow(array $attributes = []): self + { + return new self(true, 200, null, null, null, $attributes); + } + + /** + * Creates a result indicating the request is unauthorized (401). + * + * Use when no valid credentials are provided or the token is invalid. + * + * @param string|null $error OAuth error code (e.g., "invalid_token") + * @param string|null $errorDescription Human-readable error description + * @param list|null $scopes Required scopes to include in challenge + */ + public static function unauthorized( + ?string $error = null, + ?string $errorDescription = null, + ?array $scopes = null, + ): self { + return new self(false, 401, $error, $errorDescription, $scopes, []); + } + + /** + * Creates a result indicating the request is forbidden (403). + * + * Use when the token is valid but lacks required permissions/scopes. + * + * @param string|null $error OAuth error code (defaults to "insufficient_scope") + * @param string|null $errorDescription Human-readable error description + * @param list|null $scopes Required scopes to include in challenge + */ + public static function forbidden( + ?string $error = 'insufficient_scope', + ?string $errorDescription = null, + ?array $scopes = null, + ): self { + return new self(false, 403, $error ?? 'insufficient_scope', $errorDescription, $scopes, []); + } + + /** + * Creates a result indicating a bad request (400). + * + * Use when the Authorization header is malformed. + * + * @param string|null $error OAuth error code (defaults to "invalid_request") + * @param string|null $errorDescription Human-readable error description + */ + public static function badRequest( + ?string $error = 'invalid_request', + ?string $errorDescription = null, + ): self { + return new self(false, 400, $error ?? 'invalid_request', $errorDescription, null, []); + } + + public function isAllowed(): bool + { + return $this->allowed; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getError(): ?string + { + return $this->error; + } + + public function getErrorDescription(): ?string + { + return $this->errorDescription; + } + + /** + * @return list|null + */ + public function getScopes(): ?array + { + return $this->scopes; + } + + /** + * @return array + */ + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationTokenValidatorInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationTokenValidatorInterface.php new file mode 100644 index 0000000..78b849e --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/AuthorizationTokenValidatorInterface.php @@ -0,0 +1,32 @@ + + */ +interface AuthorizationTokenValidatorInterface +{ + /** + * Validates an access token extracted from the Authorization header. + * + * @param string $accessToken The bearer token (without "Bearer " prefix) + * + * @return AuthorizationResult The result of the validation + */ + public function validate(string $accessToken): AuthorizationResult; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ClientRegistrarInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ClientRegistrarInterface.php new file mode 100644 index 0000000..c0b58dc --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ClientRegistrarInterface.php @@ -0,0 +1,44 @@ + $registrationRequest Client metadata from the registration request body + * + * @return array Registration response including client_id and optional client_secret + * + * @throws ClientRegistrationException If registration fails (e.g. invalid metadata, storage error). + * The exception message is returned to the client as error_description — + * do not include internal details (database errors, stack traces, etc.). + */ + public function register(array $registrationRequest): array; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProvider.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProvider.php new file mode 100644 index 0000000..794ce3b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProvider.php @@ -0,0 +1,122 @@ + + */ +class JwksProvider implements JwksProviderInterface +{ + private const CACHE_KEY_PREFIX = 'mcp_jwks_'; + + private ClientInterface $httpClient; + private RequestFactoryInterface $requestFactory; + + /** + * @param OidcDiscoveryInterface $discovery OIDC discovery provider (required for JWKS URI resolution when $jwksUri is not explicit) + * @param ClientInterface|null $httpClient PSR-18 HTTP client (auto-discovered if null) + * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory (auto-discovered if null) + * @param CacheInterface|null $cache Optional PSR-16 cache + * @param int $cacheTtl JWKS cache TTL in seconds + */ + public function __construct( + private readonly OidcDiscoveryInterface $discovery, + ?ClientInterface $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + private readonly ?CacheInterface $cache = null, + private readonly int $cacheTtl = 3600, + ) { + $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); + $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + } + + /** + * @return array + */ + public function getJwks(string $issuer, ?string $jwksUri = null): array + { + $jwksUri ??= $this->discovery->getJwksUri($issuer); + $cacheKey = self::CACHE_KEY_PREFIX.hash('sha256', $jwksUri); + + if (null !== $this->cache) { + $cached = $this->cache->get($cacheKey); + if ($this->isJwksValid($cached)) { + /* @var array $cached */ + return $cached; + } + } + + $jwks = $this->fetchJwks($jwksUri); + + if (!$this->isJwksValid($jwks)) { + throw new RuntimeException(\sprintf('JWKS response from %s has invalid format: expected non-empty "keys" array.', $jwksUri)); + } + + if (null !== $this->cache) { + $this->cache->set($cacheKey, $jwks, $this->cacheTtl); + } + + return $jwks; + } + + /** + * @return array + */ + private function fetchJwks(string $jwksUri): array + { + $request = $this->requestFactory->createRequest('GET', $jwksUri) + ->withHeader('Accept', 'application/json'); + + try { + $response = $this->httpClient->sendRequest($request); + } catch (ClientExceptionInterface $e) { + throw new RuntimeException(\sprintf('Failed to fetch JWKS from %s: %s', $jwksUri, $e->getMessage()), 0, $e); + } + + if (200 !== $response->getStatusCode()) { + throw new RuntimeException(\sprintf('Failed to fetch JWKS from %s: HTTP %d', $jwksUri, $response->getStatusCode())); + } + + try { + $data = json_decode($response->getBody()->__toString(), true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new RuntimeException(\sprintf('Failed to decode JWKS: %s', $e->getMessage()), 0, $e); + } + + if (!\is_array($data)) { + throw new RuntimeException('Invalid JWKS format: expected JSON object.'); + } + + return $data; + } + + private function isJwksValid(mixed $jwks): bool + { + if (!\is_array($jwks) || !isset($jwks['keys']) || !\is_array($jwks['keys'])) { + return false; + } + + $nonEmptyKeys = array_filter($jwks['keys'], static fn (mixed $key): bool => \is_array($key) && [] !== $key); + + return [] !== $nonEmptyKeys; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProviderInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProviderInterface.php new file mode 100644 index 0000000..f33ac65 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwksProviderInterface.php @@ -0,0 +1,28 @@ + + */ +interface JwksProviderInterface +{ + /** + * @param string $issuer authorization server issuer URL + * @param string|null $jwksUri Optional explicit JWKS URI. If null, implementation may resolve via discovery. + * + * @return array + */ + public function getJwks(string $issuer, ?string $jwksUri = null): array; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwtTokenValidator.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwtTokenValidator.php new file mode 100644 index 0000000..a0ffe06 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/JwtTokenValidator.php @@ -0,0 +1,205 @@ + + */ +class JwtTokenValidator implements AuthorizationTokenValidatorInterface +{ + /** + * @param string|list $issuer Expected token issuer(s) (e.g., "https://auth.example.com/realms/mcp") + * @param string|list $audience Expected audience(s) for the token + * @param JwksProviderInterface $jwksProvider JWKS provider + * @param string|null $jwksUri Explicit JWKS URI (auto-discovered from first issuer if null) + * @param list $algorithms Allowed JWT algorithms (default: RS256, RS384, RS512) + * @param string $scopeClaim Claim name for scopes (default: "scope") + */ + public function __construct( + private readonly string|array $issuer, + private readonly string|array $audience, + private readonly JwksProviderInterface $jwksProvider, + private readonly ?string $jwksUri = null, + private readonly array $algorithms = ['RS256', 'RS384', 'RS512'], + private readonly string $scopeClaim = 'scope', + ) { + if (!class_exists(JWT::class)) { + throw new RuntimeException('For using the JwtTokenValidator, the firebase/php-jwt package is required. Try running "composer require firebase/php-jwt".'); + } + } + + public function validate(string $accessToken): AuthorizationResult + { + try { + /** @var array $claims */ + $claims = (array) JWT::decode($accessToken, $this->getJwks()); + + // Validate issuer + if (!$this->validateIssuer($claims)) { + return AuthorizationResult::unauthorized('invalid_token', 'Token issuer mismatch.'); + } + + // Validate audience + if (!$this->validateAudience($claims)) { + return AuthorizationResult::unauthorized('invalid_token', 'Token audience mismatch.'); + } + + // Build attributes to attach to request + $attributes = [ + 'oauth.claims' => $claims, + 'oauth.scopes' => $this->extractScopes($claims), + ]; + + // Add common claims as individual attributes + if (isset($claims['sub'])) { + $attributes['oauth.subject'] = $claims['sub']; + } + + if (isset($claims['client_id'])) { + $attributes['oauth.client_id'] = $claims['client_id']; + } + + // Add azp (authorized party) for OIDC tokens + if (isset($claims['azp'])) { + $attributes['oauth.authorized_party'] = $claims['azp']; + } + + return AuthorizationResult::allow($attributes); + } catch (ExpiredException) { + return AuthorizationResult::unauthorized('invalid_token', 'Token has expired.'); + } catch (SignatureInvalidException) { + return AuthorizationResult::unauthorized('invalid_token', 'Token signature verification failed.'); + } catch (BeforeValidException) { + return AuthorizationResult::unauthorized('invalid_token', 'Token is not yet valid.'); + } catch (\InvalidArgumentException|\UnexpectedValueException|\DomainException $e) { + return AuthorizationResult::unauthorized('invalid_token', 'Token validation failed: '.$e->getMessage()); + } + } + + /** + * Validates a token has the required scopes. + * + * Use this after validation to check specific scope requirements. + * + * @param AuthorizationResult $result The result from validate() + * @param list $requiredScopes Scopes required for this operation + * + * @return AuthorizationResult The original result if scopes are sufficient, forbidden otherwise + */ + public function requireScopes(AuthorizationResult $result, array $requiredScopes): AuthorizationResult + { + if (!$result->isAllowed()) { + return $result; + } + + $tokenScopes = $result->getAttributes()['oauth.scopes'] ?? []; + + if (!\is_array($tokenScopes)) { + $tokenScopes = []; + } + + foreach ($requiredScopes as $required) { + if (!\in_array($required, $tokenScopes, true)) { + return AuthorizationResult::forbidden('insufficient_scope', \sprintf('Required scope: %s', $required), $requiredScopes); + } + } + + return $result; + } + + /** + * @return array + */ + private function getJwks(): array + { + $issuer = \is_array($this->issuer) ? $this->issuer[0] : $this->issuer; + $jwksData = $this->jwksProvider->getJwks($issuer, $this->jwksUri); + + /* @var array */ + return JWK::parseKeySet($jwksData, $this->algorithms[0]); + } + + /** + * @param array $claims + */ + private function validateAudience(array $claims): bool + { + if (!isset($claims['aud'])) { + return false; + } + + $tokenAudiences = \is_array($claims['aud']) ? $claims['aud'] : [$claims['aud']]; + $expectedAudiences = \is_array($this->audience) ? $this->audience : [$this->audience]; + + foreach ($expectedAudiences as $expected) { + if (\in_array($expected, $tokenAudiences, true)) { + return true; + } + } + + return false; + } + + /** + * @param array $claims + */ + private function validateIssuer(array $claims): bool + { + if (!isset($claims['iss'])) { + return false; + } + + $expectedIssuers = \is_array($this->issuer) ? $this->issuer : [$this->issuer]; + + return \in_array($claims['iss'], $expectedIssuers, true); + } + + /** + * @param array $claims + * + * @return list + */ + private function extractScopes(array $claims): array + { + if (!isset($claims[$this->scopeClaim])) { + return []; + } + + $scopeValue = $claims[$this->scopeClaim]; + + if (\is_array($scopeValue)) { + return array_values(array_filter($scopeValue, 'is_string')); + } + + if (\is_string($scopeValue)) { + return array_values(array_filter(explode(' ', $scopeValue))); + } + + return []; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicy.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicy.php new file mode 100644 index 0000000..2a8c507 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/LenientOidcDiscoveryMetadataPolicy.php @@ -0,0 +1,54 @@ + + */ +final class LenientOidcDiscoveryMetadataPolicy implements OidcDiscoveryMetadataPolicyInterface +{ + public function isValid(mixed $metadata): bool + { + if (!\is_array($metadata) + || !isset($metadata['authorization_endpoint'], $metadata['token_endpoint'], $metadata['jwks_uri']) + || !\is_string($metadata['authorization_endpoint']) + || '' === trim($metadata['authorization_endpoint']) + || !\is_string($metadata['token_endpoint']) + || '' === trim($metadata['token_endpoint']) + || !\is_string($metadata['jwks_uri']) + || '' === trim($metadata['jwks_uri']) + ) { + return false; + } + + if (\array_key_exists('code_challenge_methods_supported', $metadata)) { + if (!\is_array($metadata['code_challenge_methods_supported']) || [] === $metadata['code_challenge_methods_supported']) { + return false; + } + + foreach ($metadata['code_challenge_methods_supported'] as $method) { + if (!\is_string($method) || '' === trim($method)) { + return false; + } + } + } + + return true; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscovery.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscovery.php new file mode 100644 index 0000000..2f24685 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscovery.php @@ -0,0 +1,236 @@ + + */ +class OidcDiscovery implements OidcDiscoveryInterface +{ + private const CACHE_KEY_PREFIX = 'mcp_oidc_discovery_'; + + private ClientInterface $httpClient; + private RequestFactoryInterface $requestFactory; + private OidcDiscoveryMetadataPolicyInterface $metadataPolicy; + + /** + * @param ClientInterface|null $httpClient PSR-18 HTTP client (auto-discovered if null) + * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory (auto-discovered if null) + * @param CacheInterface|null $cache PSR-16 cache for metadata (optional) + * @param int $cacheTtl Cache TTL in seconds (default: 1 hour) + * @param OidcDiscoveryMetadataPolicyInterface|null $metadataPolicy Metadata validation policy + */ + public function __construct( + ?ClientInterface $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + private readonly ?CacheInterface $cache = null, + private readonly int $cacheTtl = 3600, + ?OidcDiscoveryMetadataPolicyInterface $metadataPolicy = null, + ) { + $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); + $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + $this->metadataPolicy = $metadataPolicy ?? new StrictOidcDiscoveryMetadataPolicy(); + } + + /** + * Gets the JWKS URI from the authorization server metadata. + * + * @param string $issuer The issuer URL + * + * @return string The JWKS URI + * + * @throws RuntimeException If discover fails + */ + public function getJwksUri(string $issuer): string + { + $metadata = $this->discover($issuer); + + return $metadata['jwks_uri']; + } + + /** + * Gets the token endpoint from the authorization server metadata. + * + * @param string $issuer The issuer URL + * + * @return string The token endpoint URL + * + * @throws RuntimeException If discover fails + */ + public function getTokenEndpoint(string $issuer): string + { + $metadata = $this->discover($issuer); + + return $metadata['token_endpoint']; + } + + /** + * Gets the authorization endpoint from the authorization server metadata. + * + * @param string $issuer The issuer URL + * + * @return string The authorization endpoint URL + * + * @throws RuntimeException If discover fails + */ + public function getAuthorizationEndpoint(string $issuer): string + { + $metadata = $this->discover($issuer); + + return $metadata['authorization_endpoint']; + } + + /** + * Discovers authorization server metadata from the issuer URL. + * + * Tries endpoints in priority order per RFC 8414 and OpenID Connect Discovery: + * 1. OAuth 2.0 path insertion: /.well-known/oauth-authorization-server/{path} + * 2. OIDC path insertion: /.well-known/openid-configuration/{path} + * 3. OIDC path appending: {path}/.well-known/openid-configuration + * + * @param string $issuer The issuer URL (e.g., "https://auth.example.com/realms/mcp") + * + * @return array The authorization server metadata + * + * @throws RuntimeException If discovery fails + */ + public function discover(string $issuer): array + { + $cacheKey = self::CACHE_KEY_PREFIX.hash('sha256', $issuer); + + if (null !== $this->cache) { + $cached = $this->cache->get($cacheKey); + if (\is_array($cached)) { + /* @var array $cached */ + return $cached; + } + } + + $metadata = $this->fetchMetadata($issuer); + + if (null !== $this->cache) { + $this->cache->set($cacheKey, $metadata, $this->cacheTtl); + } + + return $metadata; + } + + /** + * @return array + */ + private function fetchMetadata(string $issuer): array + { + $issuer = rtrim($issuer, '/'); + $parsed = parse_url($issuer); + + if (false === $parsed || !isset($parsed['scheme'], $parsed['host'])) { + throw new RuntimeException(\sprintf('Invalid issuer URL: %s', $issuer)); + } + + $scheme = $parsed['scheme']; + $host = $parsed['host']; + $port = isset($parsed['port']) ? ':'.$parsed['port'] : ''; + $path = $parsed['path'] ?? ''; + + $baseUrl = $scheme.'://'.$host.$port; + + // Build discovery URLs in priority order per RFC 8414 Section 3.1 + $discoveryUrls = []; + + if ('' !== $path && '/' !== $path) { + // For issuer URLs with path components + // 1. OAuth 2.0 path insertion + $discoveryUrls[] = $baseUrl.'/.well-known/oauth-authorization-server'.$path; + // 2. OIDC path insertion + $discoveryUrls[] = $baseUrl.'/.well-known/openid-configuration'.$path; + // 3. OIDC path appending + $discoveryUrls[] = $issuer.'/.well-known/openid-configuration'; + } else { + // For issuer URLs without path components + $discoveryUrls[] = $baseUrl.'/.well-known/oauth-authorization-server'; + $discoveryUrls[] = $baseUrl.'/.well-known/openid-configuration'; + } + + $lastException = null; + + foreach ($discoveryUrls as $url) { + try { + $metadata = $this->fetchJson($url); + if (!$this->metadataPolicy->isValid($metadata)) { + throw new RuntimeException(\sprintf('OIDC discovery response from %s has invalid format.', $url)); + } + + if (!isset($metadata['issuer']) || !\is_string($metadata['issuer'])) { + throw new RuntimeException(\sprintf('OIDC discovery response from %s is missing required "issuer" field.', $url)); + } + if ($metadata['issuer'] !== $issuer) { + throw new RuntimeException(\sprintf('OIDC discovery issuer mismatch for %s: expected %s, got %s.', $url, $issuer, $metadata['issuer'])); + } + + return $metadata; + } catch (RuntimeException $e) { + $lastException = $e; + continue; + } + } + + throw new RuntimeException(\sprintf('Failed to discover authorization server metadata for issuer: %s', $issuer), 0, $lastException); + } + + /** + * @return array + */ + private function fetchJson(string $url): array + { + $request = $this->requestFactory->createRequest('GET', $url) + ->withHeader('Accept', 'application/json'); + + try { + $response = $this->httpClient->sendRequest($request); + } catch (ClientExceptionInterface $e) { + throw new RuntimeException(\sprintf('HTTP request to %s failed: %s', $url, $e->getMessage()), 0, $e); + } + + if (200 !== $response->getStatusCode()) { + throw new RuntimeException(\sprintf('HTTP request to %s failed with status %d', $url, $response->getStatusCode())); + } + + try { + $data = json_decode($response->getBody()->__toString(), true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new RuntimeException(\sprintf('Failed to decode JSON from %s: %s', $url, $e->getMessage()), 0, $e); + } + + if (!\is_array($data)) { + throw new RuntimeException(\sprintf('Expected JSON object from %s, got %s', $url, \gettype($data))); + } + + return $data; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryInterface.php new file mode 100644 index 0000000..aeabf0b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryInterface.php @@ -0,0 +1,31 @@ + + */ +interface OidcDiscoveryInterface +{ + /** + * @return array + */ + public function discover(string $issuer): array; + + public function getAuthorizationEndpoint(string $issuer): string; + + public function getTokenEndpoint(string $issuer): string; + + public function getJwksUri(string $issuer): string; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryMetadataPolicyInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryMetadataPolicyInterface.php new file mode 100644 index 0000000..edf94c4 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/OidcDiscoveryMetadataPolicyInterface.php @@ -0,0 +1,22 @@ + + */ +interface OidcDiscoveryMetadataPolicyInterface +{ + public function isValid(mixed $metadata): bool; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadata.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadata.php new file mode 100644 index 0000000..dd96982 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadata.php @@ -0,0 +1,245 @@ + + */ +final class ProtectedResourceMetadata implements \JsonSerializable +{ + public const DEFAULT_METADATA_PATH = '/.well-known/oauth-protected-resource'; + + private const LOCALIZED_HUMAN_READABLE_FIELD_PATTERN = '/^(resource_name|resource_documentation|resource_policy_uri|resource_tos_uri)#[A-Za-z0-9-]+$/'; + + /** @var list */ + private array $authorizationServers; + + /** @var list|null */ + private ?array $scopesSupported; + + /** @var list */ + private array $metadataPaths; + + /** @var array */ + private array $localizedHumanReadable; + + /** @var array */ + private array $extra; + + private ?string $resource; + private ?string $resourceName; + private ?string $resourceDocumentation; + private ?string $resourcePolicyUri; + private ?string $resourceTosUri; + + /** + * @param list $authorizationServers + * @param list|null $scopesSupported + * @param array $localizedHumanReadable Locale-specific values, e.g. resource_name#en => "My Resource" + * @param array $extra Additional RFC 9728 metadata fields + * @param list $metadataPaths + */ + public function __construct( + array $authorizationServers, + ?array $scopesSupported = null, + ?string $resource = null, + ?string $resourceName = null, + ?string $resourceDocumentation = null, + ?string $resourcePolicyUri = null, + ?string $resourceTosUri = null, + array $localizedHumanReadable = [], + array $extra = [], + array $metadataPaths = [self::DEFAULT_METADATA_PATH], + ) { + $this->authorizationServers = $this->normalizeStringList($authorizationServers, 'authorizationServers'); + if ([] === $this->authorizationServers) { + throw new InvalidArgumentException('Protected resource metadata requires at least one authorization server.'); + } + + $normalizedScopes = $this->normalizeStringList($scopesSupported ?? [], 'scopesSupported'); + $this->scopesSupported = [] === $normalizedScopes ? null : $normalizedScopes; + + $this->resource = $this->normalizeNullableString($resource); + $this->resourceName = $this->normalizeNullableString($resourceName); + $this->resourceDocumentation = $this->normalizeNullableString($resourceDocumentation); + $this->resourcePolicyUri = $this->normalizeNullableString($resourcePolicyUri); + $this->resourceTosUri = $this->normalizeNullableString($resourceTosUri); + $this->localizedHumanReadable = $this->normalizeLocalizedHumanReadable($localizedHumanReadable); + $this->extra = $extra; + + $this->metadataPaths = $this->normalizePaths($metadataPaths); + if ([] === $this->metadataPaths) { + throw new InvalidArgumentException('Protected resource metadata requires at least one metadata path.'); + } + } + + /** + * @return list + */ + public function getMetadataPaths(): array + { + return $this->metadataPaths; + } + + public function getPrimaryMetadataPath(): string + { + return $this->metadataPaths[0]; + } + + /** + * @return list|null + */ + public function getScopesSupported(): ?array + { + return $this->scopesSupported; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'authorization_servers' => $this->authorizationServers, + ]; + + if (null !== $this->scopesSupported) { + $data['scopes_supported'] = $this->scopesSupported; + } + + if (null !== $this->resource) { + $data['resource'] = $this->resource; + } + + if (null !== $this->resourceName) { + $data['resource_name'] = $this->resourceName; + } + + if (null !== $this->resourceDocumentation) { + $data['resource_documentation'] = $this->resourceDocumentation; + } + + if (null !== $this->resourcePolicyUri) { + $data['resource_policy_uri'] = $this->resourcePolicyUri; + } + + if (null !== $this->resourceTosUri) { + $data['resource_tos_uri'] = $this->resourceTosUri; + } + + foreach ($this->localizedHumanReadable as $key => $value) { + $data[$key] = $value; + } + + return array_merge($this->extra, $data); + } + + /** + * @param list $values + * + * @return list + */ + private function normalizeStringList(array $values, string $parameterName): array + { + $normalized = []; + + foreach ($values as $value) { + if (!\is_string($value)) { + throw new InvalidArgumentException(\sprintf('Protected resource metadata parameter "%s" must contain strings.', $parameterName)); + } + + $value = trim($value); + if ('' === $value) { + continue; + } + + $normalized[] = $value; + } + + return array_values(array_unique($normalized)); + } + + private function normalizeNullableString(?string $value): ?string + { + if (null === $value) { + return null; + } + + $value = trim($value); + + return '' === $value ? null : $value; + } + + /** + * @param list $paths + * + * @return list + */ + private function normalizePaths(array $paths): array + { + $normalized = []; + + foreach ($paths as $path) { + if (!\is_string($path)) { + throw new InvalidArgumentException('Protected resource metadata paths must be strings.'); + } + + $path = trim($path); + if ('' === $path) { + continue; + } + + if ('/' !== $path[0]) { + $path = '/'.$path; + } + + $normalized[] = $path; + } + + return array_values(array_unique($normalized)); + } + + /** + * @param array $localizedHumanReadable + * + * @return array + */ + private function normalizeLocalizedHumanReadable(array $localizedHumanReadable): array + { + $normalized = []; + + foreach ($localizedHumanReadable as $field => $value) { + if (!\is_string($field) || !preg_match(self::LOCALIZED_HUMAN_READABLE_FIELD_PATTERN, $field)) { + throw new InvalidArgumentException(\sprintf('Invalid localized human-readable field: "%s".', (string) $field)); + } + + if (!\is_string($value)) { + throw new InvalidArgumentException(\sprintf('Localized human-readable value for "%s" must be a string.', $field)); + } + + $value = trim($value); + if ('' === $value) { + continue; + } + + $normalized[$field] = $value; + } + + return $normalized; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadataHandler.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadataHandler.php new file mode 100644 index 0000000..1d0d2a1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/ProtectedResourceMetadataHandler.php @@ -0,0 +1,60 @@ + + */ +final class ProtectedResourceMetadataHandler implements RequestHandlerInterface +{ + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + + public function __construct( + private readonly ProtectedResourceMetadata $metadata, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ) { + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse(200) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream(json_encode($this->metadata, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES))); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicy.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicy.php new file mode 100644 index 0000000..b89a3f8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/OAuth/StrictOidcDiscoveryMetadataPolicy.php @@ -0,0 +1,48 @@ + + */ +final class StrictOidcDiscoveryMetadataPolicy implements OidcDiscoveryMetadataPolicyInterface +{ + public function isValid(mixed $metadata): bool + { + if (!\is_array($metadata) + || !isset($metadata['authorization_endpoint'], $metadata['token_endpoint'], $metadata['jwks_uri']) + || !\is_string($metadata['authorization_endpoint']) + || '' === trim($metadata['authorization_endpoint']) + || !\is_string($metadata['token_endpoint']) + || '' === trim($metadata['token_endpoint']) + || !\is_string($metadata['jwks_uri']) + || '' === trim($metadata['jwks_uri']) + || !isset($metadata['code_challenge_methods_supported']) + ) { + return false; + } + + if (!\is_array($metadata['code_challenge_methods_supported']) || [] === $metadata['code_challenge_methods_supported']) { + return false; + } + + foreach ($metadata['code_challenge_methods_supported'] as $method) { + if (!\is_string($method) || '' === trim($method)) { + return false; + } + } + + return true; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Http/StatelessResponder.php b/admin/vendor/mcp/sdk/src/Server/Transport/Http/StatelessResponder.php new file mode 100644 index 0000000..f752fb8 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Http/StatelessResponder.php @@ -0,0 +1,99 @@ + + */ +final class StatelessResponder +{ + public function __construct( + private readonly ResponseFactoryInterface $responseFactory, + private readonly StreamFactoryInterface $streamFactory, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function respond(StatelessResult $result): ResponseInterface + { + if ($result->isStream()) { + \assert(null !== $result->frames); + + return $this->sse($result->frames); + } + + if ($result->isEmpty()) { + return $this->responseFactory->createResponse($result->httpStatus); + } + + return $this->json($result->toJson(), $result->httpStatus); + } + + public function error(Error $error, int $httpStatus): ResponseInterface + { + return $this->json(json_encode($error, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES), $httpStatus); + } + + public function json(string $payload, int $status): ResponseInterface + { + return $this->responseFactory->createResponse($status) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream($payload)); + } + + /** + * @param \Closure(): \Generator $frames + */ + private function sse(\Closure $frames): ResponseInterface + { + $logger = $this->logger; + + $callback = static function () use ($frames, $logger): void { + try { + foreach ($frames() as $frame) { + // A null frame is a keep-alive tick: an SSE comment the + // client ignores, and the write PHP needs to spot a drop. + echo null === $frame + ? ": keep-alive\n\n" + : 'data: '.json_encode($frame, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES)."\n\n"; + flush(); + } + } catch (\Throwable $e) { + // Headers are long sent, so this cannot become an error + // response; the client sees a close without the closure frame. + $logger->error('Subscription stream ended with an error.', ['exception' => $e]); + } + }; + + return $this->responseFactory->createResponse(200) + ->withHeader('Content-Type', 'text/event-stream') + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Connection', 'keep-alive') + ->withHeader('X-Accel-Buffering', 'no') + ->withBody(new CallbackStream($callback, $this->logger)); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/InMemoryTransport.php b/admin/vendor/mcp/sdk/src/Server/Transport/InMemoryTransport.php new file mode 100644 index 0000000..ddc6ca1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/InMemoryTransport.php @@ -0,0 +1,75 @@ + + * + * @author Tobias Nyholm + */ +class InMemoryTransport extends BaseTransport +{ + /** + * @param list $messages + */ + public function __construct( + private readonly array $messages = [], + ?LoggerInterface $logger = null, + ) { + parent::__construct($logger); + } + + public function onMessage(callable $listener): void + { + $this->messageListener = $listener; + } + + public function send(string $data, array $context): void + { + if (isset($context['session_id'])) { + $this->sessionId = $context['session_id']; + } + } + + /** + * @return null + */ + public function listen(): mixed + { + $this->logger->info('InMemoryTransport is processing messages...'); + + foreach ($this->messages as $message) { + $this->handleMessage($message, $this->sessionId); + } + + $this->logger->info('InMemoryTransport finished processing.'); + $this->handleSessionEnd($this->sessionId); + + $this->sessionId = null; + + return null; + } + + public function setSessionId(?Uuid $sessionId): void + { + $this->sessionId = $sessionId; + } + + public function close(): void + { + $this->handleSessionEnd($this->sessionId); + $this->sessionId = null; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/ManagesTransportCallbacks.php b/admin/vendor/mcp/sdk/src/Server/Transport/ManagesTransportCallbacks.php new file mode 100644 index 0000000..072d3f0 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/ManagesTransportCallbacks.php @@ -0,0 +1,82 @@ + + * */ +trait ManagesTransportCallbacks +{ + /** @var callable(TransportInterface, string, ?Uuid): void */ + protected $messageListener; + + /** @var callable(Uuid): void */ + protected $sessionEndListener; + + /** @var callable(Uuid): array}> */ + protected $outgoingMessagesProvider; + + /** @var callable(Uuid): array> */ + protected $pendingRequestsProvider; + + /** @var callable(int, Uuid): Response>|Error|null */ + protected $responseFinder; + + /** @var callable(FiberSuspend|null, ?Uuid): void */ + protected $fiberYieldHandler; + + public function onMessage(callable $listener): void + { + $this->messageListener = $listener; + } + + public function onSessionEnd(callable $listener): void + { + $this->sessionEndListener = $listener; + } + + public function setOutgoingMessagesProvider(callable $provider): void + { + $this->outgoingMessagesProvider = $provider; + } + + public function setPendingRequestsProvider(callable $provider): void + { + $this->pendingRequestsProvider = $provider; + } + + /** + * @param callable(int, Uuid):(Response>|Error|null) $finder + */ + public function setResponseFinder(callable $finder): void + { + $this->responseFinder = $finder; + } + + /** + * @param callable(FiberSuspend|null, ?Uuid): void $handler + */ + public function setFiberYieldHandler(callable $handler): void + { + $this->fiberYieldHandler = $handler; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/ReadsBoundedBody.php b/admin/vendor/mcp/sdk/src/Server/Transport/ReadsBoundedBody.php new file mode 100644 index 0000000..00f47b9 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/ReadsBoundedBody.php @@ -0,0 +1,61 @@ + + */ +trait ReadsBoundedBody +{ + /** + * Returns the body contents, or `null` when the payload exceeds $maxBytes. + * + * A stream advertising its size is rejected up front; otherwise the read is + * incremental and stops at the cap, so an unbounded stream cannot exhaust + * memory. + */ + private function readBoundedBody(StreamInterface $body, int $maxBytes): ?string + { + if ($body->isSeekable()) { + $body->rewind(); + } + + $size = $body->getSize(); + if (null !== $size && $size > $maxBytes) { + return null; + } + + $contents = ''; + while (!$body->eof()) { + $chunk = $body->read(8192); + if ('' === $chunk) { + break; + } + + $contents .= $chunk; + if (\strlen($contents) > $maxBytes) { + return null; + } + } + + return $contents; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/StatelessAwareTransportInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/StatelessAwareTransportInterface.php new file mode 100644 index 0000000..52815b3 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/StatelessAwareTransportInterface.php @@ -0,0 +1,29 @@ + + */ +interface StatelessAwareTransportInterface +{ + public function connectStateless(StatelessProtocol $protocol): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/StatelessHttpTransport.php b/admin/vendor/mcp/sdk/src/Server/Transport/StatelessHttpTransport.php new file mode 100644 index 0000000..c55caed --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/StatelessHttpTransport.php @@ -0,0 +1,136 @@ + + */ +final class StatelessHttpTransport +{ + use ReadsBoundedBody; + + /** + * Upper bound on the request body read for a POST, guarding against memory + * exhaustion from an oversized (or unbounded chunked) payload. + */ + public const DEFAULT_MAX_BODY_BYTES = 4 * 1024 * 1024; + + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + private StatelessResponder $responder; + + /** @var list */ + private array $middleware; + + /** + * @param iterable|null $middleware `null` installs {@see self::defaultMiddleware()}; `[]` disables all middleware + */ + public function __construct( + private readonly StatelessProtocol $protocol, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + private readonly LoggerInterface $logger = new NullLogger(), + private readonly int $maxBodyBytes = self::DEFAULT_MAX_BODY_BYTES, + ?iterable $middleware = null, + ) { + $this->middleware = null === $middleware + ? self::defaultMiddleware() + : array_values([...$middleware]); + + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + $this->responder = new StatelessResponder($this->responseFactory, $this->streamFactory, $this->logger); + } + + /** + * Browser-facing protections, era-independent. The protocol-version + * middleware is absent: here the version travels in `_meta`, so a + * header-only check would judge half the story. + * + * @return list + */ + public static function defaultMiddleware(): array + { + return [ + new CorsMiddleware(), + new DnsRebindingProtectionMiddleware(), + ]; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $handler = new MiddlewareRequestHandler( + $this->middleware, + \Closure::fromCallable([$this, 'dispatch']), + ); + + return $handler->handle($request); + } + + private function dispatch(ServerRequestInterface $request): ResponseInterface + { + if ('OPTIONS' === $request->getMethod()) { + return $this->responseFactory->createResponse(204); + } + + // No GET stream and no DELETE teardown: there is no session to address. + if ('POST' !== $request->getMethod()) { + return $this->json( + json_encode(Error::forInvalidRequest(\sprintf('The modern lifecycle accepts POST only, got %s.', $request->getMethod())), \JSON_THROW_ON_ERROR), + 405, + ); + } + + $payload = $this->readBoundedBody($request->getBody(), $this->maxBodyBytes); + + if (null === $payload) { + $this->logger->warning('Rejected POST body exceeding the maximum allowed size.', ['limit' => $this->maxBodyBytes]); + + return $this->json( + json_encode(Error::forInvalidRequest(\sprintf('Request body exceeds the maximum allowed size of %d bytes.', $this->maxBodyBytes)), \JSON_THROW_ON_ERROR), + 413, + ); + } + + $headers = []; + foreach ($request->getHeaders() as $name => $values) { + $headers[$name] = implode(', ', $values); + } + + return $this->responder->respond($this->protocol->handle($payload, $headers)); + } + + private function json(string $payload, int $status): ResponseInterface + { + return $this->responder->json($payload, $status); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControl.php b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControl.php new file mode 100644 index 0000000..2b3c941 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControl.php @@ -0,0 +1,28 @@ + + */ +class RunnerControl implements RunnerControlInterface +{ + public static RunnerState $state = RunnerState::RUNNING; + + public function getState(): RunnerState + { + return self::$state; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControlInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControlInterface.php new file mode 100644 index 0000000..d8de01f --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerControlInterface.php @@ -0,0 +1,22 @@ + + */ +interface RunnerControlInterface +{ + public function getState(): RunnerState; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerState.php b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerState.php new file mode 100644 index 0000000..c35e5e1 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/Stdio/RunnerState.php @@ -0,0 +1,24 @@ + + */ +enum RunnerState +{ + case RUNNING; + case STOP_AND_END_SESSION; + case STOP; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/StdioTransport.php b/admin/vendor/mcp/sdk/src/Server/Transport/StdioTransport.php new file mode 100644 index 0000000..565f7da --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/StdioTransport.php @@ -0,0 +1,214 @@ + + * + * @author Kyrian Obikwelu + */ +class StdioTransport extends BaseTransport +{ + /** + * Default cap on the bytes read for a single input line. + */ + public const DEFAULT_MAX_LINE_BYTES = 4 * 1024 * 1024; + + /** Whether the current over-length line is still being drained and discarded. */ + private bool $discardingLine = false; + + /** + * @param resource $input + * @param resource $output + * @param int $maxLineBytes Maximum bytes read for a single input line. fgets() with no length reads until a + * newline or EOF, so a peer that never sends a newline would buffer the whole stream + * into one allocation and exhaust memory; a line exceeding this cap is discarded + * instead. + */ + public function __construct( + private $input = \STDIN, + private $output = \STDOUT, + ?LoggerInterface $logger = null, + private readonly RunnerControlInterface $runnerControl = new RunnerControl(), + private readonly int $maxLineBytes = self::DEFAULT_MAX_LINE_BYTES, + ) { + parent::__construct($logger); + + if ($maxLineBytes < 1) { + throw new InvalidArgumentException(\sprintf('The maximum line size must be a positive number of bytes, got %d.', $maxLineBytes)); + } + } + + public function send(string $data, array $context): void + { + if (isset($context['session_id'])) { + $this->sessionId = $context['session_id']; + } + + $this->writeLine($data); + } + + public function listen(): int + { + $this->logger->info('StdioTransport is listening for messages on STDIN...'); + stream_set_blocking($this->input, false); + + while (!feof($this->input) && RunnerState::RUNNING === $this->runnerControl->getState()) { + $this->processInput(); + $this->processFiber(); + $this->flushOutgoingMessages(); + } + + $this->logger->info('StdioTransport finished listening.'); + if (\in_array($this->runnerControl->getState(), [RunnerState::RUNNING, RunnerState::STOP_AND_END_SESSION], true)) { + $this->logger->info('StdioTransport end session.'); + $this->handleSessionEnd($this->sessionId); + } + + return 0; + } + + protected function processInput(): void + { + $line = fgets($this->input, $this->maxLineBytes); + if (false === $line) { + usleep(50000); // 50ms + + return; + } + + $lineComplete = str_ends_with($line, "\n"); + + // A previous over-length line is still being drained: keep discarding + // one bounded chunk per tick until its terminating newline is reached, + // so the run loop stays responsive instead of blocking on a drain loop. + if ($this->discardingLine) { + $this->discardingLine = !$lineComplete; + + return; + } + + // fgets() reads at most maxLineBytes - 1 bytes; a full read with no + // trailing newline means the line exceeds the cap. Discard it rather + // than buffering it, and keep discarding the remainder on later ticks. + if (!$lineComplete && \strlen($line) >= $this->maxLineBytes - 1) { + $this->discardingLine = true; + $this->logger->warning('StdioTransport discarded an input line exceeding the maximum length.', [ + 'max_line_bytes' => $this->maxLineBytes, + ]); + + return; + } + + $trimmedLine = trim($line); + if (!empty($trimmedLine)) { + $this->handleMessage($trimmedLine, $this->sessionId); + } + } + + private function processFiber(): void + { + if (null === $this->sessionFiber) { + return; + } + + if ($this->sessionFiber->isTerminated()) { + $this->handleFiberTermination(); + + return; + } + + if (!$this->sessionFiber->isSuspended()) { + return; + } + + $pendingRequests = $this->getPendingRequests($this->sessionId); + + if (empty($pendingRequests)) { + $yielded = $this->sessionFiber->resume(); + $this->handleFiberYield($yielded, $this->sessionId); + + return; + } + + foreach ($pendingRequests as $pending) { + $requestId = $pending['request_id']; + $timestamp = $pending['timestamp']; + $timeout = $pending['timeout'] ?? 120; + + $response = $this->checkForResponse($requestId, $this->sessionId); + + if (null !== $response) { + $yielded = $this->sessionFiber->resume($response); + $this->handleFiberYield($yielded, $this->sessionId); + + return; + } + + if (time() - $timestamp >= $timeout) { + $error = Error::forInternalError('Request timed out', $requestId); + $yielded = $this->sessionFiber->resume($error); + $this->handleFiberYield($yielded, $this->sessionId); + + return; + } + } + } + + private function handleFiberTermination(): void + { + $finalResult = $this->sessionFiber->getReturn(); + + if (null !== $finalResult) { + try { + $encoded = json_encode($finalResult, \JSON_THROW_ON_ERROR); + $this->writeLine($encoded); + } catch (\JsonException $e) { + $this->logger->error('STDIO: Failed to encode final Fiber result.', ['exception' => $e]); + } + } + + $this->sessionFiber = null; + } + + private function flushOutgoingMessages(): void + { + $messages = $this->getOutgoingMessages($this->sessionId); + + foreach ($messages as $message) { + $this->writeLine($message['message']); + } + } + + private function writeLine(string $payload): void + { + fwrite($this->output, $payload.\PHP_EOL); + } + + public function close(): void + { + $this->handleSessionEnd($this->sessionId); + if (\is_resource($this->input)) { + fclose($this->input); + } + if (\is_resource($this->output)) { + fclose($this->output); + } + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php b/admin/vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php new file mode 100644 index 0000000..2bf088b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php @@ -0,0 +1,471 @@ + + * + * @author Kyrian Obikwelu + */ +class StreamableHttpTransport extends BaseTransport implements StatelessAwareTransportInterface +{ + use ReadsBoundedBody; + + public const SESSION_HEADER = 'Mcp-Session-Id'; + public const PROTOCOL_VERSION_HEADER = 'Mcp-Protocol-Version'; + + /** + * Upper bound on the request body read for a POST, guarding against memory + * exhaustion from an oversized (or unbounded chunked) payload. + */ + public const DEFAULT_MAX_BODY_BYTES = 4 * 1024 * 1024; + + private ResponseFactoryInterface $responseFactory; + private StreamFactoryInterface $streamFactory; + private StatelessResponder $responder; + private InboundClassifier $classifier; + + private ?StatelessProtocol $stateless = null; + + private ?string $immediateResponse = null; + private ?int $immediateStatusCode = null; + + /** @var list|null null until {@see self::listen()} resolves the defaults */ + private ?array $middleware; + + /** + * @param iterable|null $middleware `null` installs {@see self::defaultMiddleware()}; `[]` disables all middleware + */ + public function __construct( + private ServerRequestInterface $request, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?LoggerInterface $logger = null, + ?iterable $middleware = null, + private readonly int $maxBodyBytes = self::DEFAULT_MAX_BODY_BYTES, + ) { + parent::__construct($logger); + + if ($this->maxBodyBytes < 1) { + throw new InvalidArgumentException('maxBodyBytes must be at least 1.'); + } + + $this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); + $this->responder = new StatelessResponder($this->responseFactory, $this->streamFactory, $this->logger); + $this->classifier = new InboundClassifier(); + + if (null === $middleware) { + // Left unresolved: the default stack's version middleware has to + // know which revisions this endpoint serves, and the modern + // dispatcher arrives after the constructor. + $this->middleware = null; + } else { + $this->middleware = self::normalizeMiddleware($middleware); + if ([] === $this->middleware) { + $this->logger->warning('Streamable HTTP transport started with an empty middleware list. Default security protections (CORS, DNS rebinding, protocol version validation) are disabled. Pass null (or omit the argument) to use the secure defaults, or include them via [...StreamableHttpTransport::defaultMiddleware(), $yourMiddleware].'); + } + + // Custom middleware runs before the request's era is classified, so a + // ProtocolVersionMiddleware here rejects every modern-era request by + // default — it only accepts the handshake versions it was built for. + foreach ($this->middleware as $entry) { + if ($entry instanceof ProtocolVersionMiddleware) { + $this->logger->warning('A custom middleware list includes ProtocolVersionMiddleware. It runs before the modern (2026-07-28) era is classified and rejects that era\'s requests by default, since it only recognises handshake revisions. Remove it from the custom list — the transport already applies it to handshake-era traffic on its own via self::handshakeMiddleware().'); + + break; + } + } + } + } + + /** + * Secure default middleware stack applied when no `$middleware` is provided to the constructor. + * + * These run at the edge, before the request's era is known, because what + * they enforce — origin policy, DNS rebinding — is true of both eras. The + * `MCP-Protocol-Version` header rule is not: it belongs to the handshake + * era, so {@see self::handshakeMiddleware()} carries it instead and the + * modern leg answers for its own revisions. + * + * @return list + */ + public static function defaultMiddleware(): array + { + return [ + new CorsMiddleware(), + new DnsRebindingProtectionMiddleware(), + ]; + } + + /** + * Middleware applied only to requests classified as handshake-era traffic. + * + * @return list + */ + public static function handshakeMiddleware(): array + { + return [ + new ProtocolVersionMiddleware(), + ]; + } + + public function connectStateless(StatelessProtocol $protocol): void + { + $this->stateless = $protocol; + } + + public function send(string $data, array $context): void + { + $this->immediateResponse = $data; + $this->immediateStatusCode = $context['status_code'] ?? 200; + } + + public function listen(): ResponseInterface + { + $handler = new MiddlewareRequestHandler( + $this->middleware ??= self::defaultMiddleware(), + \Closure::fromCallable([$this, 'handleRequest']), + ); + + return $handler->handle($this->request); + } + + protected function handleOptionsRequest(): ResponseInterface + { + return $this->responseFactory->createResponse(204); + } + + /** + * @param string $body the request body, already read and bounded by {@see self::handleRequest()} + */ + protected function handlePostRequest(string $body): ResponseInterface + { + $this->handleMessage($body, $this->sessionId); + + if (null !== $this->immediateResponse) { + $response = $this->responseFactory->createResponse($this->immediateStatusCode ?? 200) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream($this->immediateResponse)); + + return $response; + } + + if (null !== $this->sessionFiber) { + $this->logger->info('Fiber suspended, handling via SSE.'); + + return $this->createStreamedResponse(); + } + + return $this->createJsonResponse(); + } + + protected function handleDeleteRequest(): ResponseInterface + { + if (!$this->sessionId) { + return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header is required.'), 400); + } + + $this->handleSessionEnd($this->sessionId); + + return $this->responseFactory->createResponse(200); + } + + protected function createJsonResponse(): ResponseInterface + { + $outgoingMessages = $this->getOutgoingMessages($this->sessionId); + + if (empty($outgoingMessages)) { + return $this->responseFactory->createResponse(202) + ->withHeader('Content-Type', 'application/json'); + } + + $messages = array_column($outgoingMessages, 'message'); + $responseBody = 1 === \count($messages) ? $messages[0] : '['.implode(',', $messages).']'; + + $response = $this->responseFactory->createResponse(200) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream($responseBody)); + + if ($this->sessionId) { + $response = $response->withHeader(self::SESSION_HEADER, $this->sessionId->toRfc4122()); + } + + return $response; + } + + protected function createStreamedResponse(): ResponseInterface + { + $callback = function (): void { + try { + $this->logger->info('SSE: Starting request processing loop'); + + while ($this->sessionFiber->isSuspended()) { + $this->flushOutgoingMessages($this->sessionId); + + $pendingRequests = $this->getPendingRequests($this->sessionId); + + if (empty($pendingRequests)) { + $yielded = $this->sessionFiber->resume(); + $this->handleFiberYield($yielded, $this->sessionId); + continue; + } + + $resumed = false; + foreach ($pendingRequests as $pending) { + $requestId = $pending['request_id']; + $timestamp = $pending['timestamp']; + $timeout = $pending['timeout'] ?? 120; + + $response = $this->checkForResponse($requestId, $this->sessionId); + + if (null !== $response) { + $yielded = $this->sessionFiber->resume($response); + $this->handleFiberYield($yielded, $this->sessionId); + $resumed = true; + break; + } + + if (time() - $timestamp >= $timeout) { + $error = Error::forInternalError('Request timed out', $requestId); + $yielded = $this->sessionFiber->resume($error); + $this->handleFiberYield($yielded, $this->sessionId); + $resumed = true; + break; + } + } + + if (!$resumed) { + usleep(100000); + } // Prevent tight loop + } + + $this->handleFiberTermination(); + } finally { + $this->sessionFiber = null; + } + }; + + $stream = new CallbackStream($callback, $this->logger); + $response = $this->responseFactory->createResponse(200) + ->withHeader('Content-Type', 'text/event-stream') + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Connection', 'keep-alive') + ->withHeader('X-Accel-Buffering', 'no') + ->withBody($stream); + + if ($this->sessionId) { + $response = $response->withHeader(self::SESSION_HEADER, $this->sessionId->toRfc4122()); + } + + return $response; + } + + protected function handleFiberTermination(): void + { + $finalResult = $this->sessionFiber->getReturn(); + + if (null !== $finalResult) { + try { + $encoded = json_encode($finalResult, \JSON_THROW_ON_ERROR); + echo "event: message\n"; + echo "data: {$encoded}\n\n"; + @ob_flush(); + flush(); + } catch (\JsonException $e) { + $this->logger->error('SSE: Failed to encode final Fiber result.', ['exception' => $e]); + } + } + + $this->sessionFiber = null; + } + + protected function flushOutgoingMessages(?Uuid $sessionId): void + { + $messages = $this->getOutgoingMessages($sessionId); + + foreach ($messages as $message) { + echo "event: message\n"; + echo "data: {$message['message']}\n\n"; + @ob_flush(); + flush(); + } + } + + protected function createErrorResponse(Error $jsonRpcError, int $statusCode): ResponseInterface + { + $payload = json_encode($jsonRpcError, \JSON_THROW_ON_ERROR); + $response = $this->responseFactory->createResponse($statusCode) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream($payload)); + + if (405 === $statusCode) { + $response = $response->withHeader('Allow', 'POST, DELETE, OPTIONS'); + } + + return $response; + } + + /** + * Reads the request body, bounded by {@see self::$maxBodyBytes}. + */ + private function readBody(StreamInterface $body): ?string + { + return $this->readBoundedBody($body, $this->maxBodyBytes); + } + + /** + * @param iterable $middleware + * + * @return list + */ + private static function normalizeMiddleware(iterable $middleware): array + { + $normalized = []; + foreach ($middleware as $m) { + if (!$m instanceof MiddlewareInterface) { + throw new InvalidArgumentException('Streamable HTTP middleware must implement Psr\\Http\\Server\\MiddlewareInterface.'); + } + $normalized[] = $m; + } + + return $normalized; + } + + private function handleRequest(ServerRequestInterface $request): ResponseInterface + { + $this->request = $request; + + if ('OPTIONS' === $request->getMethod()) { + return $this->handleOptionsRequest(); + } + + // Read once, here: the era decision needs the body, and so does + // whichever leg it routes to. A PSR-7 stream over `php://input` cannot + // be read twice. + $body = null; + if ('POST' === $request->getMethod()) { + $body = $this->readBody($request->getBody()); + + if (null === $body) { + $this->logger->warning('Rejected POST body exceeding the maximum allowed size.', ['limit' => $this->maxBodyBytes]); + + return $this->createErrorResponse(Error::forInvalidRequest(\sprintf('Request body exceeds the maximum allowed size of %d bytes.', $this->maxBodyBytes)), 413); + } + } + + $classification = $this->classifier->classify($request->getMethod(), $body, self::headers($request)); + + if ($classification->isRejected()) { + \assert(null !== $classification->error); + + return $this->responder->error($classification->error, $classification->httpStatus); + } + + if ($classification->modern) { + return $this->handleModernRequest($body ?? '', $classification->claimedVersion ?? ''); + } + + // The version-header rule only reaches the traffic it is about. Running + // it at the edge would let it answer a modern claim with the handshake + // era's revision list, ahead of the leg that knows better. + return (new MiddlewareRequestHandler( + self::handshakeMiddleware(), + fn (ServerRequestInterface $handshake): ResponseInterface => $this->handleHandshakeRequest($handshake, $body), + ))->handle($request); + } + + private function handleHandshakeRequest(ServerRequestInterface $request, ?string $body): ResponseInterface + { + $sessionIdHeaders = $request->getHeader(self::SESSION_HEADER); + if (\count($sessionIdHeaders) > 1) { + return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must not be repeated.'), 400); + } + + $sessionIdString = $sessionIdHeaders[0] ?? ''; + + try { + $this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null; + // Symfony UID 5.4/6.4 throw the global parent; newer versions throw a namespaced subclass. + } catch (\InvalidArgumentException) { + return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must be a valid UUID.'), 400); + } + + return match ($request->getMethod()) { + 'POST' => $this->handlePostRequest($body ?? ''), + 'DELETE' => $this->handleDeleteRequest(), + default => $this->createErrorResponse(Error::forInvalidRequest('Method Not Allowed'), 405), + }; + } + + /** + * Answers a request that claimed the modern era's per-request envelope. + */ + private function handleModernRequest(string $body, string $claimedVersion): ResponseInterface + { + if (null === $this->stateless) { + return $this->responder->error( + Error::forUnsupportedProtocolVersion($claimedVersion, ProtocolVersion::handshakeVersions()), + 400, + ); + } + + return $this->responder->respond($this->stateless->handle($body, self::headers($this->request))); + } + + /** + * @return array + */ + private static function headers(ServerRequestInterface $request): array + { + $headers = []; + + foreach ($request->getHeaders() as $name => $values) { + $headers[$name] = implode(', ', $values); + } + + return $headers; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Transport/TransportInterface.php b/admin/vendor/mcp/sdk/src/Server/Transport/TransportInterface.php new file mode 100644 index 0000000..58d0978 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Transport/TransportInterface.php @@ -0,0 +1,132 @@ +|Error) + * @phpstan-type FiberResume (FiberReturn|null) + * @phpstan-type FiberSuspend ( + * array{type: 'notification', notification: \Mcp\Schema\JsonRpc\Notification}| + * array{type: 'request', request: \Mcp\Schema\JsonRpc\Request, timeout?: int} + * ) + * @phpstan-type McpFiber \Fiber + * + * @author Christopher Hertel + * @author Kyrian Obikwelu + */ +interface TransportInterface +{ + /** + * Initializes the transport. + */ + public function initialize(): void; + + /** + * Starts the transport's execution process. + * + * - For a blocking transport like STDIO, this method will run a continuous loop. + * - For a single-request transport like HTTP, this will process the request + * and return a result (e.g., a PSR-7 Response) to be sent to the client. + * + * @return TResult the result of the transport's execution, if any + */ + public function listen(): mixed; + + /** + * Send a message to the client immediately (bypassing session queue). + * + * Used for session resolution errors when no session is available. + * The transport decides HOW to send based on context. + * + * @param array $context Context about this message: + * - 'session_id': Uuid|null + * - 'type': 'response'|'request'|'notification' + * - 'status_code': int (HTTP status code for errors) + */ + public function send(string $data, array $context): void; + + /** + * Closes the transport and cleans up any resources. + */ + public function close(): void; + + /** + * Register callback for ALL incoming messages. + * + * The transport calls this whenever ANY message arrives, regardless of source. + * + * @param callable(TransportInterface $transport, string $message, ?Uuid $sessionId): void $listener + */ + public function onMessage(callable $listener): void; + + /** + * Register a listener for when a session is terminated. + * + * The transport calls this when a client disconnects or explicitly ends their session. + * + * @param callable(Uuid $sessionId): void $listener The callback function to execute when destroying a session + */ + public function onSessionEnd(callable $listener): void; + + /** + * Set a provider function to retrieve all queued outgoing messages. + * + * The transport calls this to retrieve all queued messages for a session. + * + * @param callable(Uuid $sessionId): array}> $provider + */ + public function setOutgoingMessagesProvider(callable $provider): void; + + /** + * Set a provider function to retrieve all pending server-initiated requests. + * + * The transport calls this to decide if it should wait for a client response before resuming a Fiber. + * + * @param callable(Uuid $sessionId): array> $provider + */ + public function setPendingRequestsProvider(callable $provider): void; + + /** + * Set a finder function to check for a specific client response. + * + * @param callable(int, Uuid):FiberResume $finder + */ + public function setResponseFinder(callable $finder): void; + + /** + * Set a handler for processing values yielded from a suspended Fiber. + * + * The transport calls this to let the Protocol handle new requests/notifications + * that are yielded from a Fiber's execution. + * + * @param callable(FiberSuspend|null, ?Uuid $sessionId): void $handler + */ + public function setFiberYieldHandler(callable $handler): void; + + /** + * @param McpFiber $fiber + */ + public function attachFiberToSession(\Fiber $fiber, Uuid $sessionId): void; + + /** + * Set the session ID for the current transport context. + * + * @param Uuid|null $sessionId The session ID, or null to clear + */ + public function setSessionId(?Uuid $sessionId): void; +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/CachePolicy.php b/admin/vendor/mcp/sdk/src/Server/Wire/CachePolicy.php new file mode 100644 index 0000000..e22fb21 --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/CachePolicy.php @@ -0,0 +1,87 @@ + + */ +final class CachePolicy +{ + /** + * @param array $perMethod + */ + private function __construct( + private readonly int $ttlMs, + private readonly CacheScope $scope, + private readonly array $perMethod = [], + ) { + } + + /** + * The conservative default: nothing is fresh, nothing is shared. + */ + public static function none(): self + { + return new self(0, CacheScope::Private); + } + + /** + * @param int $ttlMs how long an answer stays fresh, in milliseconds + */ + public static function default(int $ttlMs, CacheScope $scope = CacheScope::Private): self + { + if ($ttlMs < 0) { + throw new InvalidArgumentException(\sprintf('A cache TTL must be zero or more milliseconds, got %d.', $ttlMs)); + } + + return new self($ttlMs, $scope); + } + + /** + * Overrides the policy for one method. + * + * Only the methods the specification makes cacheable are worth naming; any + * other is accepted and simply never consulted. + */ + public function withMethod(string $method, int $ttlMs, CacheScope $scope = CacheScope::Private): self + { + if ($ttlMs < 0) { + throw new InvalidArgumentException(\sprintf('A cache TTL must be zero or more milliseconds, got %d.', $ttlMs)); + } + + return new self($this->ttlMs, $this->scope, [...$this->perMethod, $method => [$ttlMs, $scope]]); + } + + public function ttlFor(string $method): int + { + return $this->perMethod[$method][0] ?? $this->ttlMs; + } + + public function scopeFor(string $method): CacheScope + { + return $this->perMethod[$method][1] ?? $this->scope; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/EraClassification.php b/admin/vendor/mcp/sdk/src/Server/Wire/EraClassification.php new file mode 100644 index 0000000..20c0aae --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/EraClassification.php @@ -0,0 +1,61 @@ + + */ +final class EraClassification +{ + private function __construct( + public readonly bool $modern, + public readonly ?string $claimedVersion, + public readonly ?Error $error, + public readonly int $httpStatus, + ) { + } + + /** + * The handshake era: everything that makes no per-request envelope claim. + */ + public static function legacy(?string $claimedVersion = null): self + { + return new self(false, $claimedVersion, null, 200); + } + + /** + * The modern era, claiming $version — which may be one this server does not + * serve. Routing and support are separate questions, and the dispatcher + * owns the second one so its answer can name what it does support. + */ + public static function modern(string $version): self + { + return new self(true, $version, null, 200); + } + + public static function reject(Error $error, int $httpStatus): self + { + return new self(false, null, $error, $httpStatus); + } + + public function isRejected(): bool + { + return null !== $this->error; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/InboundClassifier.php b/admin/vendor/mcp/sdk/src/Server/Wire/InboundClassifier.php new file mode 100644 index 0000000..826284d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/InboundClassifier.php @@ -0,0 +1,222 @@ + + */ +final class InboundClassifier +{ + public const PROTOCOL_VERSION_HEADER = 'MCP-Protocol-Version'; + + /** + * @param string $httpMethod the request's HTTP method + * @param string|null $body the request body, already read + * @param array $headers request headers, case-insensitively matched + */ + public function classify(string $httpMethod, ?string $body, array $headers = []): EraClassification + { + if ('POST' !== strtoupper($httpMethod)) { + return EraClassification::legacy(); + } + + if (null === $body || '' === trim($body)) { + return EraClassification::legacy(); + } + + try { + $decoded = json_decode($body, true, flags: \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + // Unreadable to both eras. Routed to the handshake leg so the one + // parse error the client sees is the one that leg already writes. + return EraClassification::legacy(); + } + + if (!\is_array($decoded)) { + return EraClassification::legacy(); + } + + $headerVersion = self::header($headers, self::PROTOCOL_VERSION_HEADER); + + if (array_is_list($decoded)) { + return $this->classifyBatch($decoded, $headerVersion); + } + + /* @var array $decoded */ + return $this->classifyMessage($decoded, $headerVersion); + } + + /** + * The header-against-body check both eras' entries share. + * + * Kept here rather than in the dispatcher so the edge and the leg it routes + * to cannot disagree about what a request claims. + * + * @return string|null the disagreement, or null when the two agree + */ + public static function crossCheckVersion(?string $headerVersion, string $claimedVersion): ?string + { + if (null === $headerVersion || $headerVersion === $claimedVersion) { + return null; + } + + return \sprintf('MCP-Protocol-Version header "%s" contradicts the "%s" declared in _meta.', $headerVersion, $claimedVersion); + } + + /** + * Case-insensitive header lookup, since PSR-7 preserves the sender's casing. + * + * @param array $headers + */ + public static function header(array $headers, string $name): ?string + { + foreach ($headers as $key => $value) { + if (0 === strcasecmp($key, $name)) { + return '' === $value ? null : $value; + } + } + + return null; + } + + /** + * @param list $messages + */ + private function classifyBatch(array $messages, ?string $headerVersion): EraClassification + { + foreach ($messages as $message) { + if (!\is_array($message) || array_is_list($message)) { + continue; + } + + /** @var array $message */ + $classification = $this->classifyMessage($message, $headerVersion); + + if ($classification->isRejected()) { + return $classification; + } + + if ($classification->modern) { + return EraClassification::reject( + Error::forInvalidRequest(\sprintf('Protocol revision %s removed JSON-RPC batching; send one message per request.', $classification->claimedVersion)), + 400, + ); + } + } + + return EraClassification::legacy(); + } + + /** + * @param array $message + */ + private function classifyMessage(array $message, ?string $headerVersion): EraClassification + { + $id = $message['id'] ?? null; + $isNotification = !\array_key_exists('id', $message) || null === $id; + + if (!\is_string($id) && !\is_int($id)) { + $id = null; + } + + $params = \is_array($message['params'] ?? null) ? $message['params'] : null; + $meta = \is_array($params['_meta'] ?? null) ? $params['_meta'] : null; + $claim = $meta[RequestMeta::PROTOCOL_VERSION] ?? null; + + if (null !== $claim) { + if (!\is_string($claim) || '' === $claim) { + return EraClassification::reject( + Error::forInvalidParams(\sprintf('Request "_meta" member "%s" must be a non-empty string.', RequestMeta::PROTOCOL_VERSION), $id), + 400, + ); + } + + if (null !== $mismatch = self::crossCheckVersion($headerVersion, $claim)) { + return EraClassification::reject(Error::forHeaderMismatch($mismatch, $id), 400); + } + + return self::eraOf($claim); + } + + if (!self::namesModern($headerVersion)) { + return EraClassification::legacy(); + } + + // The header names a revision that has no handshake, so the body has to + // carry the envelope. A notification is the exception: it has no claim + // to carry under this revision, so there the header is all there is. + if ($isNotification) { + return EraClassification::modern($headerVersion); + } + + return EraClassification::reject( + Error::forInvalidParams(\sprintf('Protocol revision %s requires the "%s" member in "params._meta".', $headerVersion, RequestMeta::PROTOCOL_VERSION), $id), + 400, + ); + } + + /** + * The era a claimed revision belongs to. + * + * An unknown revision counts as modern: it cannot be negotiated through a + * handshake, and the modern leg is the one that can name what it does serve. + */ + private static function eraOf(string $version): EraClassification + { + $known = ProtocolVersion::tryFrom($version); + + if (null !== $known && !$known->isModern()) { + return EraClassification::legacy($version); + } + + return EraClassification::modern($version); + } + + /** + * Only a *known* modern revision counts here. An unrecognised header with + * nothing in the body to back it up is not evidence of an era — it is a + * version this endpoint does not serve, and the handshake leg's version + * middleware is what says so, naming everything the endpoint does serve. + */ + private static function namesModern(?string $version): bool + { + return null !== $version && true === ProtocolVersion::tryFrom($version)?->isModern(); + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/Rev2025Codec.php b/admin/vendor/mcp/sdk/src/Server/Wire/Rev2025Codec.php new file mode 100644 index 0000000..0e1472d --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/Rev2025Codec.php @@ -0,0 +1,28 @@ + + */ +final class Rev2025Codec implements WireCodecInterface +{ + public function encodeResult(string $method, array $result, bool $cacheable = true): array + { + return $result; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/Rev2026Codec.php b/admin/vendor/mcp/sdk/src/Server/Wire/Rev2026Codec.php new file mode 100644 index 0000000..d3b21ef --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/Rev2026Codec.php @@ -0,0 +1,146 @@ + + */ +final class Rev2026Codec implements WireCodecInterface +{ + /** Methods that can come back asking for input (MRTR). */ + public const EXTENDED_RESULT_TYPE_METHODS = [ + 'tools/call', + 'prompts/get', + 'resources/read', + ]; + + /** Methods whose results a client may cache, and which therefore MUST carry hints. */ + public const CACHEABLE_METHODS = [ + 'server/discover', + 'tools/list', + 'prompts/list', + 'resources/list', + 'resources/templates/list', + 'resources/read', + ]; + + private readonly CachePolicy $cachePolicy; + + public function __construct( + private readonly ?Implementation $serverInfo = null, + ?CachePolicy $cachePolicy = null, + ) { + $this->cachePolicy = $cachePolicy ?? CachePolicy::none(); + } + + public function encodeResult(string $method, array $result, bool $cacheable = true): array + { + $stamped = $this->stampResultType($method, $result); + + return $this->stampServerInfo( + $cacheable ? $this->fillCacheHints($method, $stamped) : $stamped, + ); + } + + /** + * A result naming its own type keeps it, but only where the method's + * vocabulary allows more than `complete`. + * + * @param array $result + * + * @return array + */ + private function stampResultType(string $method, array $result): array + { + $provided = $result['resultType'] ?? null; + + if (null === $provided) { + return [...$result, 'resultType' => ResultType::Complete->value]; + } + + if (ResultType::Complete->value === $provided || \in_array($method, self::EXTENDED_RESULT_TYPE_METHODS, true)) { + return $result; + } + + return [...$result, 'resultType' => ResultType::Complete->value]; + } + + /** + * Fills `ttlMs`/`cacheScope`, most-specific author first: a value the + * result itself carries, then the configured {@see CachePolicy}, whose own + * default is "private, do not cache". + * + * @param array $result + * + * @return array + */ + private function fillCacheHints(string $method, array $result): array + { + if (ResultType::Complete->value !== ($result['resultType'] ?? null)) { + return $result; + } + + if (!\in_array($method, self::CACHEABLE_METHODS, true)) { + return $result; + } + + $ttl = $result['ttlMs'] ?? null; + $scope = $result['cacheScope'] ?? null; + + // Invalid authored values fall through to the next author down. + if (!\is_int($ttl) || $ttl < 0) { + $ttl = $this->cachePolicy->ttlFor($method); + } + + if (!\is_string($scope) || null === CacheScope::tryFrom($scope)) { + $scope = $this->cachePolicy->scopeFor($method)->value; + } + + return [...$result, 'ttlMs' => $ttl, 'cacheScope' => $scope]; + } + + /** + * Servers SHOULD identify themselves on every response; an identity the + * result already carries wins. + * + * @param array $result + * + * @return array + */ + private function stampServerInfo(array $result): array + { + if (null === $this->serverInfo) { + return $result; + } + + $meta = \is_array($result['_meta'] ?? null) ? $result['_meta'] : []; + + if (isset($meta[RequestMeta::SERVER_INFO])) { + return $result; + } + + $meta[RequestMeta::SERVER_INFO] = $this->serverInfo; + + return [...$result, '_meta' => $meta]; + } +} diff --git a/admin/vendor/mcp/sdk/src/Server/Wire/WireCodecInterface.php b/admin/vendor/mcp/sdk/src/Server/Wire/WireCodecInterface.php new file mode 100644 index 0000000..e183c4b --- /dev/null +++ b/admin/vendor/mcp/sdk/src/Server/Wire/WireCodecInterface.php @@ -0,0 +1,41 @@ + + */ +interface WireCodecInterface +{ + /** + * Stamps an already-serialized result with whatever this era requires. + * + * @param string $method the request method the result answers + * @param array $result the neutral result body + * @param bool $cacheable whether this answer may carry caching hints at all — false for one + * produced by a multi round-trip retry, whose inputs are not part of + * any cache key + * + * @return array + */ + public function encodeResult(string $method, array $result, bool $cacheable = true): array; +} diff --git a/admin/vendor/nyholm/psr7-server/.github/FUNDING.yml b/admin/vendor/nyholm/psr7-server/.github/FUNDING.yml new file mode 100644 index 0000000..11255a0 --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [nyholm, Zegnat] diff --git a/admin/vendor/nyholm/psr7-server/CHANGELOG.md b/admin/vendor/nyholm/psr7-server/CHANGELOG.md new file mode 100644 index 0000000..b696b98 --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.2 + +### Fixed + +- `ServerRequestCreator::createStreamFromFile()` should first look if there was an upload error +- Dont crash on numeric header names + +## 1.0.1 + +## Fixed + +- PHP8 support + +## 1.0.0 + +- Changed signature of `ServerRequestCreatorInterface::fromArrays()` to allow null values for $post + +## 0.4.2 + +### Fixed + +- Support for numerical headers + +## 0.4.1 + +### Fixed + +- Support for ´HTTP_X_FORWARDED_PROTO´ + +## 0.4.0 + +### Fixed + +- Support for Host-header with port-number + +## 0.3.0 + +### Added + +- `ServerRequestCreator` is final + +### Fixed + +- Fallback to an empty Stream if UploadedFileFactory fails. + +## 0.2.0 + +### Changed + +- Make sure we use psr/http-factory + +## 0.1.2 + +### Added + +- `ServerRequestCreatorInterface` +- `ServerRequestCreator::getHeadersFromServer` + +## 0.1.1 + +### Added + +Better testing + +## 0.1.0 + +First release diff --git a/admin/vendor/nyholm/psr7-server/LICENSE b/admin/vendor/nyholm/psr7-server/LICENSE new file mode 100644 index 0000000..d91e753 --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/vendor/nyholm/psr7-server/README.md b/admin/vendor/nyholm/psr7-server/README.md new file mode 100644 index 0000000..306b660 --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/README.md @@ -0,0 +1,38 @@ +# Helper class to create PSR-7 server request + +[![Latest Version](https://img.shields.io/github/release/Nyholm/psr7-server.svg?style=flat-square)](https://github.com/Nyholm/psr7-server/releases) +[![Build Status](https://img.shields.io/travis/Nyholm/psr7-server/master.svg?style=flat-square)](https://travis-ci.org/Nyholm/psr7-server) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/Nyholm/psr7-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/Nyholm/psr7-server) +[![Quality Score](https://img.shields.io/scrutinizer/g/Nyholm/psr7-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/Nyholm/psr7-server) +[![Total Downloads](https://poser.pugx.org/nyholm/psr7-server/downloads)](https://packagist.org/packages/nyholm/psr7-server) +[![Monthly Downloads](https://poser.pugx.org/nyholm/psr7-server/d/monthly.png)](https://packagist.org/packages/nyholm/psr7-server) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +A helper class that can create ANY PSR-7 server request. + +## Installation + +```bash +composer require nyholm/psr7-server +``` + +## Usage + +```php +// Instanciate ANY PSR-17 factory implementations. Here is nyholm/psr7 as an example +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$creator = new \Nyholm\Psr7Server\ServerRequestCreator( + $psr17Factory, // ServerRequestFactory + $psr17Factory, // UriFactory + $psr17Factory, // UploadedFileFactory + $psr17Factory // StreamFactory +); + +$serverRequest = $creator->fromGlobals(); +``` + +## Other packages + +* [nyholm/psr7](https://github.com/Nyholm/psr7) - A super fast PSR-7 implementation. +* [laminas/laminas-httphandlerrunner](https://github.com/laminas/laminas-httphandlerrunner) - To send/emit PSR-7 responses diff --git a/admin/vendor/nyholm/psr7-server/composer.json b/admin/vendor/nyholm/psr7-server/composer.json new file mode 100644 index 0000000..d7c789d --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/composer.json @@ -0,0 +1,41 @@ +{ + "name": "nyholm/psr7-server", + "description": "Helper classes to handle PSR-7 server requests", + "license": "MIT", + "keywords": ["psr-7", "psr-17"], + "homepage": "http://tnyholm.se", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "require": { + "php": "^7.1 || ^8.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-factory": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3", + "nyholm/psr7": "^1.3", + "nyholm/nsa": "^1.1" + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\Nyholm\\Psr7Server\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" + } +} diff --git a/admin/vendor/nyholm/psr7-server/src/ServerRequestCreator.php b/admin/vendor/nyholm/psr7-server/src/ServerRequestCreator.php new file mode 100644 index 0000000..a10685d --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/src/ServerRequestCreator.php @@ -0,0 +1,303 @@ + + * @author Martijn van der Ven + */ +final class ServerRequestCreator implements ServerRequestCreatorInterface +{ + private $serverRequestFactory; + + private $uriFactory; + + private $uploadedFileFactory; + + private $streamFactory; + + public function __construct( + ServerRequestFactoryInterface $serverRequestFactory, + UriFactoryInterface $uriFactory, + UploadedFileFactoryInterface $uploadedFileFactory, + StreamFactoryInterface $streamFactory + ) { + $this->serverRequestFactory = $serverRequestFactory; + $this->uriFactory = $uriFactory; + $this->uploadedFileFactory = $uploadedFileFactory; + $this->streamFactory = $streamFactory; + } + + /** + * {@inheritdoc} + */ + public function fromGlobals(): ServerRequestInterface + { + $server = $_SERVER; + if (false === isset($server['REQUEST_METHOD'])) { + $server['REQUEST_METHOD'] = 'GET'; + } + + $headers = \function_exists('getallheaders') ? getallheaders() : static::getHeadersFromServer($_SERVER); + + $post = null; + if ('POST' === $this->getMethodFromEnv($server)) { + foreach ($headers as $headerName => $headerValue) { + if (true === \is_int($headerName) || 'content-type' !== \strtolower($headerName)) { + continue; + } + if (\in_array( + \strtolower(\trim(\explode(';', $headerValue, 2)[0])), + ['application/x-www-form-urlencoded', 'multipart/form-data'] + )) { + $post = $_POST; + + break; + } + } + } + + return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $post, $_FILES, \fopen('php://input', 'r') ?: null); + } + + /** + * {@inheritdoc} + */ + public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], ?array $post = null, array $files = [], $body = null): ServerRequestInterface + { + $method = $this->getMethodFromEnv($server); + $uri = $this->getUriFromEnvWithHTTP($server); + $protocol = isset($server['SERVER_PROTOCOL']) ? \str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server); + foreach ($headers as $name => $value) { + // Because PHP automatically casts array keys set with numeric strings to integers, we have to make sure + // that numeric headers will not be sent along as integers, as withAddedHeader can only accept strings. + if (\is_int($name)) { + $name = (string) $name; + } + $serverRequest = $serverRequest->withAddedHeader($name, $value); + } + + $serverRequest = $serverRequest + ->withProtocolVersion($protocol) + ->withCookieParams($cookie) + ->withQueryParams($get) + ->withParsedBody($post) + ->withUploadedFiles($this->normalizeFiles($files)); + + if (null === $body) { + return $serverRequest; + } + + if (\is_resource($body)) { + $body = $this->streamFactory->createStreamFromResource($body); + } elseif (\is_string($body)) { + $body = $this->streamFactory->createStream($body); + } elseif (!$body instanceof StreamInterface) { + throw new \InvalidArgumentException('The $body parameter to ServerRequestCreator::fromArrays must be string, resource or StreamInterface'); + } + + return $serverRequest->withBody($body); + } + + /** + * Implementation from Laminas\Diactoros\marshalHeadersFromSapi(). + */ + public static function getHeadersFromServer(array $server): array + { + $headers = []; + foreach ($server as $key => $value) { + // Apache prefixes environment variables with REDIRECT_ + // if they are added by rewrite rules + if (0 === \strpos($key, 'REDIRECT_')) { + $key = \substr($key, 9); + + // We will not overwrite existing variables with the + // prefixed versions, though + if (\array_key_exists($key, $server)) { + continue; + } + } + + if ($value && 0 === \strpos($key, 'HTTP_')) { + $name = \strtr(\strtolower(\substr($key, 5)), '_', '-'); + $headers[$name] = $value; + + continue; + } + + if ($value && 0 === \strpos($key, 'CONTENT_')) { + $name = 'content-'.\strtolower(\substr($key, 8)); + $headers[$name] = $value; + + continue; + } + } + + return $headers; + } + + private function getMethodFromEnv(array $environment): string + { + if (false === isset($environment['REQUEST_METHOD'])) { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + + return $environment['REQUEST_METHOD']; + } + + private function getUriFromEnvWithHTTP(array $environment): UriInterface + { + $uri = $this->createUriFromArray($environment); + if (empty($uri->getScheme())) { + $uri = $uri->withScheme('http'); + } + + return $uri; + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * + * @return UploadedFileInterface[] + * + * @throws \InvalidArgumentException for unrecognized values + */ + private function normalizeFiles(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (\is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = $this->createUploadedFileFromSpec($value); + } elseif (\is_array($value)) { + $normalized[$key] = $this->normalizeFiles($value); + } else { + throw new \InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return array|UploadedFileInterface + */ + private function createUploadedFileFromSpec(array $value) + { + if (\is_array($value['tmp_name'])) { + return $this->normalizeNestedFileSpec($value); + } + + if (UPLOAD_ERR_OK !== $value['error']) { + $stream = $this->streamFactory->createStream(); + } else { + try { + $stream = $this->streamFactory->createStreamFromFile($value['tmp_name']); + } catch (\RuntimeException $e) { + $stream = $this->streamFactory->createStream(); + } + } + + return $this->uploadedFileFactory->createUploadedFile( + $stream, + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private function normalizeNestedFileSpec(array $files = []): array + { + $normalizedFiles = []; + + foreach (\array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Create a new uri from server variable. + * + * @param array $server typically $_SERVER or similar structure + */ + private function createUriFromArray(array $server): UriInterface + { + $uri = $this->uriFactory->createUri(''); + + if (isset($server['HTTP_X_FORWARDED_PROTO'])) { + $uri = $uri->withScheme($server['HTTP_X_FORWARDED_PROTO']); + } else { + if (isset($server['REQUEST_SCHEME'])) { + $uri = $uri->withScheme($server['REQUEST_SCHEME']); + } elseif (isset($server['HTTPS'])) { + $uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http'); + } + + if (isset($server['SERVER_PORT'])) { + $uri = $uri->withPort($server['SERVER_PORT']); + } + } + + if (isset($server['HTTP_HOST'])) { + if (1 === \preg_match('/^(.+)\:(\d+)$/', $server['HTTP_HOST'], $matches)) { + $uri = $uri->withHost($matches[1])->withPort($matches[2]); + } else { + $uri = $uri->withHost($server['HTTP_HOST']); + } + } elseif (isset($server['SERVER_NAME'])) { + $uri = $uri->withHost($server['SERVER_NAME']); + } + + if (isset($server['REQUEST_URI'])) { + $uri = $uri->withPath(\current(\explode('?', $server['REQUEST_URI']))); + } + + if (isset($server['QUERY_STRING'])) { + $uri = $uri->withQuery($server['QUERY_STRING']); + } + + return $uri; + } +} diff --git a/admin/vendor/nyholm/psr7-server/src/ServerRequestCreatorInterface.php b/admin/vendor/nyholm/psr7-server/src/ServerRequestCreatorInterface.php new file mode 100644 index 0000000..e9f189a --- /dev/null +++ b/admin/vendor/nyholm/psr7-server/src/ServerRequestCreatorInterface.php @@ -0,0 +1,56 @@ + + * @author Martijn van der Ven + */ +interface ServerRequestCreatorInterface +{ + /** + * Create a new server request from the current environment variables. + * Defaults to a GET request to minimise the risk of an \InvalidArgumentException. + * Includes the current request headers as supplied by the server through `getallheaders()`. + * If `getallheaders()` is unavailable on the current server it will fallback to its own `getHeadersFromServer()` method. + * Defaults to php://input for the request body. + * + * @throws \InvalidArgumentException if no valid method or URI can be determined + */ + public function fromGlobals(): ServerRequestInterface; + + /** + * Create a new server request from a set of arrays. + * + * @param array $server typically $_SERVER or similar structure + * @param array $headers typically the output of getallheaders() or similar structure + * @param array $cookie typically $_COOKIE or similar structure + * @param array $get typically $_GET or similar structure + * @param array|null $post typically $_POST or similar structure, represents parsed request body + * @param array $files typically $_FILES or similar structure + * @param StreamInterface|resource|string|null $body Typically stdIn + * + * @throws \InvalidArgumentException if no valid method or URI can be determined + */ + public function fromArrays( + array $server, + array $headers = [], + array $cookie = [], + array $get = [], + ?array $post = null, + array $files = [], + $body = null + ): ServerRequestInterface; + + /** + * Get parsed headers from ($_SERVER) array. + * + * @param array $server typically $_SERVER or similar structure + */ + public static function getHeadersFromServer(array $server): array; +} diff --git a/admin/vendor/nyholm/psr7/CHANGELOG.md b/admin/vendor/nyholm/psr7/CHANGELOG.md new file mode 100644 index 0000000..17a819f --- /dev/null +++ b/admin/vendor/nyholm/psr7/CHANGELOG.md @@ -0,0 +1,172 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.8.2 + +- Fix deprecation warnings in PHP 8.4 + +## 1.8.1 + +- Fix error handling in Stream::getContents() + +## 1.8.0 + +- Deprecate HttplugFactory, use Psr17Factory instead +- Make depencendy on php-http/message-factory optional + +## 1.7.0 + +- Bump to PHP 7.2 minimum +- Allow psr/http-message v2 +- Use copy-on-write for streams created from strings + +## 1.6.1 + +- Security fix: CVE-2023-29197 + +## 1.6.0 + +### Changed + +- Seek to the begining of the string when using Stream::create() +- Populate ServerRequest::getQueryParams() on instantiation +- Encode [reserved characters](https://www.rfc-editor.org/rfc/rfc3986#appendix-A) in userinfo in Uri +- Normalize leading slashes for Uri::getPath() +- Make Stream's constructor public +- Add some missing type checks on arguments + +## 1.5.1 + +### Fixed + +- Fixed deprecations on PHP 8.1 + +## 1.5.0 + +### Added + +- Add explicit `@return mixed` +- Add explicit return types to HttplugFactory + +### Fixed + +- Improve error handling with streams + +## 1.4.1 + +### Fixed + +- `Psr17Factory::createStreamFromFile`, `UploadedFile::moveTo`, and + `UploadedFile::getStream` no longer throw `ValueError` in PHP 8. + +## 1.4.0 + +### Removed + +The `final` keyword was replaced by `@final` annotation. + +## 1.3.2 + +### Fixed + +- `Stream::read()` must not return boolean. +- Improved exception message when using wrong HTTP status code. + +## 1.3.1 + +### Fixed + +- Allow installation on PHP8 + +## 1.3.0 + +### Added + +- Make Stream::__toString() compatible with throwing exceptions on PHP 7.4. + +### Fixed + +- Support for UTF-8 hostnames +- Support for numeric header names + +## 1.2.1 + +### Changed + +- Added `.github` and `phpstan.neon.dist` to `.gitattributes`. + +## 1.2.0 + +### Changed + +- Change minimal port number to 0 (unix socket) +- Updated `Psr17Factory::createResponse` to respect the specification. If second + argument is not used, a standard reason phrase. If an empty string is passed, + then the reason phrase will be empty. + +### Fixed + +- Check for seekable on the stream resource. +- Fixed the `Response::$reason` should never be null. + +## 1.1.0 + +### Added + +- Improved performance +- More tests for `UploadedFile` and `HttplugFactory` + +### Removed + +- Dead code + +## 1.0.1 + +### Fixed + +- Handle `fopen` failing in createStreamFromFile according to PSR-7. +- Reduce execution path to speed up performance. +- Fixed typos. +- Code style. + +## 1.0.0 + +### Added + +- Support for final PSR-17 (HTTP factories). (`Psr17Factory`) +- Support for numeric header values. +- Support for empty header values. +- All classes are final +- `HttplugFactory` that implements factory interfaces from HTTPlug. + +### Changed + +- `ServerRequest` does not extend `Request`. + +### Removed + +- The HTTPlug discovery strategy was removed since it is included in php-http/discovery 1.4. +- `UploadedFileFactory()` was removed in favor for `Psr17Factory`. +- `ServerRequestFactory()` was removed in favor for `Psr17Factory`. +- `StreamFactory`, `UriFactory`, abd `MessageFactory`. Use `HttplugFactory` instead. +- `ServerRequestFactory::createServerRequestFromArray`, `ServerRequestFactory::createServerRequestFromArrays` and + `ServerRequestFactory::createServerRequestFromGlobals`. Please use the new `nyholm/psr7-server` instead. + +## 0.3.0 + +### Added + +- Return types. +- Many `InvalidArgumentException`s are thrown when you use invalid arguments. +- Integration tests for `UploadedFile` and `ServerRequest`. + +### Changed + +- We dropped PHP7.0 support. +- PSR-17 factories have been marked as internal. They do not fall under our BC promise until PSR-17 is accepted. +- `UploadedFileFactory::createUploadedFile` does not accept a string file path. + +## 0.2.3 + +No changelog before this release diff --git a/admin/vendor/nyholm/psr7/LICENSE b/admin/vendor/nyholm/psr7/LICENSE new file mode 100644 index 0000000..d6c5231 --- /dev/null +++ b/admin/vendor/nyholm/psr7/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/vendor/nyholm/psr7/README.md b/admin/vendor/nyholm/psr7/README.md new file mode 100644 index 0000000..7fc30bc --- /dev/null +++ b/admin/vendor/nyholm/psr7/README.md @@ -0,0 +1,108 @@ +# PSR-7 implementation + +[![Latest Version](https://img.shields.io/github/release/Nyholm/psr7.svg?style=flat-square)](https://github.com/Nyholm/psr7/releases) +[![Total Downloads](https://poser.pugx.org/nyholm/psr7/downloads)](https://packagist.org/packages/nyholm/psr7) +[![Monthly Downloads](https://poser.pugx.org/nyholm/psr7/d/monthly.png)](https://packagist.org/packages/nyholm/psr7) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Static analysis](https://github.com/Nyholm/psr7/actions/workflows/static.yml/badge.svg?branch=master)](https://github.com/Nyholm/psr7/actions/workflows/static.yml?query=branch%3Amaster) +[![Tests](https://github.com/Nyholm/psr7/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/Nyholm/psr7/actions/workflows/tests.yml?query=branch%3Amaster) + +A super lightweight PSR-7 implementation. Very strict and very fast. + +| Description | Guzzle | Laminas | Slim | Nyholm | +| ---- | ------ | ---- | ---- | ------ | +| Lines of code | 3.300 | 3.100 | 1.900 | 1.000 | +| PSR-7* | 66% | 100% | 75% | 100% | +| PSR-17 | No | Yes | Yes | Yes | +| HTTPlug | No | No | No | Yes | +| Performance (runs per second)** | 14.553 | 14.703 | 13.416 | 17.734 | + +\* Percent of completed tests in https://github.com/php-http/psr7-integration-tests + +\** Benchmark with 50.000 runs. See https://github.com/devanych/psr-http-benchmark (higher is better) + +## Installation + +```bash +composer require nyholm/psr7 +``` + +If you are using Symfony Flex then you get all message factories registered as services. + +## Usage + +The PSR-7 objects do not contain any other public methods than those defined in +the [PSR-7 specification](https://www.php-fig.org/psr/psr-7/). + +### Create objects + +Use the PSR-17 factory to create requests, streams, URIs etc. + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$stream = $psr17Factory->createStream('foobar'); +``` + +### Sending a request + +With [HTTPlug](http://httplug.io/) or any other PSR-18 (HTTP client) you may send +requests like: + +```bash +composer require kriswallsmith/buzz +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$psr18Client = new \Buzz\Client\Curl($psr17Factory); + +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$response = $psr18Client->sendRequest($request); +``` + +### Create server requests + +The [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) package can be used +to create server requests from PHP superglobals. + +```bash +composer require nyholm/psr7-server +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$creator = new \Nyholm\Psr7Server\ServerRequestCreator( + $psr17Factory, // ServerRequestFactory + $psr17Factory, // UriFactory + $psr17Factory, // UploadedFileFactory + $psr17Factory // StreamFactory +); + +$serverRequest = $creator->fromGlobals(); +``` + +### Emitting a response + +```bash +composer require laminas/laminas-httphandlerrunner +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$responseBody = $psr17Factory->createStream('Hello world'); +$response = $psr17Factory->createResponse(200)->withBody($responseBody); +(new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response); +``` + +## Our goal + +This package is currently maintained by [Tobias Nyholm](http://nyholm.se) and +[Martijn van der Ven](https://vanderven.se/martijn/). They have decided that the +goal of this library should be to provide a super strict implementation of +[PSR-7](https://www.php-fig.org/psr/psr-7/) that is blazing fast. + +The package will never include any extra features nor helper methods. All our classes +and functions exist because they are required to fulfill the PSR-7 specification. diff --git a/admin/vendor/nyholm/psr7/composer.json b/admin/vendor/nyholm/psr7/composer.json new file mode 100644 index 0000000..c607615 --- /dev/null +++ b/admin/vendor/nyholm/psr7/composer.json @@ -0,0 +1,49 @@ +{ + "name": "nyholm/psr7", + "description": "A fast PHP7 implementation of PSR-7", + "license": "MIT", + "keywords": ["psr-7", "psr-17"], + "homepage": "https://tnyholm.se", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "require": { + "php": ">=7.2", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-factory": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "http-interop/http-factory-tests": "^0.9", + "symfony/error-handler": "^4.4" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0", + "psr/http-factory-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\Nyholm\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + } +} diff --git a/admin/vendor/nyholm/psr7/src/Factory/HttplugFactory.php b/admin/vendor/nyholm/psr7/src/Factory/HttplugFactory.php new file mode 100644 index 0000000..cc9285d --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Factory/HttplugFactory.php @@ -0,0 +1,53 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + * + * @deprecated since version 1.8, use Psr17Factory instead + */ +class HttplugFactory implements MessageFactory, StreamFactory, UriFactory +{ + public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface + { + return new Request($method, $uri, $headers, $body, $protocolVersion); + } + + public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1'): ResponseInterface + { + return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase); + } + + public function createStream($body = null): StreamInterface + { + return Stream::create($body ?? ''); + } + + public function createUri($uri = ''): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + return new Uri($uri); + } +} diff --git a/admin/vendor/nyholm/psr7/src/Factory/Psr17Factory.php b/admin/vendor/nyholm/psr7/src/Factory/Psr17Factory.php new file mode 100644 index 0000000..2fa98be --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Factory/Psr17Factory.php @@ -0,0 +1,78 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface +{ + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + if (2 > \func_num_args()) { + // This will make the Response class to use a custom reasonPhrase + $reasonPhrase = null; + } + + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createStream(string $content = ''): StreamInterface + { + return Stream::create($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + if ('' === $filename) { + throw new \RuntimeException('Path cannot be empty'); + } + + if (false === $resource = @\fopen($filename, $mode)) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode)); + } + + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? '')); + } + + return Stream::create($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Stream::create($resource); + } + + public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface + { + if (null === $size) { + $size = $stream->getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } +} diff --git a/admin/vendor/nyholm/psr7/src/MessageTrait.php b/admin/vendor/nyholm/psr7/src/MessageTrait.php new file mode 100644 index 0000000..7d02383 --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/MessageTrait.php @@ -0,0 +1,235 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait MessageTrait +{ + /** @var array Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + /** + * @return static + */ + public function withProtocolVersion($version): MessageInterface + { + if (!\is_scalar($version)) { + throw new \InvalidArgumentException('Protocol version must be a string'); + } + + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = (string) $version; + + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]); + } + + public function getHeader($header): array + { + if (!\is_string($header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return \implode(', ', $this->getHeader($header)); + } + + /** + * @return static + */ + public function withHeader($header, $value): MessageInterface + { + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + /** + * @return static + */ + public function withAddedHeader($header, $value): MessageInterface + { + if (!\is_string($header) || '' === $header) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $new = clone $this; + $new->setHeaders([$header => $value]); + + return $new; + } + + /** + * @return static + */ + public function withoutHeader($header): MessageInterface + { + if (!\is_string($header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (null === $this->stream) { + $this->stream = Stream::create(''); + } + + return $this->stream; + } + + /** + * @return static + */ + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + + return $new; + } + + private function setHeaders(array $headers): void + { + foreach ($headers as $header => $value) { + if (\is_int($header)) { + // If a header name was set to a numeric string, PHP will cast the key to an int. + // We must cast it back to a string in order to comply with validation. + $header = (string) $header; + } + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Make sure the header complies with RFC 7230. + * + * Header names must be a non-empty string consisting of token characters. + * + * Header values must be strings consisting of visible characters with all optional + * leading and trailing whitespace stripped. This method will always strip such + * optional whitespace. Note that the method does not allow folding whitespace within + * the values as this was deprecated for almost all instances by the RFC. + * + * header-field = field-name ":" OWS field-value OWS + * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" + * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) + * OWS = *( SP / HTAB ) + * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function validateAndTrimHeader($header, $values): array + { + if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + if (!\is_array($values)) { + // This is simple, just one value. + if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); + } + + return [\trim((string) $values, " \t")]; + } + + if (empty($values)) { + throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given'); + } + + // Assert Non empty array + $returnValues = []; + foreach ($values as $v) { + if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", (string) $v)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); + } + + $returnValues[] = \trim((string) $v, " \t"); + } + + return $returnValues; + } +} diff --git a/admin/vendor/nyholm/psr7/src/Request.php b/admin/vendor/nyholm/psr7/src/Request.php new file mode 100644 index 0000000..d50744e --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Request.php @@ -0,0 +1,47 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Request implements RequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1') + { + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until Request::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } +} diff --git a/admin/vendor/nyholm/psr7/src/RequestTrait.php b/admin/vendor/nyholm/psr7/src/RequestTrait.php new file mode 100644 index 0000000..2dbb3ab --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/RequestTrait.php @@ -0,0 +1,127 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait RequestTrait +{ + /** @var string */ + private $method; + + /** @var string|null */ + private $requestTarget; + + /** @var UriInterface|null */ + private $uri; + + public function getRequestTarget(): string + { + if (null !== $this->requestTarget) { + return $this->requestTarget; + } + + if ('' === $target = $this->uri->getPath()) { + $target = '/'; + } + if ('' !== $this->uri->getQuery()) { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + /** + * @return static + */ + public function withRequestTarget($requestTarget): RequestInterface + { + if (!\is_string($requestTarget)) { + throw new \InvalidArgumentException('Request target must be a string'); + } + + if (\preg_match('#\s#', $requestTarget)) { + throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + /** + * @return static + */ + public function withMethod($method): RequestInterface + { + if (!\is_string($method)) { + throw new \InvalidArgumentException('Method must be a string'); + } + + $new = clone $this; + $new->method = $method; + + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + /** + * @return static + */ + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !$this->hasHeader('Host')) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + if ('' === $host = $this->uri->getHost()) { + return; + } + + if (null !== ($port = $this->uri->getPort())) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $this->headerNames['host'] = $header = 'Host'; + } + + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/admin/vendor/nyholm/psr7/src/Response.php b/admin/vendor/nyholm/psr7/src/Response.php new file mode 100644 index 0000000..71eb2fa --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Response.php @@ -0,0 +1,93 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Response implements ResponseInterface +{ + use MessageTrait; + + /** @var array Map of standard HTTP status code/reason phrases */ + private const PHRASES = [ + 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null) + { + // If we got no body, defer initialization of the stream until Response::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + + $this->statusCode = $status; + $this->setHeaders($headers); + if (null === $reason && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$status]; + } else { + $this->reasonPhrase = $reason ?? ''; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + /** + * @return static + */ + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + if (!\is_int($code) && !\is_string($code)) { + throw new \InvalidArgumentException('Status code has to be an integer'); + } + + $code = (int) $code; + if ($code < 100 || $code > 599) { + throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code)); + } + + $new = clone $this; + $new->statusCode = $code; + if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + + return $new; + } +} diff --git a/admin/vendor/nyholm/psr7/src/ServerRequest.php b/admin/vendor/nyholm/psr7/src/ServerRequest.php new file mode 100644 index 0000000..a3c5ba9 --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/ServerRequest.php @@ -0,0 +1,201 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class ServerRequest implements ServerRequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** @var array */ + private $attributes = []; + + /** @var array */ + private $cookieParams = []; + + /** @var array|object|null */ + private $parsedBody; + + /** @var array */ + private $queryParams = []; + + /** @var array */ + private $serverParams; + + /** @var UploadedFileInterface[] */ + private $uploadedFiles = []; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = []) + { + $this->serverParams = $serverParams; + + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + \parse_str($uri->getQuery(), $this->queryParams); + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until ServerRequest::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + /** + * @return static + */ + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + /** + * @return static + */ + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + /** + * @return static + */ + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * @return static + */ + public function withParsedBody($data): ServerRequestInterface + { + if (!\is_array($data) && !\is_object($data) && null !== $data) { + throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); + } + + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + if (false === \array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * @return static + */ + public function withAttribute($attribute, $value): ServerRequestInterface + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * @return static + */ + public function withoutAttribute($attribute): ServerRequestInterface + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + if (false === \array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/admin/vendor/nyholm/psr7/src/Stream.php b/admin/vendor/nyholm/psr7/src/Stream.php new file mode 100644 index 0000000..63b7d6d --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Stream.php @@ -0,0 +1,399 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Stream implements StreamInterface +{ + use StreamTrait; + + /** @var resource|null A resource reference */ + private $stream; + + /** @var bool */ + private $seekable; + + /** @var bool */ + private $readable; + + /** @var bool */ + private $writable; + + /** @var array|mixed|void|bool|null */ + private $uri; + + /** @var int|null */ + private $size; + + /** @var array Hash of readable and writable stream types */ + private const READ_WRITE_HASH = [ + 'read' => [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true, + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, + ], + ]; + + /** + * @param resource $body + */ + public function __construct($body) + { + if (!\is_resource($body)) { + throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource'); + } + + $this->stream = $body; + $meta = \stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR); + $this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]); + $this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]); + } + + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface $body + * + * @throws \InvalidArgumentException + */ + public static function create($body = ''): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_string($body)) { + if (200000 <= \strlen($body)) { + $body = self::openZvalStream($body); + } else { + $resource = \fopen('php://memory', 'r+'); + \fwrite($resource, $body); + \fseek($resource, 0); + $body = $resource; + } + } + + if (!\is_resource($body)) { + throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface'); + } + + return new self($body); + } + + /** + * Closes the stream when the destructed. + */ + public function __destruct() + { + $this->close(); + } + + public function close(): void + { + if (isset($this->stream)) { + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + private function getUri() + { + if (false !== $this->uri) { + $this->uri = $this->getMetadata('uri') ?? false; + } + + return $this->uri; + } + + public function getSize(): ?int + { + if (null !== $this->size) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($uri = $this->getUri()) { + \clearstatcache(true, $uri); + } + + $stats = \fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (false === $result = @\ftell($this->stream)) { + throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function eof(): bool + { + return !isset($this->stream) || \feof($this->stream); + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + + if (-1 === \fseek($this->stream, $offset, $whence)) { + throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, true)); + } + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + + if (false === $result = @\fwrite($this->stream, $string)) { + throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + if (false === $result = @\fread($this->stream, $length)) { + throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $exception = null; + + \set_error_handler(static function ($type, $message) use (&$exception) { + throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message); + }); + + try { + return \stream_get_contents($this->stream); + } catch (\Throwable $e) { + throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e); + } finally { + \restore_error_handler(); + } + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if (null !== $key && !\is_string($key)) { + throw new \InvalidArgumentException('Metadata key must be a string'); + } + + if (!isset($this->stream)) { + return $key ? null : []; + } + + $meta = \stream_get_meta_data($this->stream); + + if (null === $key) { + return $meta; + } + + return $meta[$key] ?? null; + } + + private static function openZvalStream(string $body) + { + static $wrapper; + + $wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class() { + public $context; + + private $data; + private $position = 0; + + public function stream_open(): bool + { + $this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data']; + \stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null); + + return true; + } + + public function stream_read(int $count): string + { + $result = \substr($this->data, $this->position, $count); + $this->position += \strlen($result); + + return $result; + } + + public function stream_write(string $data): int + { + $this->data = \substr_replace($this->data, $data, $this->position, \strlen($data)); + $this->position += \strlen($data); + + return \strlen($data); + } + + public function stream_tell(): int + { + return $this->position; + } + + public function stream_eof(): bool + { + return \strlen($this->data) <= $this->position; + } + + public function stream_stat(): array + { + return [ + 'mode' => 33206, // POSIX_S_IFREG | 0666 + 'nlink' => 1, + 'rdev' => -1, + 'size' => \strlen($this->data), + 'blksize' => -1, + 'blocks' => -1, + ]; + } + + public function stream_seek(int $offset, int $whence): bool + { + if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) { + $this->position = $offset; + } elseif (\SEEK_CUR === $whence && 0 <= $offset) { + $this->position += $offset; + } elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) { + $this->position = $offset; + } else { + return false; + } + + return true; + } + + public function stream_set_option(): bool + { + return true; + } + + public function stream_truncate(int $new_size): bool + { + if ($new_size) { + $this->data = \substr($this->data, 0, $new_size); + $this->position = \min($this->position, $new_size); + } else { + $this->data = ''; + $this->position = 0; + } + + return true; + } + })); + + $context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]); + + if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', false, $context)) { + \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper); + $stream = \fopen('Nyholm-Psr7-Zval://', 'r+', false, $context); + } + + return $stream; + } +} diff --git a/admin/vendor/nyholm/psr7/src/StreamTrait.php b/admin/vendor/nyholm/psr7/src/StreamTrait.php new file mode 100644 index 0000000..41a3f9d --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/StreamTrait.php @@ -0,0 +1,57 @@ += 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) { + /** + * @internal + */ + trait StreamTrait + { + public function __toString(): string + { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } + } +} else { + /** + * @internal + */ + trait StreamTrait + { + /** + * @return string + */ + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Throwable $e) { + if (\is_array($errorHandler = \set_error_handler('var_dump'))) { + $errorHandler = $errorHandler[0] ?? null; + } + \restore_error_handler(); + + if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) { + return \trigger_error((string) $e, \E_USER_ERROR); + } + + return ''; + } + } + } +} diff --git a/admin/vendor/nyholm/psr7/src/UploadedFile.php b/admin/vendor/nyholm/psr7/src/UploadedFile.php new file mode 100644 index 0000000..c77dca4 --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/UploadedFile.php @@ -0,0 +1,179 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class UploadedFile implements UploadedFileInterface +{ + /** @var array */ + private const ERRORS = [ + \UPLOAD_ERR_OK => 1, + \UPLOAD_ERR_INI_SIZE => 1, + \UPLOAD_ERR_FORM_SIZE => 1, + \UPLOAD_ERR_PARTIAL => 1, + \UPLOAD_ERR_NO_FILE => 1, + \UPLOAD_ERR_NO_TMP_DIR => 1, + \UPLOAD_ERR_CANT_WRITE => 1, + \UPLOAD_ERR_EXTENSION => 1, + ]; + + /** @var string */ + private $clientFilename; + + /** @var string */ + private $clientMediaType; + + /** @var int */ + private $error; + + /** @var string|null */ + private $file; + + /** @var bool */ + private $moved = false; + + /** @var int */ + private $size; + + /** @var StreamInterface|null */ + private $stream; + + /** + * @param StreamInterface|string|resource $streamOrFile + * @param int $size + * @param int $errorStatus + * @param string|null $clientFilename + * @param string|null $clientMediaType + */ + public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null) + { + if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) { + throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants'); + } + + if (false === \is_int($size)) { + throw new \InvalidArgumentException('Upload file size must be an integer'); + } + + if (null !== $clientFilename && !\is_string($clientFilename)) { + throw new \InvalidArgumentException('Upload file client filename must be a string or null'); + } + + if (null !== $clientMediaType && !\is_string($clientMediaType)) { + throw new \InvalidArgumentException('Upload file client media type must be a string or null'); + } + + $this->error = $errorStatus; + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if (\UPLOAD_ERR_OK === $this->error) { + // Depending on the value set file or stream variable. + if (\is_string($streamOrFile) && '' !== $streamOrFile) { + $this->file = $streamOrFile; + } elseif (\is_resource($streamOrFile)) { + $this->stream = Stream::create($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); + } + } + } + + /** + * @throws \RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (\UPLOAD_ERR_OK !== $this->error) { + throw new \RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->moved) { + throw new \RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + if (false === $resource = @\fopen($this->file, 'r')) { + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? '')); + } + + return Stream::create($resource); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (!\is_string($targetPath) || '' === $targetPath) { + throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + } + + if (null !== $this->file) { + $this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath); + + if (false === $this->moved) { + throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? '')); + } + } else { + $stream = $this->getStream(); + if ($stream->isSeekable()) { + $stream->rewind(); + } + + if (false === $resource = @\fopen($targetPath, 'w')) { + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? '')); + } + + $dest = Stream::create($resource); + + while (!$stream->eof()) { + if (!$dest->write($stream->read(1048576))) { + break; + } + } + + $this->moved = true; + } + } + + public function getSize(): int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/admin/vendor/nyholm/psr7/src/Uri.php b/admin/vendor/nyholm/psr7/src/Uri.php new file mode 100644 index 0000000..621e2e7 --- /dev/null +++ b/admin/vendor/nyholm/psr7/src/Uri.php @@ -0,0 +1,356 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Uri implements UriInterface +{ + private const SCHEMES = ['http' => 80, 'https' => 443]; + + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + + private const CHAR_GEN_DELIMS = ':\/\?#\[\]@'; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + public function __construct(string $uri = '') + { + if ('' !== $uri) { + if (false === $parts = \parse_url($uri)) { + throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri)); + } + + // Apply parse_url parts to a URI. + $this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->userInfo = $parts['user'] ?? ''; + $this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; + $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + } + } + + public function __toString(): string + { + return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + if ('' === $this->host) { + return ''; + } + + $authority = $this->host; + if ('' !== $this->userInfo) { + $authority = $this->userInfo . '@' . $authority; + } + + if (null !== $this->port) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + $path = $this->path; + + if ('' !== $path && '/' !== $path[0]) { + if ('' !== $this->host) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + // If the path is starting with more than one "/", the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + + return $path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + /** + * @return static + */ + public function withScheme($scheme): UriInterface + { + if (!\is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->port = $new->filterPort($new->port); + + return $new; + } + + /** + * @return static + */ + public function withUserInfo($user, $password = null): UriInterface + { + if (!\is_string($user)) { + throw new \InvalidArgumentException('User must be a string'); + } + + $info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user); + if (null !== $password && '' !== $password) { + if (!\is_string($password)) { + throw new \InvalidArgumentException('Password must be a string'); + } + + $info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + + return $new; + } + + /** + * @return static + */ + public function withHost($host): UriInterface + { + if (!\is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->host = $host; + + return $new; + } + + /** + * @return static + */ + public function withPort($port): UriInterface + { + if ($this->port === $port = $this->filterPort($port)) { + return $this; + } + + $new = clone $this; + $new->port = $port; + + return $new; + } + + /** + * @return static + */ + public function withPath($path): UriInterface + { + if ($this->path === $path = $this->filterPath($path)) { + return $this; + } + + $new = clone $this; + $new->path = $path; + + return $new; + } + + /** + * @return static + */ + public function withQuery($query): UriInterface + { + if ($this->query === $query = $this->filterQueryAndFragment($query)) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + /** + * @return static + */ + public function withFragment($fragment): UriInterface + { + if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Create a URI string from its various parts. + */ + private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string + { + $uri = ''; + if ('' !== $scheme) { + $uri .= $scheme . ':'; + } + + if ('' !== $authority) { + $uri .= '//' . $authority; + } + + if ('' !== $path) { + if ('/' !== $path[0]) { + if ('' !== $authority) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + if ('' === $authority) { + // If the path is starting with more than one "/" and no authority is present, the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + } + + $uri .= $path; + } + + if ('' !== $query) { + $uri .= '?' . $query; + } + + if ('' !== $fragment) { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Is a given port non-standard for the current scheme? + */ + private static function isNonStandardPort(string $scheme, int $port): bool + { + return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme]; + } + + private function filterPort($port): ?int + { + if (null === $port) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xFFFF < $port) { + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); + } + + return self::isNonStandardPort($this->scheme, $port) ? $port : null; + } + + private function filterPath($path): string + { + if (!\is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path); + } + + private function filterQueryAndFragment($str): string + { + if (!\is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str); + } + + private static function rawurlencodeMatchZero(array $match): string + { + return \rawurlencode($match[0]); + } +} diff --git a/admin/vendor/opis/json-schema/LICENSE b/admin/vendor/opis/json-schema/LICENSE new file mode 100644 index 0000000..2bb9ad2 --- /dev/null +++ b/admin/vendor/opis/json-schema/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/NOTICE b/admin/vendor/opis/json-schema/NOTICE new file mode 100644 index 0000000..6ebfe37 --- /dev/null +++ b/admin/vendor/opis/json-schema/NOTICE @@ -0,0 +1,9 @@ +Opis Json Schema +Copyright 2018-2021 Zindex Software + +This product includes software developed at +Zindex Software (http://zindex.software). + +This software was originally developed by Marius Sarca and Sorin Sarca +(Copyright 2017-2018). The copyright info was changed with the permission +of the original authors. diff --git a/admin/vendor/opis/json-schema/README.md b/admin/vendor/opis/json-schema/README.md new file mode 100644 index 0000000..f22df27 --- /dev/null +++ b/admin/vendor/opis/json-schema/README.md @@ -0,0 +1,66 @@ +Opis JSON Schema +==================== +[![Tests](https://github.com/opis/json-schema/workflows/Tests/badge.svg)](https://github.com/opis/json-schema/actions) +[![Packagist Version](https://img.shields.io/packagist/v/opis/json-schema?label=Version)](https://packagist.org/packages/opis/json-schema) +[![Packagist Downloads](https://img.shields.io/packagist/dt/opis/json-schema?label=Downloads)](https://packagist.org/packages/opis/json-schema) +[![Packagist License](https://img.shields.io/packagist/l/opis/json-schema?color=teal&label=License)](https://packagist.org/packages/opis/json-schema) + +Validate JSON documents +----------- + +**Opis JSON Schema** is a PHP implementation for the [JSON Schema] standard (draft-2020-12, draft-2019-09, draft-07 and draft-06), that +will help you validate all sorts of JSON documents, whether they are configuration files or a set +of data sent to a RESTful API endpoint. + + +**The library's key features:** + +- Supports all keywords from all drafts (draft-2020-12 down to draft-06) +- Support for custom errors inside schema using [`$error` keyword](https://opis.io/json-schema/2.x/errors.html) +- Support for custom PHP filters using [`$filters` keyword](https://docs.opis.io/json-schema/2.x/filters.html) +- Advanced schema reuse using [`$map` keyword](https://docs.opis.io/json-schema/2.x/mappers.html) +- Intuitive schema composition using [slots](https://docs.opis.io/json-schema/2.x/slots.html) +- Support for absolute & relative [json pointers](https://docs.opis.io/json-schema/2.x/pointers.html) +- Support for [URI templates](https://docs.opis.io/json-schema/2.x/uri-template.html) +- Support for [`$data` keyword](https://docs.opis.io/json-schema/2.x/data-keyword.html) +- Support for [casting](https://docs.opis.io/json-schema/2.x/pragma.html#cast) +- Support for custom [formats](https://docs.opis.io/json-schema/2.x/php-format.html) and [media types](https://docs.opis.io/json-schema/2.x/php-media-type.html) + +### Documentation + +The full documentation for this library can be found [here][documentation]. +We provide documentation for both [JSON Schema] standard itself as well as for +the library's own API. + +### License + +**Opis JSON Schema** is licensed under the [Apache License, Version 2.0][apache_license]. + +### Requirements + +* PHP ^7.4 || ^8.0 + +## Installation + +**Opis JSON Schema** is available on [Packagist], and it can be installed from a +command line interface by using [Composer]. + +```bash +composer require opis/json-schema +``` + +Or you could directly reference it into your `composer.json` file as a dependency + +```json +{ + "require": { + "opis/json-schema": "^2.6" + } +} +``` + +[documentation]: https://opis.io/json-schema +[apache_license]: https://www.apache.org/licenses/LICENSE-2.0 "Apache License" +[Packagist]: https://packagist.org/packages/opis/json-schema "Packagist" +[Composer]: https://getcomposer.org "Composer" +[JSON Schema]: http://json-schema.org/ "JSON Schema" diff --git a/admin/vendor/opis/json-schema/SECURITY.md b/admin/vendor/opis/json-schema/SECURITY.md new file mode 100644 index 0000000..90e66c1 --- /dev/null +++ b/admin/vendor/opis/json-schema/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 2.x | :white_check_mark: | +| 1.x | :white_check_mark: | + +## Reporting a Vulnerability + +Please send an e-mail to authors specified in the [composer.json](https://github.com/opis/json-schema/blob/master/composer.json) file. diff --git a/admin/vendor/opis/json-schema/autoload.php b/admin/vendor/opis/json-schema/autoload.php new file mode 100644 index 0000000..649fab8 --- /dev/null +++ b/admin/vendor/opis/json-schema/autoload.php @@ -0,0 +1,23 @@ + false, + 'allowFormats' => true, + 'allowMappers' => false, + 'allowTemplates' => false, + 'allowGlobals' => false, + 'allowDefaults' => false, + 'allowSlots' => false, + 'allowKeywordValidators' => false, + 'allowPragmas' => false, + 'allowDataKeyword' => false, + 'allowKeywordsAlongsideRef' => false, + 'allowUnevaluated' => true, + 'allowRelativeJsonPointerInRef' => false, + 'allowExclusiveMinMaxAsBool' => false, + 'keepDependenciesKeyword' => false, + 'keepAdditionalItemsKeyword' => false, + ]; + + public function __construct( + ?SchemaLoader $loader = null, + int $max_errors = 1, + bool $stop_at_first_error = true + ) + { + parent::__construct($loader, $max_errors, $stop_at_first_error); + + // Set parser options + $parser = $this->parser(); + foreach (static::COMPLIANT_OPTIONS as $name => $value) { + $parser->setOption($name, $value); + } + } +} diff --git a/admin/vendor/opis/json-schema/src/ContentEncoding.php b/admin/vendor/opis/json-schema/src/ContentEncoding.php new file mode 100644 index 0000000..3c6eb23 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/ContentEncoding.php @@ -0,0 +1,28 @@ +args = $args; + } + + public function getArgs(): array { + return $this->args; + } +} diff --git a/admin/vendor/opis/json-schema/src/Errors/ErrorContainer.php b/admin/vendor/opis/json-schema/src/Errors/ErrorContainer.php new file mode 100644 index 0000000..5cf10de --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Errors/ErrorContainer.php @@ -0,0 +1,148 @@ +maxErrors = $max_errors; + } + + /** + * @return int + */ + public function maxErrors(): int + { + return $this->maxErrors; + } + + /** + * @param ValidationError $error + * @return ErrorContainer + */ + public function add(ValidationError $error): self + { + $this->errors[] = $error; + return $this; + } + + /** + * @return ValidationError[] + */ + public function all(): array + { + return $this->errors; + } + + /** + * @return ValidationError|null + */ + public function first(): ?ValidationError + { + if (!$this->errors) { + return null; + } + + return reset($this->errors); + } + + /** + * @return bool + */ + public function isFull(): bool + { + return count($this->errors) >= $this->maxErrors; + } + + /** + * @return bool + */ + public function isEmpty(): bool + { + return !$this->errors; + } + + /** + * @inheritDoc + */ + public function count(): int + { + return count($this->errors); + } + + /** + * @inheritDoc + */ + public function current(): ?ValidationError + { + return current($this->errors) ?: null; + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function next(): ?ValidationError + { + return next($this->errors) ?: null; + } + + /** + * @inheritDoc + */ + public function key(): ?int + { + return key($this->errors); + } + + /** + * @inheritDoc + */ + public function valid(): bool + { + return key($this->errors) !== null; + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function rewind(): ?ValidationError + { + return reset($this->errors) ?: null; + } +} diff --git a/admin/vendor/opis/json-schema/src/Errors/ErrorFormatter.php b/admin/vendor/opis/json-schema/src/Errors/ErrorFormatter.php new file mode 100644 index 0000000..8a994e0 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Errors/ErrorFormatter.php @@ -0,0 +1,423 @@ +getErrors($error) as $error => $message) { + $key = $key_formatter($error); + + if ($multiple) { + if (!isset($list[$key])) { + $list[$key] = []; + } + $list[$key][] = $formatter($error, $message); + } else { + if (!isset($list[$key])) { + $list[$key] = $formatter($error, $message); + } + } + } + + return $list; + } + + /** + * @param ValidationError|null $error + * @param string $mode One of: flag, basic, detailed or verbose + * @return array + */ + public function formatOutput(?ValidationError $error, string $mode = "flag"): array + { + if ($error === null) { + return ['valid' => true]; + } + + if ($mode === 'flag') { + return ['valid' => false]; + } + + if ($mode === 'basic') { + return [ + 'valid' => false, + 'errors' => $this->formatFlat($error, [$this, 'formatOutputError']), + ]; + } + + if ($mode === 'detailed' || $mode === 'verbose') { + $isVerbose = $mode === 'verbose'; + + return $this->getNestedErrors($error, function (ValidationError $error, ?array $subErrors = null) use ($isVerbose) { + $info = $this->formatOutputError($error); + + $info['valid'] = false; + + if ($isVerbose) { + $id = $error->schema()->info(); + $id = $id->root() ?? $id->id(); + if ($id) { + $id = rtrim($id, '#'); + } + $info['absoluteKeywordLocation'] = $id . $info['keywordLocation']; + } + + if ($subErrors) { + $info['errors'] = $subErrors; + if (!$isVerbose) { + unset($info['error']); + } + } + + return $info; + } + ); + } + + return ['valid' => false]; + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError,?array):mixed $formatter + * @return mixed + */ + public function formatNested(ValidationError $error, ?callable $formatter = null) + { + if (!$formatter) { + $formatter = function (ValidationError $error, ?array $subErrors = null): array { + $ret = [ + 'message' => $this->formatErrorMessage($error), + 'keyword' => $error->keyword(), + 'path' => $this->formatErrorKey($error), + ]; + + if ($subErrors) { + $ret['errors'] = $subErrors; + } + + return $ret; + }; + } + + return $this->getNestedErrors($error, $formatter); + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError):mixed $formatter + * @return array + */ + public function formatFlat(ValidationError $error, ?callable $formatter = null): array + { + if (!$formatter) { + $formatter = [$this, 'formatErrorMessage']; + } + + $list = []; + + foreach ($this->getFlatErrors($error) as $error) { + $list[] = $formatter($error); + } + + return $list; + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError):mixed $formatter + * @param ?callable(ValidationError):string $key_formatter + * @return array + */ + public function formatKeyed( + ValidationError $error, + ?callable $formatter = null, + ?callable $key_formatter = null + ): array { + if (!$formatter) { + $formatter = [$this, 'formatErrorMessage']; + } + + if (!$key_formatter) { + $key_formatter = [$this, 'formatErrorKey']; + } + + $list = []; + + foreach ($this->getLeafErrors($error) as $error) { + $key = $key_formatter($error); + + if (!isset($list[$key])) { + $list[$key] = []; + } + + $list[$key][] = $formatter($error); + } + + return $list; + } + + /** + * @param ValidationError $error + * @param string|null $message The message to use, if null $error->message() is used + * @return string + */ + public function formatErrorMessage(ValidationError $error, ?string $message = null): string + { + $message ??= $error->message(); + $args = $this->getDefaultArgs($error) + $error->args(); + + if (!$args) { + return $message; + } + + return preg_replace_callback( + '~{([^}]+)}~imu', + static function (array $m) use ($args) { + if (!array_key_exists($m[1], $args)) { + return $m[0]; + } + + $value = $args[$m[1]]; + + if (is_array($value)) { + return implode(', ', $value); + } + + return (string) $value; + }, + $message + ); + } + + public function formatErrorKey(ValidationError $error): string + { + return JsonPointer::pathToString($error->data()->fullPath()); + } + + protected function getDefaultArgs(ValidationError $error): array + { + $data = $error->data(); + $info = $error->schema()->info(); + + $path = $info->path(); + $path[] = $error->keyword(); + + return [ + 'data:type' => $data->type(), + 'data:value' => $data->value(), + 'data:path' => JsonPointer::pathToString($data->fullPath()), + + 'schema:id' => $info->id(), + 'schema:root' => $info->root(), + 'schema:base' => $info->base(), + 'schema:draft' => $info->draft(), + 'schema:keyword' => $error->keyword(), + 'schema:path' => JsonPointer::pathToString($path), + ]; + } + + protected function formatOutputError(ValidationError $error): array + { + $path = $error->schema()->info()->path(); + + $path[] = $error->keyword(); + + return [ + 'keywordLocation' => JsonPointer::pathToFragment($path), + 'instanceLocation' => JsonPointer::pathToFragment($error->data()->fullPath()), + 'error' => $this->formatErrorMessage($error), + ]; + } + + /** + * @param ValidationError $error + * @param callable(ValidationError,?array):mixed $formatter + * @return mixed + */ + protected function getNestedErrors(ValidationError $error, callable $formatter) + { + if ($subErrors = $error->subErrors()) { + foreach ($subErrors as &$subError) { + $subError = $this->getNestedErrors($subError, $formatter); + unset($subError); + } + } + + return $formatter($error, $subErrors); + } + + /** + * @param ValidationError $error + * @return iterable|ValidationError[] + */ + protected function getFlatErrors(ValidationError $error): iterable + { + yield $error; + + foreach ($error->subErrors() as $subError) { + yield from $this->getFlatErrors($subError); + } + } + + /** + * @param ValidationError $error + * @return iterable|ValidationError[] + */ + protected function getLeafErrors(ValidationError $error): iterable + { + if ($subErrors = $error->subErrors()) { + foreach ($subErrors as $subError) { + yield from $this->getLeafErrors($subError); + } + } else { + yield $error; + } + } + + /** + * @param ValidationError $error + * @return iterable + */ + protected function getErrors(ValidationError $error): iterable + { + $data = $error->schema()->info()->data(); + + $map = null; + $pMap = null; + + if (is_object($data)) { + switch ($error->keyword()) { + case 'required': + if (isset($data->{'$error'}->required) && is_object($data->{'$error'}->required)) { + $e = $data->{'$error'}->required; + $found = false; + foreach ($error->args()['missing'] as $prop) { + if (isset($e->{$prop})) { + yield $error => $e->{$prop}; + $found = true; + } + } + if ($found) { + return; + } + if (isset($e->{'*'})) { + yield $error => $e->{'*'}; + return; + } + unset($e, $found, $prop); + } + break; + case '$filters': + if (($args = $error->args()) && isset($args['args']['$error'])) { + yield $error => $args['args']['$error']; + return; + } + unset($args); + break; + } + + if (isset($data->{'$error'})) { + $map = $data->{'$error'}; + + if (is_string($map)) { + // We have an global error + yield $error => $map; + return; + } + + if (is_object($map)) { + if (isset($map->{$error->keyword()})) { + $pMap = $map->{'*'} ?? null; + $map = $map->{$error->keyword()}; + if (is_string($map)) { + yield $error => $map; + return; + } + } elseif (isset($map->{'*'})) { + yield $error => $map->{'*'}; + return; + } + } + } + } + + if (!is_object($map)) { + $map = null; + } + + $subErrors = $error->subErrors(); + + if (!$subErrors) { + yield $error => $pMap ?? $error->message(); + return; + } + + if (!$map) { + foreach ($subErrors as $subError) { + yield from $this->getErrors($subError); + } + return; + } + + foreach ($subErrors as $subError) { + $path = $subError->data()->path(); + if (count($path) !== 1) { + yield from $this->getErrors($subError); + } else { + $path = $path[0]; + if (isset($map->{$path})) { + yield $subError => $map->{$path}; + } elseif (isset($map->{'*'})) { + yield $subError => $map->{'*'}; + } else { + yield from $this->getErrors($subError); + } + } + } + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Errors/ValidationError.php b/admin/vendor/opis/json-schema/src/Errors/ValidationError.php new file mode 100644 index 0000000..f7c11a4 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Errors/ValidationError.php @@ -0,0 +1,96 @@ +keyword = $keyword; + $this->schema = $schema; + $this->data = $data; + $this->message = $message; + $this->args = $args; + $this->subErrors = $subErrors; + } + + public function keyword(): string + { + return $this->keyword; + } + + public function schema(): Schema + { + return $this->schema; + } + + public function data(): DataInfo + { + return $this->data; + } + + public function args(): array + { + return $this->args; + } + + public function message(): string + { + return $this->message; + } + + public function subErrors(): array + { + return $this->subErrors; + } + + public function __toString(): string + { + return $this->message; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php b/admin/vendor/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php new file mode 100644 index 0000000..b943ec8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php @@ -0,0 +1,57 @@ +id = $id; + $this->data = $data; + } + + /** + * @return null|object + */ + public function getData(): ?object + { + return $this->data; + } + + /** + * @return Uri + */ + public function getId(): Uri + { + return $this->id; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/InvalidKeywordException.php b/admin/vendor/opis/json-schema/src/Exceptions/InvalidKeywordException.php new file mode 100644 index 0000000..33a3409 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/InvalidKeywordException.php @@ -0,0 +1,46 @@ +keyword = $keyword; + } + + /** + * @return string + */ + public function keyword(): string + { + return $this->keyword; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/InvalidPragmaException.php b/admin/vendor/opis/json-schema/src/Exceptions/InvalidPragmaException.php new file mode 100644 index 0000000..d3ff204 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/InvalidPragmaException.php @@ -0,0 +1,46 @@ +pragma = $pragma; + } + + /** + * @return string + */ + public function pragma(): string + { + return $this->pragma; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/ParseException.php b/admin/vendor/opis/json-schema/src/Exceptions/ParseException.php new file mode 100644 index 0000000..23e84f9 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/ParseException.php @@ -0,0 +1,45 @@ +info = $info; + } + + /** + * @return SchemaInfo|null + */ + public function schemaInfo(): ?SchemaInfo + { + return $this->info; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/SchemaException.php b/admin/vendor/opis/json-schema/src/Exceptions/SchemaException.php new file mode 100644 index 0000000..feedbcd --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/SchemaException.php @@ -0,0 +1,25 @@ +encoding = $encoding; + } + + /** + * @return string + */ + public function getContentEncoding(): string + { + return $this->encoding; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php new file mode 100644 index 0000000..ef83d98 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php @@ -0,0 +1,44 @@ +media = $media; + } + + /** + * @return string + */ + public function getContentMediaType(): string + { + return $this->media; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedException.php b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedException.php new file mode 100644 index 0000000..fb876cf --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedException.php @@ -0,0 +1,57 @@ +schema = $schema; + $this->context = $context; + } + + /** + * @return Schema + */ + public function getSchema(): Schema + { + return $this->schema; + } + + /** + * @return ValidationContext + */ + public function getContext(): ValidationContext + { + return $this->context; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedFilterException.php b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedFilterException.php new file mode 100644 index 0000000..180693e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedFilterException.php @@ -0,0 +1,57 @@ +filter = $filter; + $this->type = $type; + } + + /** + * @return string + */ + public function getFilter(): string + { + return $this->filter; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php new file mode 100644 index 0000000..5c48bed --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php @@ -0,0 +1,45 @@ +ref = $ref; + } + + /** + * @return string + */ + public function getRef(): string + { + return $this->ref; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filter.php b/admin/vendor/opis/json-schema/src/Filter.php new file mode 100644 index 0000000..1e1b002 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filter.php @@ -0,0 +1,29 @@ +currentData(); + if (!is_string($ref)) { + return false; + } + + $ref = JsonPointer::parse($ref); + if ($ref === null) { + return false; + } + + return $ref->data($context->rootData(), $context->currentDataPath(), $this) !== $this; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/DateTimeFilters.php b/admin/vendor/opis/json-schema/src/Filters/DateTimeFilters.php new file mode 100644 index 0000000..a43eaaa --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/DateTimeFilters.php @@ -0,0 +1,100 @@ += self::CreateDate($min, $tz, false); + } + + public static function MaxDate(string $date, array $args): bool + { + $max = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate($date, $tz, false) <= self::CreateDate($max, $tz, false); + } + + public static function NotDate(string $date, array $args): bool + { + $not = $args['value']; + $tz = $args['timezone'] ?? null; + + if (!is_array($not)) { + $not = [$not]; + } + + $date = self::CreateDate($date, $tz, false); + + foreach ($not as $d) { + if ($date == self::CreateDate($d, $tz, false)) { + return false; + } + } + + return true; + } + + public static function MinDateTime(string $date, array $args): bool + { + $min = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate($date, $tz) >= self::CreateDate($min, $tz); + } + + public static function MaxDateTime(string $date, array $args): bool + { + $max = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate($date, $tz) <= self::CreateDate($max, $tz); + } + + public static function MinTime(string $time, array $args): bool + { + $min = $args['value']; + $prefix = '1970-01-01 '; + + return self::CreateDate($prefix . $time) >= self::CreateDate($prefix . $min); + } + + public static function MaxTime(string $time, array $args): bool + { + $max = $args['value']; + $prefix = '1970-01-01 '; + + return self::CreateDate($prefix . $time) <= self::CreateDate($prefix . $max); + } + + private static function CreateDate(string $value, ?string $timezone = null, bool $time = true): DateTime + { + $date = new DateTime($value, $timezone ? new DateTimeZone($timezone) : null); + if (!$time) { + return $date->setTime(0, 0, 0, 0); + } + return $date; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/FilterExistsFilter.php b/admin/vendor/opis/json-schema/src/Filters/FilterExistsFilter.php new file mode 100644 index 0000000..3b92ade --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/FilterExistsFilter.php @@ -0,0 +1,54 @@ +currentData(); + if (!is_string($filter)) { + return false; + } + + $type = null; + if (isset($args['type'])) { + if (!is_string($args['type'])) { + return false; + } + $type = $args['type']; + } + + $resolver = $context->loader()->parser()->getFilterResolver(); + + if (!$resolver) { + return false; + } + + if ($type === null) { + return (bool)$resolver->resolveAll($filter); + } + + return (bool)$resolver->resolve($filter, $type); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/FormatExistsFilter.php b/admin/vendor/opis/json-schema/src/Filters/FormatExistsFilter.php new file mode 100644 index 0000000..dacd482 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/FormatExistsFilter.php @@ -0,0 +1,54 @@ +currentData(); + if (!is_string($format)) { + return false; + } + + $type = null; + if (isset($args['type'])) { + if (!is_string($args['type'])) { + return false; + } + $type = $args['type']; + } + + $resolver = $context->loader()->parser()->getFormatResolver(); + + if (!$resolver) { + return false; + } + + if ($type === null) { + return (bool)$resolver->resolveAll($format); + } + + return (bool)$resolver->resolve($format, $type); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/GlobalVarExistsFilter.php b/admin/vendor/opis/json-schema/src/Filters/GlobalVarExistsFilter.php new file mode 100644 index 0000000..44aa396 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/GlobalVarExistsFilter.php @@ -0,0 +1,47 @@ +currentData(); + + if (!is_string($var)) { + return false; + } + + $globals = $context->globals(); + + if (!array_key_exists($var, $globals)) { + return false; + } + + if (array_key_exists('value', $args)) { + return $globals[$var] == $args['value']; + } + + return true; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/SchemaExistsFilter.php b/admin/vendor/opis/json-schema/src/Filters/SchemaExistsFilter.php new file mode 100644 index 0000000..4570bcb --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/SchemaExistsFilter.php @@ -0,0 +1,84 @@ +currentData(); + if (!is_string($ref)) { + return false; + } + + if (UriTemplate::isTemplate($ref)) { + if (isset($args['vars']) && is_object($args['vars'])) { + $vars = new VariablesContainer($args['vars'], false); + $vars = $vars->resolve($context->rootData(), $context->currentDataPath()); + if (!is_array($vars)) { + $vars = (array)$vars; + } + $vars += $context->globals(); + } else { + $vars = $context->globals(); + } + + $ref = (new UriTemplate($ref))->resolve($vars); + + unset($vars); + } + + unset($args); + + return $this->refExists($ref, $context, $schema); + } + + /** + * @param string $ref + * @param ValidationContext $context + * @param Schema $schema + * @return bool + */ + protected function refExists(string $ref, ValidationContext $context, Schema $schema): bool + { + if ($ref === '') { + return false; + } + + if ($ref === '#') { + return true; + } + + $info = $schema->info(); + + $id = Uri::merge($ref, $info->idBaseRoot(), true); + + if ($id === null) { + return false; + } + + return $context->loader()->loadSchemaById($id) !== null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Filters/SlotExistsFilter.php b/admin/vendor/opis/json-schema/src/Filters/SlotExistsFilter.php new file mode 100644 index 0000000..b703bab --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Filters/SlotExistsFilter.php @@ -0,0 +1,36 @@ +currentData(); + if (!is_string($slot)) { + return false; + } + + return $context->slot($slot) !== null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Format.php b/admin/vendor/opis/json-schema/src/Format.php new file mode 100644 index 0000000..577a534 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Format.php @@ -0,0 +1,27 @@ +.+)@(?.+)$/u', $value, $m)) { + return false; + } + + $m['name'] = $idn($m['name']); + if ($m['name'] === null) { + return false; + } + + $m['domain'] = $idn($m['domain']); + if ($m['domain'] === null) { + return false; + } + + $value = $m['name'] . '@' . $m['domain']; + } + + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * @return callable|null + */ + public static function idn(): ?callable + { + if (static::$idn === false) { + if (function_exists('idn_to_ascii')) { + static::$idn = static function (string $value): ?string { + /** @noinspection PhpComposerExtensionStubsInspection */ + $value = idn_to_ascii($value, 0, INTL_IDNA_VARIANT_UTS46); + + return is_string($value) ? $value : null; + }; + } else { + static::$idn = null; + } + } + + return static::$idn; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Formats/MiscFormats.php b/admin/vendor/opis/json-schema/src/Formats/MiscFormats.php new file mode 100644 index 0000000..a7e2af5 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Formats/MiscFormats.php @@ -0,0 +1,59 @@ +isAbsolute(); + } + + /** + * @param string $value + * @return bool + */ + public static function uriReference(string $value): bool + { + if ($value === '') { + return true; + } + + return Uri::parse($value) !== null; + } + + /** + * @param string $value + * @return bool + */ + public static function uriTemplate(string $value): bool + { + if ($value === '') { + return true; + } + + if (UriTemplate::isTemplate($value)) { + return true; + } + + return Uri::parse($value) !== null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Helper.php b/admin/vendor/opis/json-schema/src/Helper.php new file mode 100644 index 0000000..45bdf40 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Helper.php @@ -0,0 +1,392 @@ + 'number']; + + /** @var string[] */ + protected const PHP_TYPE_MAP = [ + 'NULL' => 'null', + 'integer' => 'integer', + 'double' => 'number', + 'boolean' => 'boolean', + 'array' => 'array', + 'object' => 'object', + 'string' => 'string', + ]; + + /** + * @param string $type + * @return bool + */ + public static function isValidJsonType(string $type): bool + { + if (isset(self::JSON_SUBTYPES[$type])) { + return true; + } + + return in_array($type, self::JSON_TYPES, true); + } + + /** + * @param string $type + * @return null|string + */ + public static function getJsonSuperType(string $type): ?string + { + return self::JSON_SUBTYPES[$type] ?? null; + } + + /** + * @param mixed $value + * @param bool $use_subtypes + * @return null|string + */ + public static function getJsonType($value, bool $use_subtypes = true): ?string + { + $type = self::PHP_TYPE_MAP[gettype($value)] ?? null; + if ($type === null) { + return null; + } elseif ($type === 'array') { + return self::isIndexedArray($value) ? 'array' : null; + } + + if ($use_subtypes) { + if ($type === 'number' && self::isMultipleOf($value, 1)) { + return 'integer'; + } + } elseif ($type === 'integer') { + return 'number'; + } + + return $type; + } + + /** + * @param string $type + * @param string|string[] $allowed + * @return bool + */ + public static function jsonTypeMatches(string $type, $allowed): bool + { + if (!$allowed) { + return false; + } + + if (is_string($allowed)) { + if ($type === $allowed) { + return true; + } + + return $allowed === self::getJsonSuperType($type); + } + + if (is_array($allowed)) { + if (in_array($type, $allowed, true)) { + return true; + } + + if ($type = self::getJsonSuperType($type)) { + return in_array($type, $allowed, true); + } + } + + return false; + } + + /** + * @param mixed $value + * @param string|string[] $type + * @return bool + */ + public static function valueIsOfJsonType($value, $type): bool + { + $t = self::getJsonType($value); + if ($t === null) { + return false; + } + + return self::jsonTypeMatches($t, $type); + } + + /** + * @param array $array + * @return bool + */ + public static function isIndexedArray(array $array): bool + { + for ($i = 0, $max = count($array); $i < $max; $i++) { + if (!array_key_exists($i, $array)) { + return false; + } + } + + return true; + } + + /** + * Converts assoc-arrays to objects (recursive) + * @param scalar|object|array|null $schema + * @return scalar|object|array|null + */ + public static function convertAssocArrayToObject($schema) + { + if (is_null($schema) || is_scalar($schema)) { + return $schema; + } + + $keepArray = is_array($schema) && self::isIndexedArray($schema); + + $data = []; + + foreach ($schema as $key => $value) { + $data[$key] = is_array($value) || is_object($value) ? self::convertAssocArrayToObject($value) : $value; + } + + return $keepArray ? $data : (object) $data; + } + + /** + * @param mixed $a + * @param mixed $b + * @return bool + */ + public static function equals($a, $b): bool + { + if ($a === $b) { + return true; + } + + $type = self::getJsonType($a, false); + if ($type === null || $type !== self::getJsonType($b, false)) { + return false; + } + + if ($type === 'number') { + return $a == $b; + } + + if ($type === "array") { + $count = count($a); + if ($count !== count($b)) { + return false; + } + + for ($i = 0; $i < $count; $i++) { + if (!array_key_exists($i, $a) || !array_key_exists($i, $b)) { + return false; + } + if (!self::equals($a[$i], $b[$i])) { + return false; + } + } + + return true; + } + + if ($type === "object") { + $a = get_object_vars($a); + if ($a === null) { + return false; + } + + $b = get_object_vars($b); + if ($b === null) { + return false; + } + + if (count($a) !== count($b)) { + return false; + } + + foreach ($a as $prop => $value) { + if (!array_key_exists($prop, $b)) { + return false; + } + if (!self::equals($value, $b[$prop])) { + return false; + } + } + + return true; + } + + return false; + } + + + /** + * @var bool|null True if bcmath extension is available + */ + private static ?bool $hasBCMath = null; + + /** + * @var bool True to use bcmath + */ + public static bool $useBCMath = true; + + /** + * @var int Number scale to used when using comparisons + */ + public static int $numberScale = 14; + + /** + * @param $number + * @param $divisor + * @param int|null $scale + * @return bool + */ + public static function isMultipleOf($number, $divisor, ?int $scale = null): bool + { + if (!$number || $number == $divisor) { + return true; + } + + if ($divisor == 0) { + return $number == 0; + } + + // maybe we get lucky + if (!fmod($number, $divisor)) { + return true; + } + + $divisor = abs($divisor); + if ($divisor == 1) { + // should have been caught by the above fmod() + return false; + } + + $number = abs($number); + if ($divisor > $number) { + // number / divisor < 1 + return false; + } + + // Use global scale if null + $scale ??= self::$numberScale; + + if ( + !self::$useBCMath || + !(self::$hasBCMath ??= extension_loaded('bcmath')) + ) { + // use an approximation + return abs($number - round($number / $divisor) * $divisor) < (10 ** -$scale); + } + + // use bcmath + + $number = number_format($number, $scale, '.', ''); + $divisor = number_format($divisor, $scale, '.', ''); + + // number can be zero after formatting + if (!(float)$divisor) { + return $number === $divisor; + } + + $x = bcdiv($number, $divisor, 0); + $x = bcmul($divisor, $x, $scale); + $x = bcsub($number, $x, $scale); + + return 0 === bccomp($x, 0, $scale); + } + + /** + * @param $value + * @return mixed + */ + public static function cloneValue($value) + { + if ($value === null || is_scalar($value)) { + return $value; + } + + if (is_array($value)) { + return array_map(self::class . '::cloneValue', $value); + } + + if (is_object($value)) { + return (object)array_map(self::class . '::cloneValue', get_object_vars($value)); + } + + return null; + } + + /** + * @param string $pattern + * @return bool + */ + public static function isValidPattern(string $pattern): bool + { + if (strpos($pattern, '\Z') !== false) { + return false; + } + + return @preg_match("\x07{$pattern}\x07u", '') !== false; + } + + /** + * @param string $pattern + * @return string + */ + public static function patternToRegex(string $pattern): string + { + return "\x07{$pattern}\x07uD"; + } + + /** + * @param mixed $data + * @return mixed + */ + public static function toJSON($data) + { + if ($data === null || is_scalar($data)) { + return $data; + } + + $map = []; + + $isArray = true; + $index = 0; + foreach ($data as $key => $value) { + $map[$key] = self::toJSON($value); + if ($isArray) { + if ($index !== $key) { + $isArray = false; + } else { + $index++; + } + } + } + + if ($isArray) { + if (!$map && is_object($data)) { + return (object) $map; + } + return $map; + } + + return (object) $map; + } +} diff --git a/admin/vendor/opis/json-schema/src/Info/DataInfo.php b/admin/vendor/opis/json-schema/src/Info/DataInfo.php new file mode 100644 index 0000000..57dc8a7 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Info/DataInfo.php @@ -0,0 +1,114 @@ +value = $value; + $this->type = $type; + $this->root = $root; + $this->path = $path; + $this->parent = $parent; + } + + public function value() + { + return $this->value; + } + + public function type(): ?string + { + return $this->type; + } + + public function root() + { + return $this->root; + } + + /** + * @return int[]|string[] + */ + public function path(): array + { + return $this->path; + } + + public function parent(): ?DataInfo + { + return $this->parent; + } + + /** + * @return int[]|string[] + */ + public function fullPath(): array + { + if ($this->parent === null) { + return $this->path; + } + + if ($this->fullPath === null) { + $this->fullPath = array_merge($this->parent->fullPath(), $this->path); + } + + return $this->fullPath; + } + + /** + * @param ValidationContext $context + * @return static + */ + public static function fromContext(ValidationContext $context): self + { + if ($parent = $context->parent()) { + $parent = self::fromContext($parent); + } + + return new self($context->currentData(), $context->currentDataType(), $context->rootData(), + $context->currentDataPath(), $parent); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Info/SchemaInfo.php b/admin/vendor/opis/json-schema/src/Info/SchemaInfo.php new file mode 100644 index 0000000..77b2d05 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Info/SchemaInfo.php @@ -0,0 +1,117 @@ +data = $data; + $this->id = $id; + $this->root = $root; + $this->base = $base; + $this->path = $path; + $this->draft = $draft; + } + + public function id(): ?Uri + { + return $this->id; + } + + public function root(): ?Uri + { + return $this->root; + } + + public function base(): ?Uri + { + return $this->base; + } + + public function draft(): ?string + { + return $this->draft; + } + + public function data() + { + return $this->data; + } + + public function path(): array + { + return $this->path; + } + + /** + * Returns first non-null property: id, base or root + * @return Uri|null + */ + public function idBaseRoot(): ?Uri + { + return $this->id ?? $this->base ?? $this->root; + } + + public function isBoolean(): bool + { + return is_bool($this->data); + } + + public function isObject(): bool + { + return is_object($this->data); + } + + public function isDocumentRoot(): bool + { + return $this->id && !$this->root && !$this->base; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/JsonPointer.php b/admin/vendor/opis/json-schema/src/JsonPointer.php new file mode 100644 index 0000000..22a6127 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/JsonPointer.php @@ -0,0 +1,410 @@ +0|[1-9][0-9]*)(?(?:\+|-)(?:0|[1-9][0-9]*))?)?(?(?:/[^/#]*)*)(?#)?$~'; + + /** @var string */ + protected const UNESCAPED = '/~([^01]|$)/'; + + protected int $level = -1; + + protected int $shift = 0; + + protected bool $fragment = false; + + /** @var string[]|int[] */ + protected array $path; + + protected ?string $str = null; + + final protected function __construct(array $path, int $level = -1, int $shift = 0, bool $fragment = false) + { + $this->path = $path; + $this->level = $level < 0 ? -1 : $level; + $this->shift = $shift; + $this->fragment = $level >= 0 && $fragment; + } + + public function isRelative(): bool + { + return $this->level >= 0; + } + + public function isAbsolute(): bool + { + return $this->level < 0; + } + + public function level(): int + { + return $this->level; + } + + public function shift(): int + { + return $this->shift; + } + + /** + * @return string[] + */ + public function path(): array + { + return $this->path; + } + + /** + * @return bool + */ + public function hasFragment(): bool + { + return $this->fragment; + } + + /** + * @return string + */ + public function __toString(): string + { + if ($this->str === null) { + if ($this->level >= 0) { + $this->str = (string)$this->level; + + if ($this->shift !== 0) { + if ($this->shift > 0) { + $this->str .= '+'; + } + $this->str .= $this->shift; + } + + if ($this->path) { + $this->str .= '/'; + $this->str .= implode('/', self::encodePath($this->path)); + } + + if ($this->fragment) { + $this->str .= '#'; + } + } else { + $this->str = '/'; + $this->str .= implode('/', self::encodePath($this->path)); + } + } + + return $this->str; + } + + /** + * @param $data + * @param array|null $path + * @param mixed $default + * @return mixed + */ + public function data($data, ?array $path = null, $default = null) + { + if ($this->level < 0) { + return self::getData($data, $this->path, false, $default); + } + + if ($path !== null) { + $path = $this->absolutePath($path); + } + + if ($path === null) { + return $default; + } + + return self::getData($data, $path, $this->fragment, $default); + } + + /** + * @param array $path + * @return array|null + */ + public function absolutePath(array $path = []): ?array + { + if ($this->level < 0) { + // Absolute pointer + return $this->path; + } + + if ($this->level === 0) { + if ($this->shift && !$this->handleShift($path)) { + return null; + } + return $this->path ? array_merge($path, $this->path) : $path; + } + + $count = count($path); + if ($count === $this->level) { + if ($this->shift) { + return null; + } + return $this->path; + } + + if ($count > $this->level) { + $count -= $this->level; + + /** @var array $path */ + $path = array_slice($path, 0, $count); + + if ($this->shift && !$this->handleShift($path, $count)) { + return null; + } + + return $this->path ? array_merge($path, $this->path) : $path; + } + + return null; + } + + protected function handleShift(array &$path, ?int $count = null): bool + { + if (!$path) { + return false; + } + + $count ??= count($path); + + $last = $path[$count - 1]; + + if (is_string($last) && preg_match('/^[1-9]\d*$/', $last)) { + $last = (int) $last; + } + + if (!is_int($last)) { + return false; + } + + $path[$count - 1] = $last + $this->shift; + + return true; + } + + public static function parse(string $pointer, bool $decode = true): ?self + { + if ($pointer === '' || !preg_match(self::PATTERN, $pointer, $m)) { + // Not a pointer + return null; + } + + $pointer = $m['pointer'] ?? null; + + // Check if the pointer is escaped + if ($decode && $pointer && preg_match(self::UNESCAPED, $pointer)) { + // Invalid pointer + return null; + } + + $level = isset($m['level']) && $m['level'] !== '' + ? (int)$m['level'] + : -1; + + $shift = 0; + if ($level >= 0 && isset($m['shift']) && $m['shift'] !== '') { + $shift = (int) $m['shift']; + } + + $fragment = isset($m['fragment']) && $m['fragment'] === '#'; + unset($m); + + if ($fragment && $level < 0) { + return null; + } + + if ($pointer === '') { + $pointer = null; + } elseif ($pointer !== null) { + // Remove leading slash + $pointer = substr($pointer, 1); + + if ($pointer !== '') { + $pointer = self::decodePath(explode('/', $pointer)); + } else { + $pointer = null; + } + } + + return new self($pointer ?? [], $level, $shift, $fragment); + } + + /** + * @param $data + * @param array|null $path + * @param bool $fragment + * @param mixed $default + * @return mixed + */ + public static function getData($data, ?array $path = null, bool $fragment = false, $default = null) + { + if ($path === null) { + return $default; + } + + if (!$path) { + return $fragment ? $default : $data; + } + + if ($fragment) { + return end($path); + } + + foreach ($path as $key) { + if (is_array($data)) { + if (!array_key_exists($key, $data)) { + return $default; + } + $data = $data[$key]; + } elseif (is_object($data)) { + if (!property_exists($data, $key)) { + return $default; + } + $data = $data->{$key}; + } else { + return $default; + } + } + + return $data; + } + + /** + * @param string|string[] $path + * @return string|string[] + */ + public static function encodePath($path) + { + $path = str_replace('~', '~0', $path); + $path = str_replace('/', '~1', $path); + + if (is_array($path)) { + return array_map('rawurlencode', $path); + } + + return rawurlencode($path); + } + + /** + * @param string|string[] $path + * @return string|string[] + */ + public static function decodePath($path) + { + if (is_array($path)) { + $path = array_map('rawurldecode', $path); + } else { + $path = rawurldecode($path); + } + + $path = str_replace('~1', '/', $path); + $path = str_replace('~0', '~', $path); + + return $path; + } + + /** + * @param array $path + * @return string + */ + public static function pathToString(array $path): string + { + if (!$path) { + return '/'; + } + + return '/' . implode('/', self::encodePath($path)); + } + + /** + * @param array $path + * @return string + */ + public static function pathToFragment(array $path): string + { + if (!$path) { + return '#'; + } + + return '#/' . implode('/', self::encodePath($path)); + } + + /** + * @param string $pointer + * @return bool + */ + public static function isAbsolutePointer(string $pointer): bool + { + if ($pointer === '/') { + return true; + } + + if (!preg_match(self::PATTERN, $pointer, $m)) { + return false; + } + + if (isset($m['fragment']) || isset($m['level']) && $m['level'] !== '') { + return false; + } + + if (!isset($m['pointer']) || $m['pointer'] === '') { + return true; + } + + return !preg_match(self::UNESCAPED, $m['pointer']); + } + + /** + * @param string $pointer + * @return bool + */ + public static function isRelativePointer(string $pointer): bool + { + if ($pointer === '') { + return false; + } + + if (!preg_match(self::PATTERN, $pointer, $m)) { + return false; + } + + if (!isset($m['level']) || $m['level'] === '' || (int)$m['level'] < 0) { + return false; + } + + if (!isset($m['pointer']) || $m['pointer'] === '') { + return true; + } + + return !preg_match(self::UNESCAPED, $m['pointer']); + } + + public static function createAbsolute(array $path): self + { + return new self($path, -1, 0, false); + } + + public static function createRelative(int $level, array $path = [], int $shift = 0, bool $fragment = false): self + { + return new self($path, $level, $shift, $fragment); + } +} diff --git a/admin/vendor/opis/json-schema/src/Keyword.php b/admin/vendor/opis/json-schema/src/Keyword.php new file mode 100644 index 0000000..d10c903 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keyword.php @@ -0,0 +1,30 @@ +next; + } + + /** + * @inheritDoc + */ + public function setNext(?KeywordValidator $next): KeywordValidator + { + $this->next = $next; + + return $this; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php b/admin/vendor/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php new file mode 100644 index 0000000..2cb7f59 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php @@ -0,0 +1,59 @@ +callback = $callback; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + return ($this->callback)($context); + } + + /** + * @inheritDoc + */ + public function next(): ?KeywordValidator + { + return null; + } + + /** + * @inheritDoc + */ + public function setNext(?KeywordValidator $next): KeywordValidator + { + return $this; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php b/admin/vendor/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php new file mode 100644 index 0000000..7913b06 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php @@ -0,0 +1,63 @@ +pragmas = $pragmas; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + if (!$this->next) { + return null; + } + + if (!$this->pragmas) { + return $this->next->validate($context); + } + + $data = []; + + foreach ($this->pragmas as $key => $handler) { + $data[$key] = $handler->enter($context); + } + + $error = $this->next->validate($context); + + foreach (array_reverse($this->pragmas, true) as $key => $handler) { + $handler->leave($context, $data[$key] ?? null); + } + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/AbstractRefKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/AbstractRefKeyword.php new file mode 100644 index 0000000..4c8e7be --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/AbstractRefKeyword.php @@ -0,0 +1,129 @@ +mapper = $mapper; + $this->globals = $globals; + $this->slots = $slots; + $this->keyword = $keyword; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($error = $this->doValidate($context, $schema)) { + $uri = $this->lastRefUri; + $this->lastRefUri = null; + + return $this->error($schema, $context, $this->keyword, 'The data must match {keyword}', [ + 'keyword' => $this->keyword, + 'uri' => (string) $uri, + ], $error); + } + + $this->lastRefUri = null; + + return null; + } + + + abstract protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError; + + protected function setLastRefUri(?Uri $uri): void + { + $this->lastRefUri = $uri; + } + + protected function setLastRefSchema(Schema $schema): void + { + $info = $schema->info(); + + if ($info->id()) { + $this->lastRefUri = $info->id(); + } else { + $this->lastRefUri = Uri::merge(JsonPointer::pathToFragment($info->path()), $info->idBaseRoot()); + } + } + + /** + * @param ValidationContext $context + * @param Schema $schema + * @return ValidationContext + */ + protected function createContext(ValidationContext $context, Schema $schema): ValidationContext + { + return $context->create($schema, $this->mapper, $this->globals, $this->slots); + } + + /** + * @param SchemaLoader $repo + * @param JsonPointer $pointer + * @param Uri $base + * @param array|null $path + * @return null|Schema + */ + protected function resolvePointer(SchemaLoader $repo, JsonPointer $pointer, + Uri $base, ?array $path = null): ?Schema + { + if ($pointer->isAbsolute()) { + $path = (string)$pointer; + } else { + if ($pointer->hasFragment()) { + return null; + } + + $path = $path ? $pointer->absolutePath($path) : $pointer->path(); + if ($path === null) { + return null; + } + + $path = JsonPointer::pathToString($path); + } + + return $repo->loadSchemaById(Uri::merge('#' . $path, $base)); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php new file mode 100644 index 0000000..ae0910a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php @@ -0,0 +1,97 @@ +value = $value; + $this->index = $startIndex; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->value === true) { + $context->markAllAsEvaluatedItems(); + return null; + } + + $data = $context->currentData(); + $count = count($data); + + if ($this->index >= $count) { + return null; + } + + if ($this->value === false) { + return $this->error($schema, $context, 'additionalItems', 'Array should not have additional items', [ + 'index' => $this->index, + ]); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $object = $this->createArrayObject($context); + + $error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->index, $count), + 'additionalItems', 'All additional array items must match schema', [], $object); + + if ($object && $object->count()) { + $context->addEvaluatedItems($object->getArrayCopy()); + } + + return $error; + } + + /** + * @param int $start + * @param int $max + * @return iterable|int[] + */ + protected function indexes(int $start, int $max): iterable + { + for ($i = $start; $i < $max; $i++) { + yield $i; + } + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php new file mode 100644 index 0000000..b3ac59f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php @@ -0,0 +1,83 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->value === true) { + $context->markAllAsEvaluatedProperties(); + return null; + } + + $props = $context->getUncheckedProperties(); + + if (!$props) { + return null; + } + + if ($this->value === false) { + return $this->error($schema, $context, + 'additionalProperties', 'Additional object properties are not allowed: {properties}', [ + 'properties' => $props + ]); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $object = $this->createArrayObject($context); + + $error = $this->validateIterableData($schema, $this->value, $context, $props, + 'additionalProperties', 'All additional object properties must match schema: {properties}', [ + 'properties' => $props + ], $object); + + if ($object && $object->count()) { + $context->addEvaluatedProperties($object->getArrayCopy()); + } + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/AllOfKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/AllOfKeyword.php new file mode 100644 index 0000000..cbc9c61 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/AllOfKeyword.php @@ -0,0 +1,78 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $object = $this->createArrayObject($context); + + foreach ($this->value as $index => $value) { + if ($value === true) { + continue; + } + + if ($value === false) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [ + 'index' => $index, + ]); + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); + } + + if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [ + 'index' => $index, + ], $error); + } + } + + $this->addEvaluatedFromArrayObject($object, $context); + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/AnyOfKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/AnyOfKeyword.php new file mode 100644 index 0000000..1a7f325 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/AnyOfKeyword.php @@ -0,0 +1,94 @@ +value = $value; + $this->alwaysValid = $alwaysValid; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $object = $this->createArrayObject($context); + if ($this->alwaysValid && !$object) { + return null; + } + + $errors = []; + $ok = false; + + foreach ($this->value as $index => $value) { + if ($value === true) { + $ok = true; + if ($object) { + continue; + } + return null; + } + + if ($value === false) { + continue; + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); + } + + if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { + $errors[] = $error; + continue; + } + + if (!$object) { + return null; + } + $ok = true; + } + + $this->addEvaluatedFromArrayObject($object, $context); + + if ($ok) { + return null; + } + + return $this->error($schema, $context, 'anyOf', 'The data should match at least one schema', [], $errors); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ConstDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ConstDataKeyword.php new file mode 100644 index 0000000..4f5af4e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ConstDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(null); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + if ($value === $this) { + return $this->error($schema, $context, 'const', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->const = $value; + $ret = parent::validate($context, $schema); + $this->const = null; + + return $ret; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ConstKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ConstKeyword.php new file mode 100644 index 0000000..3dde51e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ConstKeyword.php @@ -0,0 +1,51 @@ +const = $const; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (Helper::equals($this->const, $context->currentData())) { + return null; + } + + return $this->error($schema, $context, 'const', 'The data must match the const value', [ + 'const' => $this->const + ]); + } +} diff --git a/admin/vendor/opis/json-schema/src/Keywords/ContainsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ContainsKeyword.php new file mode 100644 index 0000000..8d0ee95 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ContainsKeyword.php @@ -0,0 +1,143 @@ +value = $value; + $this->min = $min; + $this->max = $max; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + $count = count($data); + + $context->markAllAsEvaluatedItems(); + + if ($this->min > $count) { + return $this->error($schema, $context, 'minContains', 'Array must have at least {min} items', [ + 'min' => $this->min, + 'count' => $count, + ]); + } + + $isMaxNull = $this->max === null; + + if ($this->value === true) { + if ($count) { + if (!$isMaxNull && $count > $this->max) { + return $this->error($schema, $context, 'maxContains', 'Array must have at most {max} items', [ + 'max' => $this->max, + 'count' => $count, + ]); + } + return null; + } + + return $this->error($schema, $context, 'contains', 'Array must not be empty'); + } + + if ($this->value === false) { + return $this->error($schema, $context, 'contains', 'Any array is invalid'); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $errors = []; + $valid = 0; + + $isMinNull = $this->min === null; + + if ($isMaxNull && $isMinNull) { + foreach ($data as $key => $item) { + $context->pushDataPath($key); + $error = $this->value->validate($context); + $context->popDataPath(); + if ($error) { + $errors[] = $error; + } else { + return null; + } + } + + return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [], + $errors); + } + + foreach ($data as $key => $item) { + $context->pushDataPath($key); + $error = $this->value->validate($context); + $context->popDataPath(); + + if ($error) { + $errors[] = $error; + } else { + $valid++; + } + } + + if (!$isMinNull && $valid < $this->min) { + return $this->error($schema, $context, 'minContains', 'At least {min} array items must match schema', [ + 'min' => $this->min, + 'count' => $valid, + ]); + } + + if (!$isMaxNull && $valid > $this->max) { + return $this->error($schema, $context, 'maxContains', 'At most {max} array items must match schema', [ + 'max' => $this->max, + 'count' => $valid, + ]); + } + + if ($valid) { + return null; + } + + return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [], + $errors); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ContentEncodingKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ContentEncodingKeyword.php new file mode 100644 index 0000000..54ff41a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ContentEncodingKeyword.php @@ -0,0 +1,77 @@ +name = $name; + $this->resolver = $resolver; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (!$this->resolver) { + return null; + } + + if ($this->encoding === false) { + $this->encoding = $this->resolver->resolve($this->name); + } + + if ($this->encoding === null) { + throw new UnresolvedContentEncodingException($this->name, $schema, $context); + } + + $result = $this->encoding instanceof ContentEncoding + ? $this->encoding->decode($context->currentData(), $this->name) + : ($this->encoding)($context->currentData(), $this->name); + + if ($result === null) { + return $this->error($schema, $context, 'contentEncoding', "The value must be encoded as '{encoding}'", [ + 'encoding' => $this->name, + ]); + } + + $context->setDecodedContent($result); + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php new file mode 100644 index 0000000..358ebdd --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php @@ -0,0 +1,83 @@ +name = $name; + $this->resolver = $resolver; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (!$this->resolver) { + return null; + } + + if ($this->media === false) { + $this->media = $this->resolver->resolve($this->name); + } + + if ($this->media === null) { + throw new UnresolvedContentMediaTypeException($this->name, $schema, $context); + } + + $data = $context->getDecodedContent(); + + $ok = $this->media instanceof ContentMediaType + ? $this->media->validate($data, $this->name) + : ($this->media)($data, $this->name); + if ($ok) { + return null; + } + + unset($data); + + return $this->error($schema, $context, 'contentMediaType', "The media type of the data must be '{media}'", [ + 'media' => $this->name, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ContentSchemaKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ContentSchemaKeyword.php new file mode 100644 index 0000000..01b3cc7 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ContentSchemaKeyword.php @@ -0,0 +1,64 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = json_decode($context->getDecodedContent(), false); + + if ($error = json_last_error() !== JSON_ERROR_NONE) { + $message = json_last_error_msg(); + + return $this->error($schema, $context, 'contentSchema', "Invalid JSON content: {message}", [ + 'error' => $error, + 'message' => $message, + ]); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + if ($error = $this->value->validate($context->newInstance($data, $schema))) { + return $this->error($schema, $context, 'contentSchema', "The JSON content must match schema", [], $error); + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/DefaultKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/DefaultKeyword.php new file mode 100644 index 0000000..afaa727 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/DefaultKeyword.php @@ -0,0 +1,53 @@ +defaults = $defaults; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + + if (is_object($data)) { + foreach ($this->defaults as $name => $value) { + if (!property_exists($data, $name)) { + $data->{$name} = Helper::cloneValue($value); + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/DependenciesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/DependenciesKeyword.php new file mode 100644 index 0000000..15216aa --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/DependenciesKeyword.php @@ -0,0 +1,95 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + $object = $this->createArrayObject($context); + + foreach ($this->value as $name => $value) { + if ($value === true || !property_exists($data, $name)) { + continue; + } + + if ($value === false) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'dependencies', "Property '{property}' is not allowed", [ + 'property' => $name, + ]); + } + + if (is_array($value)) { + foreach ($value as $prop) { + if (!property_exists($data, $prop)) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'dependencies', + "Property '{missing}' property is required by property '{property}'", [ + 'property' => $name, + 'missing' => $prop, + ]); + } + } + + continue; + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$name] = $context->loader()->loadObjectSchema($value); + } + + if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'dependencies', + "The object must match dependency schema defined on property '{property}'", [ + 'property' => $name, + ], $error); + } + } + + $this->addEvaluatedFromArrayObject($object, $context); + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/DependentRequiredKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/DependentRequiredKeyword.php new file mode 100644 index 0000000..90b6188 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/DependentRequiredKeyword.php @@ -0,0 +1,66 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + + foreach ($this->value as $name => $value) { + if (!property_exists($data, $name)) { + continue; + } + foreach ($value as $prop) { + if (!property_exists($data, $prop)) { + return $this->error($schema, $context, 'dependentRequired', + "'{$prop}' property is required by '{$name}' property", [ + 'property' => $name, + 'missing' => $prop, + ]); + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/DependentSchemasKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/DependentSchemasKeyword.php new file mode 100644 index 0000000..1714bb1 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/DependentSchemasKeyword.php @@ -0,0 +1,76 @@ +value = (array)$value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + $object = $this->createArrayObject($context); + + foreach ($this->value as $name => $value) { + if ($value === true || !property_exists($data, $name)) { + continue; + } + + if ($value === false) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'dependentSchemas', "'{$name}' property is not allowed", [ + 'property' => $name, + ]); + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$name] = $context->loader()->loadObjectSchema($value); + } + + if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'dependentSchemas', + "The object must match dependency schema defined on property '{$name}'", [ + 'property' => $name, + ], $error); + } + } + + $this->addEvaluatedFromArrayObject($object, $context); + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/EnumDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/EnumDataKeyword.php new file mode 100644 index 0000000..6e2707e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/EnumDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct([]); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + if ($value === $this || !is_array($value) || empty($value)) { + return $this->error($schema, $context, 'enum', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->enum = $this->listByType($value); + $ret = parent::validate($context, $schema); + $this->enum = null; + + return $ret; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/EnumKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/EnumKeyword.php new file mode 100644 index 0000000..92b0a0d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/EnumKeyword.php @@ -0,0 +1,79 @@ +enum = $this->listByType($enum); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $type = $context->currentDataType(); + $data = $context->currentData(); + + if (isset($this->enum[$type])) { + foreach ($this->enum[$type] as $value) { + if (Helper::equals($value, $data)) { + return null; + } + } + } + + return $this->error($schema, $context, 'enum', 'The data should match one item from enum'); + } + + /** + * @param array $values + * @return array + */ + protected function listByType(array $values): array + { + $list = []; + + foreach ($values as $value) { + $type = Helper::getJsonType($value); + if (!isset($list[$type])) { + $list[$type] = []; + } + $list[$type][] = $value; + } + + return $list; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ErrorTrait.php b/admin/vendor/opis/json-schema/src/Keywords/ErrorTrait.php new file mode 100644 index 0000000..8d78360 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ErrorTrait.php @@ -0,0 +1,55 @@ +all(); + } + } + + return new ValidationError($keyword, $schema, DataInfo::fromContext($context), $message, $args, + is_array($errors) ? $errors : []); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php new file mode 100644 index 0000000..056c6f8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var float|int $number */ + $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($number === $this || !(is_float($number) || is_int($number))) { + return $this->error($schema, $context, 'exclusiveMaximum', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->number = $number; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php new file mode 100644 index 0000000..66ecd61 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php @@ -0,0 +1,50 @@ +number = $number; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($context->currentData() < $this->number) { + return null; + } + + return $this->error($schema, $context, 'exclusiveMaximum', "Number must be lower than {max}", [ + 'max' => $this->number, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php new file mode 100644 index 0000000..92635ae --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var float|int $number */ + $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($number === $this || !(is_float($number) || is_int($number))) { + return $this->error($schema, $context, 'exclusiveMinimum', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->number = $number; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php new file mode 100644 index 0000000..90a85f5 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php @@ -0,0 +1,50 @@ +number = $number; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($context->currentData() > $this->number) { + return null; + } + + return $this->error($schema, $context, 'exclusiveMinimum', "Number must be greater than {min}", [ + 'min' => $this->number, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/FiltersKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/FiltersKeyword.php new file mode 100644 index 0000000..e85b961 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/FiltersKeyword.php @@ -0,0 +1,93 @@ +filters = $filters; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $type = $context->currentDataType(); + + foreach ($this->filters as $filter) { + if (!isset($filter->types[$type])) { + throw new UnresolvedFilterException($filter->name, $type, $schema, $context); + } + + $func = $filter->types[$type]; + + if ($filter->args) { + $args = (array)$filter->args->resolve($context->rootData(), $context->currentDataPath()); + $args += $context->globals(); + } else { + $args = $context->globals(); + } + + try { + if ($func instanceof Filter) { + $ok = $func->validate($context, $schema, $args); + } else { + $ok = $func($context->currentData(), $args); + } + } catch (CustomError $error) { + return $this->error($schema, $context, '$filters', $error->getMessage(), $error->getArgs() + [ + 'filter' => $filter->name, + 'type' => $type, + 'args' => $args, + ]); + } + + if ($ok) { + unset($func, $args, $ok); + continue; + } + + return $this->error($schema, $context, '$filters', "Filter '{filter}' ({type}) was not passed", [ + 'filter' => $filter->name, + 'type' => $type, + 'args' => $args, + ]); + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/FormatDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/FormatDataKeyword.php new file mode 100644 index 0000000..105baa2 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/FormatDataKeyword.php @@ -0,0 +1,76 @@ +value = $value; + $this->resolver = $resolver; + parent::__construct('', []); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + if ($value === $this || !is_string($value)) { + return $this->error($schema, $context, 'format', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + /** @var string $value */ + + $type = $context->currentDataType(); + + $types = [ + $type => $this->resolver->resolve($value, $type), + ]; + + if (!$types[$type] && ($super = Helper::getJsonSuperType($type))) { + $types[$super] = $this->resolver->resolve($value, $super); + unset($super); + } + + unset($type); + + $this->name = $value; + $this->types = $types; + $ret = parent::validate($context, $schema); + $this->name = $this->types = null; + + return $ret; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/FormatKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/FormatKeyword.php new file mode 100644 index 0000000..3693809 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/FormatKeyword.php @@ -0,0 +1,82 @@ +name = $name; + $this->types = $types; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $type = $context->currentDataType(); + + if (!isset($this->types[$type])) { + return null; + } + + $format = $this->types[$type]; + + try { + if ($format instanceof Format) { + $ok = $format->validate($context->currentData()); + } else { + $ok = $format($context->currentData()); + } + } catch (CustomError $error) { + return $this->error($schema, $context, 'format', $error->getMessage(), $error->getArgs() + [ + 'format' => $this->name, + 'type' => $type, + ]); + } + + if ($ok) { + return null; + } + + return $this->error($schema, $context, 'format', "The data must match the '{format}' format", [ + 'format' => $this->name, + 'type' => $type, + ]); + } +} diff --git a/admin/vendor/opis/json-schema/src/Keywords/IfThenElseKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/IfThenElseKeyword.php new file mode 100644 index 0000000..730ac68 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/IfThenElseKeyword.php @@ -0,0 +1,104 @@ +if = $if; + $this->then = $then; + $this->else = $else; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->if === true) { + return $this->validateBranch('then', $context, $schema); + } elseif ($this->if === false) { + return $this->validateBranch('else', $context, $schema); + } + + if (is_object($this->if) && !($this->if instanceof Schema)) { + $this->if = $context->loader()->loadObjectSchema($this->if); + } + + if ($context->validateSchemaWithoutEvaluated($this->if, null, true)) { + return $this->validateBranch('else', $context, $schema); + } + + return $this->validateBranch('then', $context, $schema); + } + + /** + * @param string $branch + * @param ValidationContext $context + * @param Schema $schema + * @return ValidationError|null + */ + protected function validateBranch(string $branch, ValidationContext $context, Schema $schema): ?ValidationError + { + $value = $this->{$branch}; + + if ($value === true) { + return null; + } elseif ($value === false) { + return $this->error($schema, $context, $branch, "The data is never valid on '{branch}' branch", [ + 'branch' => $branch, + ]); + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->{$branch} = $context->loader()->loadObjectSchema($value); + } + + if ($error = $value->validate($context)) { + return $this->error($schema, $context, $branch, "The data is not valid on '{branch}' branch", [ + 'branch' => $branch, + ], $error); + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/ItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/ItemsKeyword.php new file mode 100644 index 0000000..4726455 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/ItemsKeyword.php @@ -0,0 +1,162 @@ +value = $value; + $this->alwaysValid = $alwaysValid; + + if (is_array($value)) { + $this->count = count($value); + } + + $this->keyword = $keyword; + $this->startIndex = $startIndex; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->alwaysValid || $this->value === true) { + if ($this->count === -1) { + $context->markAllAsEvaluatedItems(); + } else { + $context->markCountAsEvaluatedItems($this->count); + } + return null; + } + + $count = count($context->currentData()); + + if ($this->startIndex >= $count) { + // Already validated by other keyword + return null; + } + + if ($this->value === false) { + if ($count === 0) { + return null; + } + + return $this->error($schema, $context, $this->keyword, 'Array must be empty'); + } + + if ($this->count >= 0) { + + $errors = $this->errorContainer($context->maxErrors()); + $max = min($count, $this->count); + $evaluated = []; + + for ($i = $this->startIndex; $i < $max; $i++) { + if ($this->value[$i] === true) { + $evaluated[] = $i; + continue; + } + + if ($this->value[$i] === false) { + $context->addEvaluatedItems($evaluated); + return $this->error($schema, $context, $this->keyword, "Array item at index {index} is not allowed", [ + 'index' => $i, + ]); + } + + if (is_object($this->value[$i]) && !($this->value[$i] instanceof Schema)) { + $this->value[$i] = $context->loader()->loadObjectSchema($this->value[$i]); + } + + $context->pushDataPath($i); + $error = $this->value[$i]->validate($context); + $context->popDataPath(); + + if ($error) { + $errors->add($error); + if ($errors->isFull()) { + break; + } + } else { + $evaluated[] = $i; + } + } + + $context->addEvaluatedItems($evaluated); + + if ($errors->isEmpty()) { + return null; + } + + return $this->error($schema, $context, $this->keyword, 'Array items must match corresponding schemas', [], + $errors); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $object = $this->createArrayObject($context); + + $error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->startIndex, $count), + $this->keyword, 'All array items must match schema', [], $object); + + if ($object && $object->count()) { + $context->addEvaluatedItems($object->getArrayCopy()); + } + + return $error; + } + + /** + * @param int $start + * @param int $max + * @return iterable|int[] + */ + protected function indexes(int $start, int $max): iterable + { + for ($i = $start; $i < $max; $i++) { + yield $i; + } + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/IterableDataValidationTrait.php b/admin/vendor/opis/json-schema/src/Keywords/IterableDataValidationTrait.php new file mode 100644 index 0000000..99754ec --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/IterableDataValidationTrait.php @@ -0,0 +1,110 @@ +errorContainer($context->maxErrors()); + + if ($keys) { + foreach ($iterator as $key) { + $context->pushDataPath($key); + $error = $schema->validate($context); + $context->popDataPath(); + + if ($error) { + if (!$container->isFull()) { + $container->add($error); + } + } else { + $keys[] = $key; + } + } + } else { + foreach ($iterator as $key) { + $context->pushDataPath($key); + $error = $schema->validate($context); + $context->popDataPath(); + + if ($error && $container->add($error)->isFull()) { + break; + } + } + } + + return $container; + } + + /** + * @param Schema $parentSchema + * @param Schema $schema + * @param ValidationContext $context + * @param iterable $iterator + * @param string $keyword + * @param string $message + * @param array $args + * @param ArrayObject|null $visited_keys + * @return ValidationError|null + */ + protected function validateIterableData( + Schema $parentSchema, + Schema $schema, + ValidationContext $context, + iterable $iterator, + string $keyword, + string $message, + array $args = [], + ?ArrayObject $visited_keys = null + ): ?ValidationError { + $errors = $this->iterateAndValidate($schema, $context, $iterator, $visited_keys); + + if ($errors->isEmpty()) { + return null; + } + + return $this->error($parentSchema, $context, $keyword, $message, $args, $errors); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php new file mode 100644 index 0000000..7e53797 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $count */ + $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($count === $this || !is_int($count) || $count < 0) { + return $this->error($schema, $context, 'maxItems', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->count = $count; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaxItemsKeyword.php new file mode 100644 index 0000000..4003d2f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxItemsKeyword.php @@ -0,0 +1,58 @@ +count = $count; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $count = count($context->currentData()); + + if ($count <= $this->count) { + return null; + } + + return $this->error($schema, $context, "maxItems", + "Array should have at most {max} items, {count} found", [ + 'max' => $this->count, + 'count' => $count, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php new file mode 100644 index 0000000..ea2366d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $length */ + $length = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($length === $this || !is_int($length) || $length < 0) { + return $this->error($schema, $context, 'maxLength', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->length = $length; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxLengthKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaxLengthKeyword.php new file mode 100644 index 0000000..81b3cc5 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxLengthKeyword.php @@ -0,0 +1,58 @@ +length = $length; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $length = $context->getStringLength(); + + if ($length <= $this->length) { + return null; + } + + return $this->error($schema, $context, 'maxLength', "Maximum string length is {max}, found {length}", + [ + 'max' => $this->length, + 'length' => $length, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php new file mode 100644 index 0000000..f48f439 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $count */ + $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($count === $this || !is_int($count) || $count < 0) { + return $this->error($schema, $context, 'maxProperties', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->count = $count; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php b/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php new file mode 100644 index 0000000..34fa0d6 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php @@ -0,0 +1,58 @@ +count = $count; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $count = count($context->getObjectProperties()); + + if ($count <= $this->count) { + return null; + } + + return $this->error($schema, $context, 'maxProperties', + "Object must have at most {max} properties, {count} found", [ + 'max' => $this->count, + 'count' => $count, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaximumDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaximumDataKeyword.php new file mode 100644 index 0000000..55b9dfd --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaximumDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var float|int $number */ + $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($number === $this || !(is_float($number) || is_int($number))) { + return $this->error($schema, $context, 'maximum', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->number = $number; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MaximumKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MaximumKeyword.php new file mode 100644 index 0000000..6888a27 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MaximumKeyword.php @@ -0,0 +1,54 @@ +number = $number; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($context->currentData() <= $this->number) { + return null; + } + + return $this->error($schema, $context, 'maximum', "Number must be lower than or equal to {max}", [ + 'max' => $this->number, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinItemsDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinItemsDataKeyword.php new file mode 100644 index 0000000..1ace7fd --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinItemsDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $count */ + $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($count === $this || !is_int($count) || $count < 0) { + return $this->error($schema, $context, 'minItems', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->count = $count; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinItemsKeyword.php new file mode 100644 index 0000000..bcc0d83 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinItemsKeyword.php @@ -0,0 +1,58 @@ +count = $count; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $count = count($context->currentData()); + + if ($count >= $this->count) { + return null; + } + + return $this->error($schema, $context, "minItems", + "Array should have at least {min} items, {count} found", [ + 'min' => $this->count, + 'count' => $count, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinLengthDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinLengthDataKeyword.php new file mode 100644 index 0000000..57d78c8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinLengthDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $length */ + $length = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($length === $this || !is_int($length) || $length < 0) { + return $this->error($schema, $context, 'minLength', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->length = $length; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinLengthKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinLengthKeyword.php new file mode 100644 index 0000000..7c446d0 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinLengthKeyword.php @@ -0,0 +1,61 @@ +length = $length; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->length === 0) { + return null; + } + + $length = $context->getStringLength(); + + if ($length >= $this->length) { + return null; + } + + return $this->error($schema, $context, 'minLength', "Minimum string length is {min}, found {length}", [ + 'min' => $this->length, + 'length' => $length, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php new file mode 100644 index 0000000..e1b2a2d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var int $count */ + $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($count === $this || !is_int($count) || $count < 0) { + return $this->error($schema, $context, 'minProperties', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->count = $count; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesKeyword.php new file mode 100644 index 0000000..7836168 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinPropertiesKeyword.php @@ -0,0 +1,58 @@ +count = $count; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $count = count($context->getObjectProperties()); + + if ($this->count <= $count) { + return null; + } + + return $this->error($schema, $context, 'minProperties', + "Object must have at least {min} properties, {count} found", [ + 'min' => $this->count, + 'count' => $count, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinimumDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinimumDataKeyword.php new file mode 100644 index 0000000..37140c3 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinimumDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var float|int $number */ + $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($number === $this || !(is_float($number) || is_int($number))) { + return $this->error($schema, $context, 'minimum', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->number = $number; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MinimumKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MinimumKeyword.php new file mode 100644 index 0000000..a499aef --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MinimumKeyword.php @@ -0,0 +1,54 @@ +number = $number; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($context->currentData() >= $this->number) { + return null; + } + + return $this->error($schema, $context, 'minimum', "Number must be greater than or equal to {min}", [ + 'min' => $this->number, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php new file mode 100644 index 0000000..532ee8a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php @@ -0,0 +1,55 @@ +value = $value; + parent::__construct(0); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + /** @var float|int $number */ + $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($number === $this || !(is_float($number) || is_int($number)) || $number <= 0) { + return $this->error($schema, $context, 'multipleOf', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->number = $number; + + return parent::validate($context, $schema); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/MultipleOfKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/MultipleOfKeyword.php new file mode 100644 index 0000000..28ac8f6 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/MultipleOfKeyword.php @@ -0,0 +1,55 @@ +number = $number; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (Helper::isMultipleOf($context->currentData(), $this->number)) { + return null; + } + + return $this->error($schema, $context, 'multipleOf', "Number must be a multiple of {divisor}", [ + 'divisor' => $this->number, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/NotKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/NotKeyword.php new file mode 100644 index 0000000..3a5acd4 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/NotKeyword.php @@ -0,0 +1,66 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->value === false) { + return null; + } + if ($this->value === true) { + return $this->error($schema, $context, 'not', "The data is never valid"); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $error = $context->validateSchemaWithoutEvaluated($this->value, 1); + + if ($error) { + return null; + } + + return $this->error($schema, $context, 'not', 'The data must not match schema'); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/OfTrait.php b/admin/vendor/opis/json-schema/src/Keywords/OfTrait.php new file mode 100644 index 0000000..bcb0b2c --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/OfTrait.php @@ -0,0 +1,45 @@ +trackUnevaluated() ? new ArrayObject() : null; + } + + protected function addEvaluatedFromArrayObject(?ArrayObject $object, ValidationContext $context): void + { + if (!$object || !$object->count()) { + return; + } + + foreach ($object as $value) { + if (isset($value['properties'])) { + $context->addEvaluatedProperties($value['properties']); + } + if (isset($value['items'])) { + $context->addEvaluatedItems($value['items']); + } + } + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/OneOfKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/OneOfKeyword.php new file mode 100644 index 0000000..12473b6 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/OneOfKeyword.php @@ -0,0 +1,98 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $count = 0; + $matchedIndex = -1; + $object = $this->createArrayObject($context); + $errors = []; + + foreach ($this->value as $index => $value) { + if ($value === false) { + continue; + } + + if ($value === true) { + if (++$count > 1) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ + 'matched' => [$matchedIndex, $index], + ]); + } + + $matchedIndex = $index; + continue; + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); + } + + $error = $context->validateSchemaWithoutEvaluated($value, null, false, $object); + if ($error) { + $errors[] = $error; + } else { + if (++$count > 1) { + $this->addEvaluatedFromArrayObject($object, $context); + return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ + 'matched' => [$matchedIndex, $index], + ]); + } + $matchedIndex = $index; + } + } + + $this->addEvaluatedFromArrayObject($object, $context); + + if ($count === 1) { + return null; + } + + return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ + 'matched' => [], + ], $errors); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PatternDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PatternDataKeyword.php new file mode 100644 index 0000000..1fa7c15 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PatternDataKeyword.php @@ -0,0 +1,56 @@ +value = $value; + parent::__construct(''); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $pattern = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + if ($pattern === $this || !is_string($pattern) || !Helper::isValidPattern($pattern)) { + return $this->error($schema, $context, 'pattern', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $this->pattern = $pattern; + $this->regex = Helper::patternToRegex($pattern); + $ret = parent::validate($context, $schema); + $this->pattern = $this->regex = null; + + return $ret; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PatternKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PatternKeyword.php new file mode 100644 index 0000000..b16cbc1 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PatternKeyword.php @@ -0,0 +1,53 @@ +pattern = $pattern; + $this->regex = Helper::patternToRegex($pattern); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (preg_match($this->regex, $context->currentData())) { + return null; + } + + return $this->error($schema, $context, 'pattern', "The string should match pattern: {pattern}", [ + 'pattern' => $this->pattern, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php new file mode 100644 index 0000000..7980a85 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php @@ -0,0 +1,119 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $props = $context->getObjectProperties(); + + if (!$props) { + return null; + } + + $checked = []; + + foreach ($this->value as $pattern => $value) { + if ($value === true) { + iterator_to_array($this->matchedProperties($pattern, $props, $checked)); + continue; + } + + if ($value === false) { + $list = iterator_to_array($this->matchedProperties($pattern, $props, $checked)); + + if ($list) { + if ($context->trackUnevaluated()) { + $context->addEvaluatedProperties(array_diff(array_keys($checked), $list)); + } + return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' are not allowed", [ + 'pattern' => $pattern, + 'forbidden' => $list, + ]); + } + + unset($list); + continue; + } + + if (is_object($value) && !($value instanceof Schema)) { + $value = $this->value[$pattern] = $context->loader()->loadObjectSchema($value); + } + + $subErrors = $this->iterateAndValidate($value, $context, $this->matchedProperties($pattern, $props, $checked)); + + if (!$subErrors->isEmpty()) { + if ($context->trackUnevaluated()) { + $context->addEvaluatedProperties(array_keys($checked)); + } + return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' must also match pattern's schema", [ + 'pattern' => $pattern, + ], $subErrors); + } + + unset($subErrors); + } + + if ($checked) { + $checked = array_keys($checked); + $context->addCheckedProperties($checked); + $context->addEvaluatedProperties($checked); + } + + return null; + } + + /** + * @param string $pattern + * @param array $props + * @param array $checked + * @return Traversable|string[] + */ + protected function matchedProperties(string $pattern, array $props, array &$checked): Traversable + { + $pattern = Helper::patternToRegex($pattern); + + foreach ($props as $prop) { + if (preg_match($pattern, (string)$prop)) { + $checked[$prop] = true; + yield $prop; + } + } + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PointerRefKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PointerRefKeyword.php new file mode 100644 index 0000000..a11142b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PointerRefKeyword.php @@ -0,0 +1,54 @@ +pointer = $pointer; + } + + protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->resolved === false) { + $info = $schema->info(); + $this->resolved = $this->resolvePointer($context->loader(), $this->pointer, $info->idBaseRoot(), $info->path()); + } + + if ($this->resolved === null) { + throw new UnresolvedReferenceException((string)$this->pointer, $schema, $context); + } + + return $this->resolved->validate($this->createContext($context, $schema)); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PropertiesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PropertiesKeyword.php new file mode 100644 index 0000000..5af6557 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PropertiesKeyword.php @@ -0,0 +1,109 @@ +properties = $properties; + $this->propertyKeys = array_keys($properties); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if (!$this->properties) { + return null; + } + + $checked = []; + $evaluated = []; + + $data = $context->currentData(); + + $errors = $this->errorContainer($context->maxErrors()); + + foreach ($this->properties as $name => $prop) { + if (!property_exists($data, $name)) { + continue; + } + + $checked[] = $name; + + if ($prop === true) { + $evaluated[] = $name; + continue; + } + + if ($prop === false) { + $context->addEvaluatedProperties($evaluated); + return $this->error($schema, $context, 'properties', "Property '{property}' is not allowed", [ + 'property' => $name, + ]); + } + + if (is_object($prop) && !($prop instanceof Schema)) { + $prop = $this->properties[$name] = $context->loader()->loadObjectSchema($prop); + } + + $context->pushDataPath($name); + $error = $prop->validate($context); + $context->popDataPath(); + + if ($error) { + $errors->add($error); + if ($errors->isFull()) { + break; + } + } else { + $evaluated[] = $name; + } + } + + $context->addEvaluatedProperties($evaluated); + + if (!$errors->isEmpty()) { + return $this->error($schema, $context, 'properties', "The properties must match schema: {properties}", [ + 'properties' => array_values(array_diff($checked, $evaluated)) + ], $errors); + } + unset($errors); + + $context->addCheckedProperties($checked); + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/PropertyNamesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/PropertyNamesKeyword.php new file mode 100644 index 0000000..4012ca9 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/PropertyNamesKeyword.php @@ -0,0 +1,74 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->value === true) { + return null; + } + + $props = $context->getObjectProperties(); + if (!$props) { + return null; + } + + if ($this->value === false) { + return $this->error($schema, $context, 'propertyNames', "No properties are allowed"); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + foreach ($props as $prop) { + if ($error = $this->value->validate($context->newInstance($prop, $schema))) { + return $this->error($schema, $context, 'propertyNames', "Property '{property}' must match schema", [ + 'property' => $prop, + ], $error); + } + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/RecursiveRefKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/RecursiveRefKeyword.php new file mode 100644 index 0000000..d460001 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/RecursiveRefKeyword.php @@ -0,0 +1,138 @@ +uri = $uri; + $this->anchor = $anchor; + $this->anchorValue = $anchorValue; + } + + /** + * @inheritDoc + */ + public function doValidate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->resolved === false) { + $this->resolved = $context->loader()->loadSchemaById($this->uri); + } + + if ($this->resolved === null) { + throw new UnresolvedReferenceException((string)$this->uri, $schema, $context); + } + + $new_context = $this->createContext($context, $schema); + + if (!$this->hasRecursiveAnchor($this->resolved)) { + $this->setLastRefSchema($this->resolved); + return $this->resolved->validate($new_context); + } + + $ok_sender = $this->resolveSchema($context); + + if (!$ok_sender) { + $this->setLastRefSchema($this->resolved); + return $this->resolved->validate($new_context); + } + + $this->setLastRefSchema($ok_sender); + + return $ok_sender->validate($new_context); + } + + protected function resolveSchema(ValidationContext $context): ?Schema + { + $ok = null; + $loader = $context->loader(); + + while ($context) { + $sender = $context->sender(); + + if (!$sender) { + break; + } + + if (!$this->hasRecursiveAnchor($sender)) { + if ($sender->info()->id()) { + // id without recursiveAnchor + break; + } + + $sender = $loader->loadSchemaById($sender->info()->root()); + if (!$sender || !$this->hasRecursiveAnchor($sender)) { + // root without recursiveAnchor + break; + } + } + + if ($sender->info()->id()) { + // id with recursiveAnchor + $ok = $sender; + } else { + // root with recursiveAnchor + $ok = $loader->loadSchemaById($sender->info()->root()); + } + + $context = $context->parent(); + } + + return $ok; + } + + protected function hasRecursiveAnchor(?Schema $schema): bool + { + if (!$schema) { + return false; + } + + $info = $schema->info(); + + if (!$info->isObject()) { + return false; + } + + $data = $info->data(); + + if (!property_exists($data, $this->anchor)) { + return false; + } + + return $data->{$this->anchor} === $this->anchorValue; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/RequiredDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/RequiredDataKeyword.php new file mode 100644 index 0000000..d518244 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/RequiredDataKeyword.php @@ -0,0 +1,85 @@ +value = $value; + $this->filter = $filter; + parent::__construct([]); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $required = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + if ($required === $this || !is_array($required) || !$this->requiredPropsAreValid($required)) { + return $this->error($schema, $context, 'required', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + $required = array_unique($required); + + if ($this->filter) { + $required = array_filter($required, $this->filter); + } + + if (!$required) { + return null; + } + + $this->required = $required; + $ret = parent::validate($context, $schema); + $this->required = null; + + return $ret; + } + + /** + * @param array $props + * @return bool + */ + protected function requiredPropsAreValid(array $props): bool + { + foreach ($props as $prop) { + if (!is_string($prop)) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/RequiredKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/RequiredKeyword.php new file mode 100644 index 0000000..005e2dc --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/RequiredKeyword.php @@ -0,0 +1,68 @@ +required = $required; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $data = $context->currentData(); + $max = $context->maxErrors(); + $list = []; + + foreach ($this->required as $name) { + if (!property_exists($data, $name)) { + $list[] = $name; + if (--$max <= 0) { + break; + } + } + } + + if (!$list) { + return null; + } + + return $this->error($schema, $context, 'required', 'The required properties ({missing}) are missing', [ + 'missing' => $list, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/SlotsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/SlotsKeyword.php new file mode 100644 index 0000000..c843845 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/SlotsKeyword.php @@ -0,0 +1,151 @@ +slots = $slots; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $newContext = $context->newInstance($context->currentData(), $schema); + + foreach ($this->slots as $name => $fallback) { + $slot = $this->resolveSlotSchema($name, $context); + + if ($slot === null) { + $save = true; + if (is_string($fallback)) { + $save = false; + $fallback = $this->resolveSlot($fallback, $context); + } + + if ($fallback === true) { + continue; + } + + if ($fallback === false) { + return $this->error($schema, $context, '$slots', "Required slot '{slot}' is missing", [ + 'slot' => $name, + ]); + } + + if (is_object($fallback) && !($fallback instanceof Schema)) { + $fallback = $context->loader()->loadObjectSchema($fallback); + if ($save) { + $this->slots[$name] = $fallback; + } + } + + $slot = $fallback; + } + + if ($error = $slot->validate($newContext)) { + return $this->error($schema, $context,'$slots', "Schema for slot '{slot}' was not matched", [ + 'slot' => $name, + ], $error); + } + } + + return null; + } + + /** + * @param string $name + * @param ValidationContext $context + * @return Schema|null + */ + protected function resolveSlotSchema(string $name, ValidationContext $context): ?Schema + { + do { + $slot = $context->slot($name); + } while ($slot === null && $context = $context->parent()); + + return $slot; + } + + /** + * @param string $name + * @param ValidationContext $context + * @return bool|Schema + */ + protected function resolveSlot(string $name, ValidationContext $context) + { + $slot = $this->resolveSlotSchema($name, $context); + + if ($slot !== null) { + return $slot; + } + + if (!isset($this->slots[$name])) { + return false; + } + + $slot = $this->slots[$name]; + + if (is_bool($slot)) { + return $slot; + } + + if (is_object($slot)) { + if ($slot instanceof Schema) { + return $slot; + } + + $slot = $context->loader()->loadObjectSchema($slot); + $this->slots[$name] = $slot; + return $slot; + } + + if (!is_string($slot)) { + // Looks like the slot is missing + return false; + } + + if (in_array($slot, $this->stack)) { + // Recursive + return false; + } + + $this->stack[] = $slot; + $slot = $this->resolveSlot($slot, $context); + array_pop($this->stack); + + return $slot; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/TemplateRefKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/TemplateRefKeyword.php new file mode 100644 index 0000000..d9876e2 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/TemplateRefKeyword.php @@ -0,0 +1,119 @@ +template = $template; + $this->vars = $vars; + $this->allowRelativeJsonPointerInRef = $allowRelativeJsonPointerInRef; + } + + protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->vars) { + $vars = $this->vars->resolve($context->rootData(), $context->currentDataPath()); + if (!is_array($vars)) { + $vars = (array)$vars; + } + $vars += $context->globals(); + } else { + $vars = $context->globals(); + } + + $ref = $this->template->resolve($vars); + + $key = isset($ref[32]) ? md5($ref) : $ref; + + if (!array_key_exists($key, $this->cached)) { + $this->cached[$key] = $this->resolveRef($ref, $context->loader(), $schema); + } + + $resolved = $this->cached[$key]; + unset($key); + + if (!$resolved) { + throw new UnresolvedReferenceException($ref, $schema, $context); + } + + return $resolved->validate($this->createContext($context, $schema)); + } + + /** + * @param string $ref + * @param SchemaLoader $repo + * @param Schema $schema + * @return null|Schema + */ + protected function resolveRef(string $ref, SchemaLoader $repo, Schema $schema): ?Schema + { + if ($ref === '') { + return null; + } + + $baseUri = $schema->info()->idBaseRoot(); + + if ($ref === '#') { + return $repo->loadSchemaById($baseUri); + } + + // Check if is pointer + if ($ref[0] === '#') { + if ($pointer = JsonPointer::parse(substr($ref, 1))) { + if ($pointer->isAbsolute()) { + return $this->resolvePointer($repo, $pointer, $baseUri); + } + unset($pointer); + } + } elseif ($this->allowRelativeJsonPointerInRef && ($pointer = JsonPointer::parse($ref))) { + if ($pointer->isRelative()) { + return $this->resolvePointer($repo, $pointer, $baseUri, $schema->info()->path()); + } + unset($pointer); + } + + $ref = Uri::merge($ref, $baseUri, true); + + if ($ref === null || !$ref->isAbsolute()) { + return null; + } + + return $repo->loadSchemaById($ref); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/TypeKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/TypeKeyword.php new file mode 100644 index 0000000..af4331b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/TypeKeyword.php @@ -0,0 +1,58 @@ +type = $type; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $type = $context->currentDataType(); + if ($type && Helper::jsonTypeMatches($type, $this->type)) { + return null; + } + + return $this->error($schema, $context, 'type', 'The data ({type}) must match the type: {expected}', [ + 'expected' => $this->type, + 'type' => $type, + ]); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/URIRefKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/URIRefKeyword.php new file mode 100644 index 0000000..ab8beba --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/URIRefKeyword.php @@ -0,0 +1,55 @@ +uri = $uri; + } + + protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError + { + if ($this->resolved === false) { + $this->resolved = $context->loader()->loadSchemaById($this->uri); + } + + if ($this->resolved === null) { + throw new UnresolvedReferenceException((string)$this->uri, $schema, $context); + } + + $this->setLastRefSchema($this->resolved); + + return $this->resolved->validate($this->createContext($context, $schema)); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php new file mode 100644 index 0000000..a9673fe --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php @@ -0,0 +1,75 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $unevaluated = $context->getUnevaluatedItems(); + + if (!$unevaluated) { + return null; + } + + if ($this->value === true) { + $context->addEvaluatedItems($unevaluated); + return null; + } + + if ($this->value === false) { + return $this->error($schema, $context, 'unevaluatedItems', + 'Unevaluated array items are not allowed: {indexes}', [ + 'indexes' => $unevaluated, + ]); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $object = $this->createArrayObject($context); + + $error = $this->validateIterableData($schema, $this->value, $context, $unevaluated, + 'unevaluatedItems', 'All unevaluated array items must match schema: {indexes}', [ + 'indexes' => $unevaluated, + ], $object); + + if ($object && $object->count()) { + $context->addEvaluatedItems($object->getArrayCopy()); + } + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php new file mode 100644 index 0000000..ac1a485 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php @@ -0,0 +1,77 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $unevaluated = $context->getUnevaluatedProperties(); + + if (!$unevaluated) { + return null; + } + + if ($this->value === true) { + $context->addEvaluatedProperties($unevaluated); + return null; + } + + if ($this->value === false) { + return $this->error($schema, $context, 'unevaluatedProperties', + 'Unevaluated object properties not allowed: {properties}', [ + 'properties' => $unevaluated, + ]); + } + + if (is_object($this->value) && !($this->value instanceof Schema)) { + $this->value = $context->loader()->loadObjectSchema($this->value); + } + + $object = $this->createArrayObject($context); + + $error = $this->validateIterableData($schema, $this->value, $context, $unevaluated, + 'unevaluatedProperties', 'All unevaluated object properties must match schema: {properties}', [ + 'properties' => $unevaluated, + ], $object); + + + if ($object && $object->count()) { + $context->addEvaluatedProperties($object->getArrayCopy()); + } + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php new file mode 100644 index 0000000..cd860e8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php @@ -0,0 +1,51 @@ +value = $value; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); + + if ($value === $this || !is_bool($value)) { + return $this->error($schema, $context, 'uniqueItems', 'Invalid $data', [ + 'pointer' => (string)$this->value, + ]); + } + + return $value ? parent::validate($context, $schema) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsKeyword.php b/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsKeyword.php new file mode 100644 index 0000000..f952c76 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Keywords/UniqueItemsKeyword.php @@ -0,0 +1,57 @@ +currentData(); + if (!$data) { + return null; + } + + $count = count($data); + + for ($i = 0; $i < $count - 1; $i++) { + for ($j = $i + 1; $j < $count; $j++) { + if (Helper::equals($data[$i], $data[$j])) { + return $this->error($schema, $context, 'uniqueItems', 'Array must have unique items', [ + 'duplicate' => $data[$i], + 'indexes' => [$i, $j], + ]); + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/DataKeywordTrait.php b/admin/vendor/opis/json-schema/src/Parsers/DataKeywordTrait.php new file mode 100644 index 0000000..44e0f45 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/DataKeywordTrait.php @@ -0,0 +1,59 @@ +{'$data'}) || count(get_object_vars($value)) !== 1) { + return null; + } + + return JsonPointer::parse($value->{'$data'}); + } + + /** + * @param SchemaParser $parser + * @param string|null $keyword + * @return bool + */ + protected function isDataKeywordAllowed(SchemaParser $parser, ?string $keyword = null): bool + { + if (!($enabled = $parser->option('allowDataKeyword'))) { + return false; + } + + if ($enabled === true) { + return true; + } + + if ($keyword === null) { + return false; + } + + return is_array($enabled) && in_array($keyword, $enabled); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/DefaultVocabulary.php b/admin/vendor/opis/json-schema/src/Parsers/DefaultVocabulary.php new file mode 100644 index 0000000..d05d428 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/DefaultVocabulary.php @@ -0,0 +1,56 @@ +getKeywordParsers(); + $keywordValidators = $this->getKeywordValidatorParsers(); + $pragmas = $this->getPragmaParsers(); + + if ($extraVocabulary) { + $keywords = array_merge($keywords, $extraVocabulary->keywords()); + $keywordValidators = array_merge($keywordValidators, $extraVocabulary->keywordValidators()); + $pragmas = array_merge($pragmas, $extraVocabulary->pragmas()); + } + + array_unshift($keywords, $this->getRefKeywordParser()); + + parent::__construct($keywords, $keywordValidators, $pragmas); + } + + /** + * @return string + */ + abstract public function version(): string; + + /** + * @return bool + */ + abstract public function allowKeywordsAlongsideRef(): bool; + + /** + * @return bool + */ + abstract public function supportsAnchorId(): bool; + + /** + * @return KeywordParser + */ + abstract protected function getRefKeywordParser(): KeywordParser; + + /** + * @return KeywordParser[] + */ + abstract protected function getKeywordParsers(): array; + + /** + * @return KeywordValidatorParser[] + */ + protected function getKeywordValidatorParsers(): array + { + return []; + } + + /** + * @return PragmaParser[] + */ + protected function getPragmaParsers(): array + { + return []; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/DraftOptionTrait.php b/admin/vendor/opis/json-schema/src/Parsers/DraftOptionTrait.php new file mode 100644 index 0000000..32014b4 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/DraftOptionTrait.php @@ -0,0 +1,43 @@ +option($option); + + if (!$value) { + return false; + } + + if ($value === true) { + return true; + } + + if (is_array($value)) { + return in_array($info->draft(), $value); + } + + return $value === $info->draft(); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft06.php b/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft06.php new file mode 100644 index 0000000..f8a4834 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft06.php @@ -0,0 +1,145 @@ + '$recursiveRef', 'anchor' => '$recursiveAnchor', 'fragment' => false], + ]); + } + + /** + * @inheritDoc + */ + protected function getKeywordParsers(): array + { + return [ + // Generic + new TypeKeywordParser('type'), + new ConstKeywordParser('const'), + new EnumKeywordParser('enum'), + new FormatKeywordParser('format'), + + // String + new MinLengthKeywordParser('minLength'), + new MaxLengthKeywordParser('maxLength'), + new PatternKeywordParser("pattern"), + new ContentEncodingKeywordParser('contentEncoding'), + new ContentMediaTypeKeywordParser('contentMediaType'), + new ContentSchemaKeywordParser('contentSchema'), + + // Number + new MinimumKeywordParser('minimum', 'exclusiveMinimum'), + new MaximumKeywordParser('maximum', 'exclusiveMaximum'), + new ExclusiveMinimumKeywordParser('exclusiveMinimum'), + new ExclusiveMaximumKeywordParser('exclusiveMaximum'), + new MultipleOfKeywordParser('multipleOf'), + + // Array + new MinItemsKeywordParser('minItems'), + new MaxItemsKeywordParser('maxItems'), + new UniqueItemsKeywordParser('uniqueItems'), + new ContainsKeywordParser('contains', 'minContains', 'maxContains'), + new ItemsKeywordParser('items'), + new AdditionalItemsKeywordParser('additionalItems'), + + // Object + new MinPropertiesKeywordParser('minProperties'), + new MaxPropertiesKeywordParser('maxProperties'), + new RequiredKeywordParser('required'), + new DependenciesKeywordParser('dependencies'), // keep for draft-07 compatibility + new DependentRequiredKeywordParser('dependentRequired'), + new DependentSchemasKeywordParser('dependentSchemas'), + new PropertyNamesKeywordParser('propertyNames'), + new PropertiesKeywordParser('properties'), + new PatternPropertiesKeywordParser('patternProperties'), + new AdditionalPropertiesKeywordParser('additionalProperties'), + + // Conditionals + new IfThenElseKeywordParser('if', 'then', 'else'), + new AnyOfKeywordParser('anyOf'), + new AllOfKeywordParser('allOf'), + new OneOfKeywordParser('oneOf'), + new NotKeywordParser('not'), + + // Unevaluated + new UnevaluatedPropertiesKeywordParser('unevaluatedProperties'), + new UnevaluatedItemsKeywordParser('unevaluatedItems'), + + // Optional + new DefaultKeywordParser('default'), + ]; + } + +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft202012.php b/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft202012.php new file mode 100644 index 0000000..b4e960f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Drafts/Draft202012.php @@ -0,0 +1,163 @@ + '$dynamicRef', 'anchor' => '$dynamicAnchor', 'fragment' => true], + ['ref' => '$recursiveRef', 'anchor' => '$recursiveAnchor', 'fragment' => false], + ]); + } + + /** + * @inheritDoc + */ + protected function getKeywordParsers(): array + { + return [ + // Generic + new TypeKeywordParser('type'), + new ConstKeywordParser('const'), + new EnumKeywordParser('enum'), + new FormatKeywordParser('format'), + + // String + new MinLengthKeywordParser('minLength'), + new MaxLengthKeywordParser('maxLength'), + new PatternKeywordParser("pattern"), + new ContentEncodingKeywordParser('contentEncoding'), + new ContentMediaTypeKeywordParser('contentMediaType'), + new ContentSchemaKeywordParser('contentSchema'), + + // Number + new MinimumKeywordParser('minimum', 'exclusiveMinimum'), + new MaximumKeywordParser('maximum', 'exclusiveMaximum'), + new ExclusiveMinimumKeywordParser('exclusiveMinimum'), + new ExclusiveMaximumKeywordParser('exclusiveMaximum'), + new MultipleOfKeywordParser('multipleOf'), + + // Array + new MinItemsKeywordParser('minItems'), + new MaxItemsKeywordParser('maxItems'), + new UniqueItemsKeywordParser('uniqueItems'), + new ContainsKeywordParser('contains', 'minContains', 'maxContains'), + new ItemsKeywordParser('prefixItems', ItemsKeywordParser::ONLY_ARRAY), + new ItemsKeywordParser('items', ItemsKeywordParser::ONLY_SCHEMA, 'prefixItems'), + // keep for draft-2019-09 compatibility + new AdditionalItemsKeywordParser('additionalItems'), + + // Object + new MinPropertiesKeywordParser('minProperties'), + new MaxPropertiesKeywordParser('maxProperties'), + new RequiredKeywordParser('required'), + new DependenciesKeywordParser('dependencies'), // keep for draft-07 compatibility + new DependentRequiredKeywordParser('dependentRequired'), + new DependentSchemasKeywordParser('dependentSchemas'), + new PropertyNamesKeywordParser('propertyNames'), + new PropertiesKeywordParser('properties'), + new PatternPropertiesKeywordParser('patternProperties'), + new AdditionalPropertiesKeywordParser('additionalProperties'), + + // Conditionals + new IfThenElseKeywordParser('if', 'then', 'else'), + new AnyOfKeywordParser('anyOf'), + new AllOfKeywordParser('allOf'), + new OneOfKeywordParser('oneOf'), + new NotKeywordParser('not'), + + // Unevaluated + new UnevaluatedPropertiesKeywordParser('unevaluatedProperties'), + new UnevaluatedItemsKeywordParser('unevaluatedItems'), + + // Optional + new DefaultKeywordParser('default'), + ]; + } + +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/KeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/KeywordParser.php new file mode 100644 index 0000000..4a49a24 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/KeywordParser.php @@ -0,0 +1,62 @@ +draft(); + return $draft !== '06' && $draft !== '07'; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/KeywordParserTrait.php b/admin/vendor/opis/json-schema/src/Parsers/KeywordParserTrait.php new file mode 100644 index 0000000..b604afc --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/KeywordParserTrait.php @@ -0,0 +1,76 @@ +keyword = $keyword; + } + + /** + * @param object|SchemaInfo $schema + * @param string|null $keyword + * @return bool + */ + protected function keywordExists(object $schema, ?string $keyword = null): bool + { + if ($schema instanceof SchemaInfo) { + $schema = $schema->data(); + } + + return property_exists($schema, $keyword ?? $this->keyword); + } + + /** + * @param object|SchemaInfo $schema + * @param string|null $keyword + * @return mixed + */ + protected function keywordValue(object $schema, ?string $keyword = null) + { + if ($schema instanceof SchemaInfo) { + $schema = $schema->data(); + } + + return $schema->{$keyword ?? $this->keyword}; + } + + /** + * @param string $message + * @param SchemaInfo $info + * @param string|null $keyword + * @return InvalidKeywordException + */ + protected function keywordException(string $message, SchemaInfo $info, ?string $keyword = null): InvalidKeywordException + { + $keyword = $keyword ?? $this->keyword; + + return new InvalidKeywordException(str_replace('{keyword}', $keyword, $message), $keyword, $info); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/KeywordValidatorParser.php b/admin/vendor/opis/json-schema/src/Parsers/KeywordValidatorParser.php new file mode 100644 index 0000000..19e4324 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/KeywordValidatorParser.php @@ -0,0 +1,34 @@ +option('allowPragmas') || !$this->keywordExists($info)) { + return null; + } + + $value = $this->keywordValue($info); + + if (!is_object($value)) { + throw $this->keywordException('{keyword} must be an object', $info); + } + + $list = []; + + $draft = $info->draft() ?? $parser->defaultDraftVersion(); + + $pragmaInfo = new SchemaInfo($value, null, $info->id() ?? $info->base(), $info->root(), + array_merge($info->path(), [$this->keyword]), $draft); + + foreach ($parser->draft($draft)->pragmas() as $pragma) { + if ($handler = $pragma->parse($pragmaInfo, $parser, $shared)) { + $list[] = $handler; + } + } + + return $list ? new PragmaKeywordValidator($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php new file mode 100644 index 0000000..ceb0c82 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php @@ -0,0 +1,63 @@ +option('keepAdditionalItemsKeyword') && $info->draft() === '2020-12') { + return null; + } + + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + if (!property_exists($schema, 'items') || !is_array($schema->items)) { + // Ignore additionalItems + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_bool($value) && !is_object($value)) { + throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); + } + + return new AdditionalItemsKeyword($value, count($schema->items)); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php new file mode 100644 index 0000000..931cd3f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php @@ -0,0 +1,54 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_bool($value) && !is_object($value)) { + throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); + } + + return new AdditionalPropertiesKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php new file mode 100644 index 0000000..8831826 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php @@ -0,0 +1,75 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_array($value)) { + throw $this->keywordException("{keyword} should be an array of json schemas", $info); + } + + if (!$value) { + throw $this->keywordException("{keyword} must have at least one element", $info); + } + + $valid = 0; + + foreach ($value as $index => $item) { + if ($item === false) { + throw $this->keywordException("{keyword} contains false schema", $info); + } + if ($item === true) { + $valid++; + continue; + } + if (!is_object($item)) { + throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); + } elseif (!count(get_object_vars($item))) { + $valid++; + } + } + + return $valid !== count($value) ? new AllOfKeyword($value) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php new file mode 100644 index 0000000..5dd76a4 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php @@ -0,0 +1,75 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_array($value)) { + throw $this->keywordException("{keyword} should be an array of json schemas", $info); + } + + if (!$value) { + throw $this->keywordException("{keyword} must have at least one element", $info); + } + + $alwaysValid = false; + + foreach ($value as $index => $item) { + if ($item === true) { + $alwaysValid = true; + continue; + } + if ($item === false) { + continue; + } + if (!is_object($item)) { + throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); + } elseif (!count(get_object_vars($item))) { + $alwaysValid = true; + } + } + + return new AnyOfKeyword($value, $alwaysValid); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php new file mode 100644 index 0000000..f15cd15 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php @@ -0,0 +1,74 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new ConstDataKeyword($pointer); + } + } + + $type = Helper::getJsonType($value); + if ($type === null) { + throw $this->keywordException("{keyword} contains unknown json data type", $info); + } + + if (isset($shared->types)) { + if (!Helper::jsonTypeMatches($type, $shared->types)) { + throw $this->keywordException("{keyword} contains a value that doesn't match the type keyword", $info); + } + } else { + $shared->types = [$type]; + } + + return new ConstKeyword($value); + } + +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php new file mode 100644 index 0000000..e6e1c2d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php @@ -0,0 +1,85 @@ +minContains = $minContains; + $this->maxContains = $maxContains; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_ARRAY; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_bool($value) && !is_object($value)) { + throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); + } + + $min = $max = null; + + if ($this->minContains && $this->keywordExists($schema, $this->minContains)) { + $min = $this->keywordValue($schema, $this->minContains); + if (!is_int($min) || $min < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info, $this->minContains); + } + } + + if ($this->maxContains && $this->keywordExists($schema, $this->maxContains)) { + $max = $this->keywordValue($schema, $this->maxContains); + if (!is_int($max) || $max < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info, $this->maxContains); + } + if ($min !== null && $max < $min) { + throw $this->keywordException("{keyword} must be greater than {$this->minContains}", $info, $this->maxContains); + } + } elseif ($min === 0) { + return null; + } + + return new ContainsKeyword($value, $min, $max); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php new file mode 100644 index 0000000..992019b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php @@ -0,0 +1,62 @@ +optionAllowedForDraft('decodeContent', $info, $parser)) { + return null; + } + + $schema = $info->data(); + + $resolver = $parser->getContentEncodingResolver(); + + if (!$resolver || !$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_string($value)) { + throw $this->keywordException("{keyword} must be a string", $info); + } + + return new ContentEncodingKeyword(strtolower($value), $resolver); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php new file mode 100644 index 0000000..97c5cac --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php @@ -0,0 +1,62 @@ +optionAllowedForDraft('decodeContent', $info, $parser)) { + return null; + } + + $schema = $info->data(); + + $resolver = $parser->getMediaTypeResolver(); + + if (!$resolver || !$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_string($value)) { + throw $this->keywordException("{keyword} must be a string", $info); + } + + return new ContentMediaTypeKeyword($value, $resolver); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php new file mode 100644 index 0000000..58cce2f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php @@ -0,0 +1,60 @@ +optionAllowedForDraft('decodeContent', $info, $parser)) { + return null; + } + + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be a valid json schema object", $info); + } + + return new ContentSchemaKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php new file mode 100644 index 0000000..bf13a6f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php @@ -0,0 +1,85 @@ +properties = $properties; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_APPEND; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$parser->option('allowDefaults')) { + return null; + } + + $defaults = null; + + if ($this->keywordExists($schema)) { + $defaults = $this->keywordValue($schema); + + if (is_object($defaults)) { + $defaults = (array)Helper::cloneValue($defaults); + } else { + $defaults = null; + } + } + + if ($this->properties !== null && property_exists($schema, $this->properties) + && is_object($schema->{$this->properties})) { + foreach ($schema->{$this->properties} as $name => $value) { + if (is_object($value) && property_exists($value, $this->keyword)) { + $defaults[$name] = $value->{$this->keyword}; + } + } + } + + if (!$defaults) { + return null; + } + + return new DefaultKeyword($defaults); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php new file mode 100644 index 0000000..078a3b9 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php @@ -0,0 +1,80 @@ +option('keepDependenciesKeyword') && !in_array($info->draft(), ['06', '07'])) { + return null; + } + + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be an object", $info); + } + + $list = get_object_vars($value); + + foreach ($list as $name => $s) { + if (is_array($s)) { + if (!$s) { + unset($list[$name]); + continue; + } + foreach ($s as $p) { + if (!is_string($p)) { + throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); + } + } + $list[$name] = array_unique($s); + } elseif (is_bool($s)) { + if ($s) { + unset($list[$name]); + } + } elseif (!is_object($s)) { + throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); + } + } + + return $list ? new DependenciesKeyword($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php new file mode 100644 index 0000000..d8f4289 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php @@ -0,0 +1,70 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be an object", $info); + } + + $list = []; + foreach ($value as $name => $s) { + if (!is_array($s)) { + throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); + } + if (!$s) { + // Empty array + continue; + } + foreach ($s as $p) { + if (!is_string($p)) { + throw $this->keywordException("{keyword} must be an object containing arrays of property names", $info); + } + } + $list[$name] = array_unique($s); + } + + return $list ? new DependentRequiredKeyword($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php new file mode 100644 index 0000000..4fa313a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php @@ -0,0 +1,73 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be an object", $info); + } + + $valid = 0; + $total = 0; + + foreach ($value as $name => $s) { + $total++; + if (is_bool($s)) { + if ($s) { + $valid++; + } + } elseif (!is_object($s)) { + throw $this->keywordException("{keyword} must be an object containing json schemas", $info); + } elseif (!count(get_object_vars($s))) { + $valid++; + } + } + + if (!$total) { + return null; + } + + return $valid !== $total ? new DependentSchemasKeyword($value) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php new file mode 100644 index 0000000..e666c9e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php @@ -0,0 +1,107 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new EnumDataKeyword($pointer); + } + } + + if (!is_array($value) || !$value) { + throw $this->keywordException("{keyword} must be a non-empty array", $info); + } + + $hasConst = property_exists($schema, 'const'); + $constMatched = false; + + $allowedTypes = isset($shared->types) ? $shared->types : null; + $foundTypes = []; + $list = []; + foreach ($value as $item) { + $type = Helper::getJsonType($item); + if ($type === null) { + throw $this->keywordException("{keyword} contains invalid json data type", $info); + } + + if ($allowedTypes && !Helper::jsonTypeMatches($type, $allowedTypes)) { + continue; + } + + if ($hasConst && Helper::equals($item, $schema->const)) { + $constMatched = true; + break; + } + + if (!in_array($type, $foundTypes)) { + $foundTypes[] = $type; + } + + $list[] = $item; + } + + if ($hasConst) { + if ($constMatched) { + return null; + } + throw $this->keywordException("{keyword} does not contain the value of const keyword", $info); + } + + if ($foundTypes) { + if ($allowedTypes === null) { + $shared->types = $foundTypes; + } else { + $shared->types = array_unique(array_merge($shared->types, $foundTypes)); + } + } + + return new EnumKeyword($list); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php new file mode 100644 index 0000000..afe8f78 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php @@ -0,0 +1,70 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (is_bool($value) && $parser->option('allowExclusiveMinMaxAsBool')) { + return null; + } + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new ExclusiveMaximumDataKeyword($pointer); + } + } + + if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { + throw $this->keywordException('{keyword} must contain a valid number', $info); + } + + return new ExclusiveMaximumKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php new file mode 100644 index 0000000..540d62d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php @@ -0,0 +1,70 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (is_bool($value) && $parser->option('allowExclusiveMinMaxAsBool')) { + return null; + } + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new ExclusiveMinimumDataKeyword($pointer); + } + } + + if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { + throw $this->keywordException('{keyword} must contain a valid number', $info); + } + + return new ExclusiveMinimumKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php new file mode 100644 index 0000000..2b8ced8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php @@ -0,0 +1,161 @@ +data(); + + if (!$parser->option('allowFilters')) { + return null; + } + + $resolver = $parser->getFilterResolver(); + + if (!$resolver || !$this->keywordExists($schema)) { + return null; + } + + $filters = $this->parseFilters($parser, $resolver, $this->keywordValue($schema), $info); + if (!$filters) { + return null; + } + + return new FiltersKeyword($filters); + } + + /** + * @param SchemaParser $parser + * @param FilterResolver $filterResolver + * @param mixed $filters + * @param SchemaInfo $info + * @return array|null + */ + protected function parseFilters( + SchemaParser $parser, + FilterResolver $filterResolver, + $filters, + SchemaInfo $info + ): ?array + { + if (is_string($filters)) { + if ($filters = $this->parseFilter($parser, $filterResolver, $filters, $info)) { + return [$filters]; + } + + return null; + } + + if (is_object($filters)) { + if ($filter = $this->parseFilter($parser, $filterResolver, $filters, $info)) { + return [$filter]; + } + + return null; + } + + if (is_array($filters)) { + if (!$filters) { + return null; + } + $list = []; + foreach ($filters as $filter) { + if ($filter = $this->parseFilter($parser, $filterResolver, $filter, $info)) { + $list[] = $filter; + } + } + + return $list ?: null; + } + + throw $this->keywordException('{keyword} can be a non-empty string, an object or an array of string and objects', $info); + } + + /** + * @param SchemaParser $parser + * @param FilterResolver $resolver + * @param $filter + * @param SchemaInfo $info + * @return object|null + */ + protected function parseFilter( + SchemaParser $parser, + FilterResolver $resolver, + $filter, + SchemaInfo $info + ): ?object + { + $vars = null; + if (is_object($filter)) { + if (!property_exists($filter, '$func') || !is_string($filter->{'$func'}) || $filter->{'$func'} === '') { + throw $this->keywordException('$func (for {keyword}) must be a non-empty string', $info); + } + + $vars = get_object_vars($filter); + unset($vars['$func']); + + if (property_exists($filter, '$vars')) { + if (!is_object($filter->{'$vars'})) { + throw $this->keywordException('$vars (for {keyword}) must be an object', $info); + } + unset($vars['$vars']); + $vars = get_object_vars($filter->{'$vars'}) + $vars; + } + + $filter = $filter->{'$func'}; + } elseif (!is_string($filter) || $filter === '') { + throw $this->keywordException('{keyword} can be a non-empty string, an object or an array of string and objects', $info); + } + + $list = $resolver->resolveAll($filter); + if (!$list) { + throw $this->keywordException("{keyword}: {$filter} doesn't exists", $info); + } + + $list = $this->resolveSubTypes($list); + + return (object)[ + 'name' => $filter, + 'args' => $vars ? $this->createVariables($parser, $vars) : null, + 'types' => $list, + ]; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php new file mode 100644 index 0000000..b3c0262 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php @@ -0,0 +1,71 @@ +data(); + + $resolver = $parser->getFormatResolver(); + + if (!$resolver || !$parser->option('allowFormats') || !$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new FormatDataKeyword($pointer, $resolver); + } + } + + if (!is_string($value)) { + throw $this->keywordException("{keyword} must be a string", $info); + } + + $list = $resolver->resolveAll($value); + + if (!$list) { + return null; + } + + return new FormatKeyword($value, $this->resolveSubTypes($list)); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php new file mode 100644 index 0000000..7ba5001 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php @@ -0,0 +1,109 @@ +then = $then; + $this->else = $else; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_AFTER; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $if = $this->keywordValue($schema); + if (!$this->isJsonSchema($if)) { + throw $this->keywordException("{keyword} keyword must be a json schema", $info); + } + + $then = true; + if (property_exists($schema, $this->then)) { + $then = $schema->{$this->then}; + } + if (!$this->isJsonSchema($then)) { + throw $this->keywordException("{keyword} keyword must be a json schema", $info, $this->then); + } + + $else = true; + if (property_exists($schema, $this->else)) { + $else = $schema->{$this->else}; + } + if (!$this->isJsonSchema($else)) { + throw $this->keywordException("{keyword} keyword must be a json schema", $info, $this->else); + } + + if ($if === true) { + if ($then === true) { + return null; + } + $else = true; + } elseif ($if === false) { + if ($else === true) { + return null; + } + $then = true; + } elseif ($then === true && $else === true) { + return null; + } + + return new IfThenElseKeyword($if, $then, $else); + } + + /** + * @param $value + * @return bool + */ + protected function isJsonSchema($value): bool + { + return is_bool($value) || is_object($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php new file mode 100644 index 0000000..697ec60 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php @@ -0,0 +1,117 @@ +mode = $mode; + $this->startIndexKeyword = $startIndexKeyword; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_ARRAY; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + $alwaysValid = false; + + if (is_bool($value)) { + if ($this->mode === self::ONLY_ARRAY) { + throw $this->keywordException("{keyword} must contain an array of json schemas", $info); + } + if ($value) { + $alwaysValid = true; + } + } elseif (is_array($value)) { + if ($this->mode === self::ONLY_SCHEMA) { + throw $this->keywordException("{keyword} must contain a valid json schema", $info); + } + $valid = 0; + foreach ($value as $index => $v) { + if (is_bool($v)) { + if ($v) { + $valid++; + } + } elseif (!is_object($v)) { + throw $this->keywordException("{keyword}[$index] must contain a valid json schema", $info); + } elseif (!count(get_object_vars($v))) { + $valid++; + } + } + if ($valid === count($value)) { + $alwaysValid = true; + } + } elseif (!is_object($value)) { + if ($this->mode === self::BOTH) { + throw $this->keywordException("{keyword} must be a json schema or an array of json schemas", $info); + } elseif ($this->mode === self::ONLY_ARRAY) { + throw $this->keywordException("{keyword} must contain an array of json schemas", $info); + } else { + throw $this->keywordException("{keyword} must contain a valid json schema", $info); + } + } else { + if ($this->mode === self::ONLY_ARRAY) { + throw $this->keywordException("{keyword} must contain an array of json schemas", $info); + } + if (!count(get_object_vars($value))) { + $alwaysValid = true; + } + } + + $startIndex = 0; + if ($this->startIndexKeyword !== null && $this->keywordExists($schema, $this->startIndexKeyword)) { + $start = $this->keywordValue($schema, $this->startIndexKeyword); + if (is_array($start)) { + $startIndex = count($start); + } + } + + return new ItemsKeyword($value, $alwaysValid, $this->keyword, $startIndex); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php new file mode 100644 index 0000000..e24fa75 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php @@ -0,0 +1,63 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MaxItemsDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} most be a positive integer", $info); + } + + return new MaxItemsKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php new file mode 100644 index 0000000..54bc88b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php @@ -0,0 +1,63 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MaxLengthDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info); + } + + return new MaxLengthKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php new file mode 100644 index 0000000..971d3c8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php @@ -0,0 +1,63 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MaxPropertiesDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info); + } + + return new MaxPropertiesKeywords($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php new file mode 100644 index 0000000..1eb9824 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php @@ -0,0 +1,91 @@ +exclusiveKeyword = $exclusiveKeyword; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_NUMBER; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + $exclusive = false; + if ($parser->option('allowExclusiveMinMaxAsBool') && + $this->exclusiveKeyword !== null && + property_exists($schema, $this->exclusiveKeyword)) { + $exclusive = $schema->{$this->exclusiveKeyword} === true; + } + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return $exclusive + ? new ExclusiveMaximumDataKeyword($pointer) + : new MaximumDataKeyword($pointer); + } + } + + if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { + throw $this->keywordException('{keyword} must contain a valid number', $info); + } + + return $exclusive + ? new ExclusiveMaximumKeyword($value) + : new MaximumKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php new file mode 100644 index 0000000..55b40aa --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MinItemsDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} most be a positive integer", $info); + } + + if ($value === 0) { + return null; + } + + return new MinItemsKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php new file mode 100644 index 0000000..0a4ab4e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MinLengthDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info); + } + + if ($value === 0) { + return null; + } + + return new MinLengthKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php new file mode 100644 index 0000000..c8f8928 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MinPropertiesDataKeyword($pointer); + } + } + + if (!is_int($value) || $value < 0) { + throw $this->keywordException("{keyword} must be a non-negative integer", $info); + } + + if ($value === 0) { + return null; + } + + return new MinPropertiesKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php new file mode 100644 index 0000000..640ca90 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php @@ -0,0 +1,91 @@ +exclusiveKeyword = $exclusiveKeyword; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_NUMBER; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $schema = $info->data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + $exclusive = false; + if ($parser->option('allowExclusiveMinMaxAsBool') && + $this->exclusiveKeyword !== null && + property_exists($schema, $this->exclusiveKeyword)) { + $exclusive = $schema->{$this->exclusiveKeyword} === true; + } + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return $exclusive + ? new ExclusiveMinimumDataKeyword($pointer) + : new MinimumDataKeyword($pointer); + } + } + + if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { + throw $this->keywordException('{keyword} must contain a valid number', $info); + } + + return $exclusive + ? new ExclusiveMinimumKeyword($value) + : new MinimumKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php new file mode 100644 index 0000000..8281d47 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new MultipleOfDataKeyword($pointer); + } + } + + if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { + throw $this->keywordException("{keyword} must be a valid number (integer or float)", $info); + } + + if ($value <= 0) { + throw $this->keywordException("{keyword} must be greater than zero", $info); + } + + return new MultipleOfKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php new file mode 100644 index 0000000..53bc577 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php @@ -0,0 +1,58 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (is_bool($value)) { + if (!$value) { + return null; + } + } elseif (!is_object($value)) { + throw $this->keywordException("{keyword} must contain a json schema (object or boolean)", $info); + } + + return new NotKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php new file mode 100644 index 0000000..66e0693 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php @@ -0,0 +1,79 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_array($value)) { + throw $this->keywordException("{keyword} should be an array of json schemas", $info); + } + + if (!$value) { + throw $this->keywordException("{keyword} must have at least one element", $info); + } + + $valid = 0; + + foreach ($value as $index => $item) { + if ($item === false) { + continue; + } + if ($item === true) { + if (++$valid > 1) { + throw $this->keywordException("{keyword} contains multiple true values", $info); + } + continue; + } + if (!is_object($item)) { + throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); + } elseif (!count(get_object_vars($item))) { + if (++$valid > 1) { + throw $this->keywordException("{keyword} contains multiple true values", $info); + } + } + } + + return new OneOfKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php new file mode 100644 index 0000000..bae5466 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new PatternDataKeyword($pointer); + } + } + + if (!is_string($value)) { + throw $this->keywordException("{keyword} value must be a string", $info); + } + + if (!Helper::isValidPattern($value)) { + throw $this->keywordException("{keyword} value must be a valid regex", $info); + } + + return new PatternKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php new file mode 100644 index 0000000..70f1085 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php @@ -0,0 +1,67 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be an object", $info); + } + + $list = []; + + foreach ($value as $pattern => $item) { + if (!Helper::isValidPattern($pattern)) { + throw $this->keywordException("Each property name from {keyword} must be valid regex", $info); + } + + if (!is_bool($item) && !is_object($item)) { + throw $this->keywordException("{keyword}[{$pattern}] must be a json schema (object or boolean)", $info); + } + + $list[$pattern] = $item; + } + + return $list ? new PatternPropertiesKeyword($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php new file mode 100644 index 0000000..9bdf76b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php @@ -0,0 +1,64 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_object($value)) { + throw $this->keywordException("{keyword} must be an object", $info); + } + + $list = []; + + foreach ($value as $name => $s) { + if (!is_bool($s) && !is_object($s)) { + throw $this->keywordException("{keyword}[{$name}] must be a json schema (object or boolean)", $info); + } + + $list[$name] = $s; + } + + return $list ? new PropertiesKeyword($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php new file mode 100644 index 0000000..d8f3b2d --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php @@ -0,0 +1,58 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (is_bool($value)) { + if ($value) { + return null; + } + } elseif (!is_object($value)) { + throw $this->keywordException("{keyword} must be a valid json schema (object or boolean)", $info); + } + + return new PropertyNamesKeyword($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php new file mode 100644 index 0000000..5c14856 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php @@ -0,0 +1,228 @@ +variations = $variations; + } + + /** + * @inheritDoc + */ + public function type(): string + { + return self::TYPE_AFTER_REF; + } + + /** + * @inheritDoc + */ + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + $ref = null; + $recursive = false; + $schema = $info->data(); + $variation = null; + + if ($this->keywordExists($schema)) { + $ref = $this->keywordValue($schema); + if (!is_string($ref) || $ref === '') { + throw $this->keywordException('{keyword} must be a non-empty string', $info); + } + } elseif ($this->variations) { + foreach ($this->variations as $v) { + if (!$this->keywordExists($schema, $v['ref'])) { + continue; + } + $ref = $this->keywordValue($schema, $v['ref']); + if ($v['fragment']) { + if (!preg_match('/^#[a-z][a-z0-9\\-.:_]*/i', $ref)) { + $this->keywordException("{keyword} value is malformed", $info, $v['ref']); + } + } elseif ($ref !== '#') { + $this->keywordException("{keyword} supports only '#' as value", $info, $v['ref']); + } + $variation = $v; + $recursive = true; + break; + } + if (!$recursive) { + return null; + } + } else { + return null; + } + + // Mappers + $mapper = null; + if ($parser->option('allowMappers') && property_exists($schema, '$map')) { + if (!is_object($schema->{'$map'}) && !is_array($schema->{'$map'})) { + throw $this->keywordException('$map keyword must be an object or an array', $info, '$map'); + } + + if (!empty($schema->{'$map'})) { + $mapper = $this->createVariables($parser, $schema->{'$map'}); + } + } + + // Globals + $globals = null; + if ($parser->option('allowGlobals') && property_exists($schema, '$globals')) { + if (!is_object($schema->{'$globals'})) { + throw $this->keywordException('$globals keyword must be an object', $info, '$globals'); + } + + if (!empty($schema->{'$globals'})) { + $globals = $this->createVariables($parser, $schema->{'$globals'}); + } + } + + // Pass slots + $slots = null; + if ($parser->option('allowSlots') && property_exists($schema, '$inject')) { + $slots = $this->parseInjectedSlots($info, $parser, '$inject'); + } + + if ($recursive) { + $ref = $info->idBaseRoot()->resolveRef($ref); + if ($variation['fragment']) { + return new RecursiveRefKeyword($ref->resolveRef('#'), $mapper, $globals, $slots, + $variation['ref'], $variation['anchor'], $ref->fragment()); + } + return new RecursiveRefKeyword($ref, $mapper, $globals, $slots, + $variation['ref'], $variation['anchor'], true); + } + + if ($ref === '#') { + return new URIRefKeyword(Uri::merge('#', $info->idBaseRoot()), $mapper, $globals, $slots, $this->keyword); + } + + if ($parser->option('allowTemplates') && UriTemplate::isTemplate($ref)) { + $tpl = new UriTemplate($ref); + + if ($tpl->hasPlaceholders()) { + $vars = null; + + if (property_exists($schema, '$vars')) { + if (!is_object($schema->{'$vars'})) { + throw $this->keywordException('$vars keyword must be an object', $info, '$vars'); + } + + if (!empty($schema->{'$vars'})) { + $vars = $this->createVariables($parser, $schema->{'$vars'}); + } + } + + return new TemplateRefKeyword( + $tpl, $vars, $mapper, + $globals, $slots, $this->keyword, + $parser->option('allowRelativeJsonPointerInRef') + ); + } + + unset($tpl); + } + + if ($ref[0] === '#') { + if (($pointer = JsonPointer::parse(substr($ref, 1))) && $pointer->isAbsolute()) { + return new PointerRefKeyword($pointer, $mapper, $globals, $slots, $this->keyword); + } + } elseif ($parser->option('allowRelativeJsonPointerInRef') && + ($pointer = JsonPointer::parse($ref)) && $pointer->isRelative()) { + return new PointerRefKeyword($pointer, $mapper, $globals, $slots, $this->keyword); + } + + $ref = Uri::merge($ref, $info->idBaseRoot(), true); + + if ($ref === null || !$ref->isAbsolute()) { + throw $this->keywordException('{keyword} must be a valid uri, uri-reference, uri-template or json-pointer', + $info); + } + + return new URIRefKeyword($ref, $mapper, $globals, $slots, $this->keyword); + } + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param string $keyword + * @return string[]|object[]|Schema[] + */ + protected function parseInjectedSlots(SchemaInfo $info, SchemaParser $parser, string $keyword): ?array + { + $schema = $info->data(); + + if (!is_object($schema->{$keyword})) { + throw $this->keywordException('{keyword} keyword value must be an object', $info, $keyword); + } + + return $this->getSlotSchemas($info, $parser, $schema->{$keyword}, [$keyword]); + } + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param object $slots + * @param array $path + * @return null + */ + protected function getSlotSchemas(SchemaInfo $info, SchemaParser $parser, object $slots, array $path): ?array + { + $keyword = null; + if ($path) { + $keyword = end($path); + $path = array_merge($info->path(), $path); + } else { + $path = $info->path(); + } + + $list = []; + + foreach ($slots as $name => $value) { + if ($value === null) { + continue; + } + if (is_string($value) || is_object($value)) { + $list[$name] = $value; + } elseif (is_bool($value)) { + $list[$name] = $parser->parseSchema(new SchemaInfo( + $value, null, $info->id() ?? $info->base(), $info->root(), + array_merge($path, [$name]), + $info->draft() ?? $parser->defaultDraftVersion() + )); + } else { + throw $this->keywordException('Slots must contain valid json schemas or slot names', $info, $keyword); + } + } + + return $list ?: null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php new file mode 100644 index 0000000..b66557e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php @@ -0,0 +1,105 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + $filter = $this->propertiesFilter($parser, $schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new RequiredDataKeyword($pointer, $filter); + } + } + + if (!is_array($value)) { + throw $this->keywordException("{keyword} must be an array of strings", $info); + } + + foreach ($value as $name) { + if (!is_string($name)) { + throw $this->keywordException("{keyword} must be an array of strings", $info); + } + } + + if ($filter) { + $value = array_filter($value, $filter); + } + + return $value ? new RequiredKeyword(array_unique($value)) : null; + } + + /** + * @param SchemaParser $parser + * @param object $schema + * @return callable|null + */ + protected function propertiesFilter(SchemaParser $parser, object $schema): ?callable + { + if (!$parser->option('allowDefaults')) { + return null; + } + + if (!property_exists($schema, 'properties') || !is_object($schema->properties)) { + return null; + } + + $props = $schema->properties; + + return static function (string $name) use ($props) { + if (!property_exists($props, $name)) { + return true; + } + + if (is_object($props->{$name}) && property_exists($props->{$name}, 'default')) { + return false; + } + + return true; + }; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php new file mode 100644 index 0000000..176de51 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php @@ -0,0 +1,64 @@ +data(); + + if (!$parser->option('allowSlots') || !$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if (!is_object($value)) { + throw $this->keywordException('{keyword} keyword value must be an object', $info); + } + + $slots = []; + foreach ($value as $name => $fallback) { + if (!is_string($name) || $name === '') { + continue; + } + if (is_bool($fallback) || is_string($fallback) || is_object($fallback)) { + $slots[$name] = $fallback; + } + } + + return $slots ? new SlotsKeyword($slots) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php new file mode 100644 index 0000000..764d5c9 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php @@ -0,0 +1,78 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $type = $this->keywordValue($schema); + + if (is_string($type)) { + $type = [$type]; + } elseif (!is_array($type)) { + throw $this->keywordException('{keyword} can only be a string or an array of string', $info); + } + + foreach ($type as $t) { + if (!Helper::isValidJsonType($t)) { + throw $this->keywordException("{keyword} contains invalid json type: {$t}", $info); + } + } + + $type = array_unique($type); + + if (!isset($shared->types)) { + $shared->types = $type; + } else { + $shared->types = array_unique(array_merge($shared->types, $type)); + } + + $count = count($type); + + if ($count === 0) { + throw $this->keywordException("{keyword} cannot be an empty array", $info); + } elseif ($count === 1) { + $type = reset($type); + } + + return new TypeKeyword($type); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php new file mode 100644 index 0000000..b60a029 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php @@ -0,0 +1,73 @@ +data(); + + if (!$this->keywordExists($schema) || !$parser->option('allowUnevaluated')) { + return null; + } + +// if (!$this->makesSense($schema)) { +// return null; +// } + + $value = $this->keywordValue($schema); + + if (!is_bool($value) && !is_object($value)) { + throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); + } + + return new UnevaluatedItemsKeyword($value); + } + + protected function makesSense(object $schema): bool + { + if (property_exists($schema, 'additionalItems')) { + return false; + } +// if (property_exists($schema, 'contains')) { +// return false; +// } + if (property_exists($schema, 'items') && !is_array($schema->items)) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php new file mode 100644 index 0000000..4e3a22e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php @@ -0,0 +1,68 @@ +data(); + + if (!$this->keywordExists($schema) || !$parser->option('allowUnevaluated')) { + return null; + } + +// if (!$this->makesSense($schema)) { +// return null; +// } + + $value = $this->keywordValue($schema); + + if (!is_bool($value) && !is_object($value)) { + throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); + } + + return new UnevaluatedPropertiesKeyword($value); + } + + protected function makesSense(object $schema): bool + { + if (property_exists($schema, 'additionalProperties')) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php new file mode 100644 index 0000000..4712d6b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php @@ -0,0 +1,63 @@ +data(); + + if (!$this->keywordExists($schema)) { + return null; + } + + $value = $this->keywordValue($schema); + + if ($this->isDataKeywordAllowed($parser, $this->keyword)) { + if ($pointer = $this->getDataKeywordPointer($value)) { + return new UniqueItemsDataKeyword($pointer); + } + } + + if (!is_bool($value)) { + throw $this->keywordException("{keyword} must be a boolean", $info); + } + + return $value ? new UniqueItemsKeyword() : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/PragmaParser.php b/admin/vendor/opis/json-schema/src/Parsers/PragmaParser.php new file mode 100644 index 0000000..1d20e6a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/PragmaParser.php @@ -0,0 +1,84 @@ +pragma = $pragma; + } + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param object $shared + * @return Pragma|null + */ + abstract public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Pragma; + + /** + * @param object|SchemaInfo $schema + * @param string|null $pragma + * @return bool + */ + protected function pragmaExists(object $schema, ?string $pragma = null): bool + { + if ($schema instanceof SchemaInfo) { + $schema = $schema->isObject() ? $schema->data() : null; + } + + return is_object($schema) && property_exists($schema, $pragma ?? $this->pragma); + } + + /** + * @param object|SchemaInfo $schema + * @param string|null $pragma + * @return mixed + */ + protected function pragmaValue(object $schema, ?string $pragma = null) + { + if ($schema instanceof SchemaInfo) { + $schema = $schema->isObject() ? $schema->data() : null; + } + + return is_object($schema) ? $schema->{$pragma ?? $this->pragma} : null; + } + + /** + * @param string $message + * @param SchemaInfo $info + * @param string|null $pragma + * @return InvalidPragmaException + */ + protected function pragmaException(string $message, SchemaInfo $info, ?string $pragma = null): InvalidPragmaException + { + $pragma = $pragma ?? $this->pragma; + + return new InvalidPragmaException(str_replace('{pragma}', $pragma, $message), $pragma, $info); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php new file mode 100644 index 0000000..e460fef --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php @@ -0,0 +1,45 @@ +pragmaExists($info)) { + return null; + } + + $value = $this->pragmaValue($info); + + if (!is_string($value) || !Helper::isValidJsonType($value)) { + throw $this->pragmaException('Pragma {pragma} must contain a valid json type name', $info); + } + + return new CastPragma($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php new file mode 100644 index 0000000..55dddb7 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php @@ -0,0 +1,48 @@ +option('allowGlobals') || !$this->pragmaExists($info)) { + return null; + } + + $value = $this->pragmaValue($info); + + if (!is_object($value)) { + throw $this->pragmaException('Pragma {pragma} must be an object', $info); + } + + $value = get_object_vars($value); + + return $value ? new GlobalsPragma($this->createVariables($parser, $value)) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php new file mode 100644 index 0000000..168e7d1 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php @@ -0,0 +1,44 @@ +pragmaExists($info)) { + return null; + } + + $value = $this->pragmaValue($info); + + if (!is_int($value)) { + throw $this->pragmaException('Pragma {pragma} must be an integer', $info); + } + + return new MaxErrorsPragma($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php new file mode 100644 index 0000000..66f5edb --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php @@ -0,0 +1,65 @@ +option('allowSlots') || !$this->pragmaExists($info)) { + return null; + } + + $value = $this->pragmaValue($info); + + if (!is_object($value)) { + throw $this->pragmaException('Pragma {pragma} must be an object', $info); + } + + $list = []; + + foreach ($value as $name => $slot) { + if ($slot === null) { + continue; + } + + if (is_bool($slot)) { + + $list[$name] = $parser->parseSchema(new SchemaInfo( + $slot, null, $info->base(), $info->root(), + array_merge($info->path(), [$this->pragma, $name]), + $info->draft() ?? $parser->defaultDraftVersion() + )); + } elseif (is_string($slot) || is_object($slot)) { + $list[$name] = $slot; + } else { + throw $this->pragmaException('Pragma {pragma} contains invalid value for slot ' . $name, $info); + } + } + + return $list ? new SlotsPragma($list) : null; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/ResolverTrait.php b/admin/vendor/opis/json-schema/src/Parsers/ResolverTrait.php new file mode 100644 index 0000000..0c66340 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/ResolverTrait.php @@ -0,0 +1,38 @@ + $super) { + if (!isset($list[$sub]) && isset($list[$super])) { + $list[$sub] = $list[$super]; + } + } + + return $list; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/SchemaParser.php b/admin/vendor/opis/json-schema/src/Parsers/SchemaParser.php new file mode 100644 index 0000000..4eef186 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/SchemaParser.php @@ -0,0 +1,644 @@ + true, + 'allowFormats' => true, + 'allowMappers' => true, + 'allowTemplates' => true, + 'allowGlobals' => true, + 'allowDefaults' => true, + 'allowSlots' => true, + 'allowKeywordValidators' => true, + 'allowPragmas' => true, + + 'allowDataKeyword' => true, + 'allowKeywordsAlongsideRef' => false, + 'allowUnevaluated' => true, + 'allowRelativeJsonPointerInRef' => true, + 'allowExclusiveMinMaxAsBool' => true, + + 'keepDependenciesKeyword' => true, + 'keepAdditionalItemsKeyword' => true, + + 'decodeContent' => ['06', '07'], + 'defaultDraft' => self::DEFAULT_DRAFT, + + 'varRefKey' => '$ref', + 'varEachKey' => '$each', + 'varDefaultKey' => 'default', + ]; + + /** @var array */ + protected array $options; + + /** @var Draft[] */ + protected array $drafts; + + /** @var array */ + protected array $resolvers; + + /** + * @param array $resolvers + * @param array $options + * @param Vocabulary|null $extraVocabulary + */ + public function __construct( + array $resolvers = [], + array $options = [], + ?Vocabulary $extraVocabulary = null + ) + { + if ($options) { + $this->options = $options + self::DEFAULT_OPTIONS; + } else { + $this->options = self::DEFAULT_OPTIONS; + } + + $this->resolvers = $this->getResolvers($resolvers); + + $this->drafts = $this->getDrafts($extraVocabulary ?? new DefaultVocabulary()); + } + + /** + * @param Vocabulary|null $extraVocabulary + * @return array + */ + protected function getDrafts(?Vocabulary $extraVocabulary): array + { + return [ + '06' => new Draft06($extraVocabulary), + '07' => new Draft07($extraVocabulary), + '2019-09' => new Draft201909($extraVocabulary), + '2020-12' => new Draft202012($extraVocabulary), + ]; + } + + /** + * @param array $resolvers + * @return array + */ + protected function getResolvers(array $resolvers): array + { + if (!array_key_exists('format', $resolvers)) { + $resolvers['format'] = new FormatResolver(); + } + + if (!array_key_exists('contentEncoding', $resolvers)) { + $resolvers['contentEncoding'] = new ContentEncodingResolver(); + } + + if (!array_key_exists('contentMediaType', $resolvers)) { + $resolvers['contentMediaType'] = new ContentMediaTypeResolver(); + } + + if (!array_key_exists('$filters', $resolvers)) { + $resolvers['$filters'] = new FilterResolver(); + } + + return $resolvers; + } + + /** + * @param string $name + * @param null $default + * @return mixed|null + */ + public function option(string $name, $default = null) + { + return $this->options[$name] ?? $default; + } + + /** + * @param string $name + * @param $value + * @return $this + */ + public function setOption(string $name, $value): self + { + $this->options[$name] = $value; + + return $this; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @param string $name + * @param $resolver + * @return $this + */ + public function setResolver(string $name, $resolver): self + { + $this->resolvers[$name] = $resolver; + + return $this; + } + + /** + * @return null|FilterResolver + */ + public function getFilterResolver(): ?FilterResolver + { + return $this->getResolver('$filters'); + } + + /** + * @param null|FilterResolver $resolver + * @return $this + */ + public function setFilterResolver(?FilterResolver $resolver): self + { + return $this->setResolver('$filters', $resolver); + } + + /** + * @return null|FormatResolver + */ + public function getFormatResolver(): ?FormatResolver + { + return $this->getResolver('format'); + } + + /** + * @param FormatResolver|null $resolver + * @return $this + */ + public function setFormatResolver(?FormatResolver $resolver): self + { + return $this->setResolver('format', $resolver); + } + + /** + * @return null|ContentEncodingResolver + */ + public function getContentEncodingResolver(): ?ContentEncodingResolver + { + return $this->getResolver('contentEncoding'); + } + + /** + * @param ContentEncodingResolver|null $resolver + * @return $this + */ + public function setContentEncodingResolver(?ContentEncodingResolver $resolver): self + { + return $this->setResolver('contentEncoding', $resolver); + } + + /** + * @return null|ContentMediaTypeResolver + */ + public function getMediaTypeResolver(): ?ContentMediaTypeResolver + { + return $this->getResolver('contentMediaType'); + } + + /** + * @param ContentMediaTypeResolver|null $resolver + * @return $this + */ + public function setMediaTypeResolver(?ContentMediaTypeResolver $resolver): self + { + return $this->setResolver('contentMediaType', $resolver); + } + + /** + * @return string + */ + public function defaultDraftVersion(): string + { + return $this->option('defaultDraft', self::DEFAULT_DRAFT); + } + + /** + * @param string $draft + * @return $this + */ + public function setDefaultDraftVersion(string $draft): self + { + return $this->setOption('defaultDraft', $draft); + } + + /** + * @param string $schema + * @return string|null + */ + public function parseDraftVersion(string $schema): ?string + { + if (!preg_match(self::DRAFT_REGEX, $schema, $m)) { + return null; + } + + return $m[1] ?? null; + } + + /** + * @param object $schema + * @return string|null + */ + public function parseId(object $schema): ?string + { + if (property_exists($schema, '$id') && is_string($schema->{'$id'})) { + return $schema->{'$id'}; + } + + return null; + } + + /** + * @param object $schema + * @param string $draft + * @return string|null + */ + public function parseAnchor(object $schema, string $draft): ?string + { + if (!property_exists($schema, '$anchor') || + !isset($this->drafts[$draft]) || + !$this->drafts[$draft]->supportsAnchorId()) { + return null; + } + + $anchor = $schema->{'$anchor'}; + + if (!is_string($anchor) || !preg_match(self::ANCHOR_REGEX, $anchor)) { + return null; + } + + return $anchor; + } + + /** + * @param object $schema + * @return string|null + */ + public function parseSchemaDraft(object $schema): ?string + { + if (!property_exists($schema, '$schema') || !is_string($schema->{'$schema'})) { + return null; + } + + return $this->parseDraftVersion($schema->{'$schema'}); + } + + /** + * @param object $schema + * @param Uri $id + * @param callable $handle_id + * @param callable $handle_object + * @param string|null $draft + * @return Schema|null + */ + public function parseRootSchema( + object $schema, + Uri $id, + callable $handle_id, + callable $handle_object, + ?string $draft = null + ): ?Schema + { + $existent = false; + if (property_exists($schema, '$id')) { + $existent = true; + $id = Uri::parse($schema->{'$id'}, true); + } + + if ($id instanceof Uri) { + if ($id->fragment() === null) { + $id = Uri::merge($id, null, true); + } + } else { + throw new ParseException('Root schema id must be an URI', new SchemaInfo($schema, $id)); + } + + if (!$id->isAbsolute()) { + throw new ParseException('Root schema id must be an absolute URI', new SchemaInfo($schema, $id)); + } + + if ($id->fragment() !== '') { + throw new ParseException('Root schema id must have an empty fragment or none', new SchemaInfo($schema, $id)); + } + + // Check if id exists + if ($resolved = $handle_id($id)) { + return $resolved; + } + + if (property_exists($schema, '$schema')) { + if (!is_string($schema->{'$schema'})) { + throw new ParseException('Schema draft must be a string', new SchemaInfo($schema, $id)); + } + $draft = $this->parseDraftVersion($schema->{'$schema'}); + } + + if ($draft === null) { + $draft = $this->defaultDraftVersion(); + } + + if (!$existent) { + $schema->{'$id'} = (string)$id; + } + + $resolved = $handle_object($schema, $id, $draft); + + if (!$existent) { + unset($schema->{'$id'}); + } + + return $resolved; + } + + /** + * @param SchemaInfo $info + * @return Schema + */ + public function parseSchema(SchemaInfo $info): Schema + { + if ($info->isBoolean()) { + return new BooleanSchema($info); + } + + try { + return $this->parseSchemaObject($info); + } catch (SchemaException $exception) { + return new ExceptionSchema($info, $exception); + } + } + + /** + * @param string $version + * @return Draft|null + */ + public function draft(string $version): ?Draft + { + return $this->drafts[$version] ?? null; + } + + /** + * @param Draft $draft + * @return $this + */ + public function addDraft(Draft $draft): self + { + $this->drafts[$draft->version()] = $draft; + + return $this; + } + + /** + * @return string[] + */ + public function supportedDrafts(): array + { + return array_keys($this->drafts); + } + + /** + * @param array $options + * @return $this + */ + protected function setOptions(array $options): self + { + $this->options = $options + $this->options; + + return $this; + } + + /** + * @param string $name + * @return mixed|null + */ + protected function getResolver(string $name) + { + $resolver = $this->resolvers[$name] ?? null; + + if (!is_object($resolver)) { + return null; + } + + return $resolver; + } + + /** + * @param SchemaInfo $info + * @return Schema + */ + protected function parseSchemaObject(SchemaInfo $info): Schema + { + $draftObject = $this->draft($info->draft()); + + if ($draftObject === null) { + throw new ParseException("Unsupported draft-{$info->draft()}", $info); + } + + /** @var object $schema */ + $schema = $info->data(); + + // Check id + if (property_exists($schema, '$id')) { + $id = $info->id(); + if ($id === null || !$id->isAbsolute()) { + throw new ParseException('Schema id must be a valid URI', $info); + } + } + + if ($hasRef = property_exists($schema, '$ref')) { + if ($this->option('allowKeywordsAlongsideRef') || $draftObject->allowKeywordsAlongsideRef()) { + $hasRef = false; + } + } + + $shared = (object) []; + + if ($this->option('allowKeywordValidators')) { + $keywordValidator = $this->parseKeywordValidators($info, $draftObject->keywordValidators(), $shared); + } else { + $keywordValidator = null; + } + + return $this->parseSchemaKeywords($info, $keywordValidator, $draftObject->keywords(), $shared, $hasRef); + } + + /** + * @param SchemaInfo $info + * @param KeywordValidatorParser[] $keywordValidators + * @param object $shared + * @return KeywordValidator|null + */ + protected function parseKeywordValidators(SchemaInfo $info, array $keywordValidators, object $shared): ?KeywordValidator + { + $last = null; + + while ($keywordValidators) { + /** @var KeywordValidatorParser $keywordValidator */ + $keywordValidator = array_pop($keywordValidators); + if ($keywordValidator && ($keyword = $keywordValidator->parse($info, $this, $shared))) { + $keyword->setNext($last); + $last = $keyword; + unset($keyword); + } + unset($keywordValidator); + } + + return $last; + } + + /** + * @param SchemaInfo $info + * @param KeywordValidator|null $keywordValidator + * @param KeywordParser[] $parsers + * @param object $shared + * @param bool $hasRef + * @return Schema + */ + protected function parseSchemaKeywords(SchemaInfo $info, ?KeywordValidator $keywordValidator, + array $parsers, object $shared, bool $hasRef = false): Schema + { + /** @var Keyword[] $prepend */ + $prepend = []; + /** @var Keyword[] $append */ + $append = []; + /** @var Keyword[] $before */ + $before = []; + /** @var Keyword[] $after */ + $after = []; + /** @var Keyword[][] $types */ + $types = []; + /** @var Keyword[] $ref */ + $ref = []; + + if ($hasRef) { + foreach ($parsers as $parser) { + $kType = $parser->type(); + + if ($kType === KeywordParser::TYPE_APPEND) { + $container = &$append; + } elseif ($kType === KeywordParser::TYPE_AFTER_REF) { + $container = &$ref; + } elseif ($kType === KeywordParser::TYPE_PREPEND) { + $container = &$prepend; + } else { + continue; + } + + if ($keyword = $parser->parse($info, $this, $shared)) { + $container[] = $keyword; + } + + unset($container, $keyword, $kType); + } + } else { + foreach ($parsers as $parser) { + $keyword = $parser->parse($info, $this, $shared); + if ($keyword === null) { + continue; + } + + $kType = $parser->type(); + + switch ($kType) { + case KeywordParser::TYPE_PREPEND: + $prepend[] = $keyword; + break; + case KeywordParser::TYPE_APPEND: + $append[] = $keyword; + break; + case KeywordParser::TYPE_BEFORE: + $before[] = $keyword; + break; + case KeywordParser::TYPE_AFTER: + $after[] = $keyword; + break; + case KeywordParser::TYPE_AFTER_REF: + $ref[] = $keyword; + break; + default: + if (!isset($types[$kType])) { + $types[$kType] = []; + } + $types[$kType][] = $keyword; + break; + + } + } + } + + unset($shared); + + if ($prepend) { + $before = array_merge($prepend, $before); + } + unset($prepend); + + if ($ref) { + $after = array_merge($after, $ref); + } + unset($ref); + + if ($append) { + $after = array_merge($after, $append); + } + unset($append); + + if (empty($before)) { + $before = null; + } + if (empty($after)) { + $after = null; + } + if (empty($types)) { + $types = null; + } + + if (empty($types) && empty($before) && empty($after)) { + return new EmptySchema($info, $keywordValidator); + } + + return new ObjectSchema($info, $keywordValidator, $types, $before, $after); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/VariablesTrait.php b/admin/vendor/opis/json-schema/src/Parsers/VariablesTrait.php new file mode 100644 index 0000000..6022fb9 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/VariablesTrait.php @@ -0,0 +1,41 @@ +option('varRefKey', '$ref'), + $parser->option('varEachKey', '$each'), + $parser->option('varDefaultKey', 'default') + ); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Parsers/Vocabulary.php b/admin/vendor/opis/json-schema/src/Parsers/Vocabulary.php new file mode 100644 index 0000000..0f83fe2 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Parsers/Vocabulary.php @@ -0,0 +1,126 @@ +keywords = $keywords; + $this->keywordValidators = $keywordValidators; + $this->pragmas = $pragmas; + } + + /** + * @return KeywordParser[] + */ + public function keywords(): array + { + return $this->keywords; + } + + /** + * @return KeywordValidatorParser[] + */ + public function keywordValidators(): array + { + return $this->keywordValidators; + } + + /** + * @return PragmaParser[] + */ + public function pragmas(): array + { + return $this->pragmas; + } + + /** + * @param KeywordParser $keyword + * @return Vocabulary + */ + public function appendKeyword(KeywordParser $keyword): self + { + $this->keywords[] = $keyword; + return $this; + } + + /** + * @param KeywordParser $keyword + * @return Vocabulary + */ + public function prependKeyword(KeywordParser $keyword): self + { + array_unshift($this->keywords, $keyword); + return $this; + } + + /** + * @param KeywordValidatorParser $keywordValidatorParser + * @return Vocabulary + */ + public function appendKeywordValidator(KeywordValidatorParser $keywordValidatorParser): self + { + $this->keywordValidators[] = $keywordValidatorParser; + return $this; + } + + /** + * @param KeywordValidatorParser $keywordValidator + * @return Vocabulary + */ + public function prependKeywordValidator(KeywordValidatorParser $keywordValidator): self + { + array_unshift($this->keywordValidators, $keywordValidator); + return $this; + } + + /** + * @param PragmaParser $pragma + * @return Vocabulary + */ + public function appendPragma(PragmaParser $pragma): self + { + $this->pragmas[] = $pragma; + return $this; + } + + /** + * @param PragmaParser $pragma + * @return Vocabulary + */ + public function prependPragma(PragmaParser $pragma): self + { + array_unshift($this->pragmas, $pragma); + return $this; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Pragma.php b/admin/vendor/opis/json-schema/src/Pragma.php new file mode 100644 index 0000000..fc8860b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Pragma.php @@ -0,0 +1,33 @@ +cast = $cast; + $this->func = $this->getCastFunction($cast); + } + + /** + * @inheritDoc + */ + public function enter(ValidationContext $context) + { + $currentType = $context->currentDataType(); + if ($currentType !== null && Helper::jsonTypeMatches($currentType, $this->cast)) { + // Cast not needed + return $this; + } + unset($currentType); + + $currentData = $context->currentData(); + + $context->setCurrentData(($this->func)($currentData)); + + return $currentData; + } + + /** + * @inheritDoc + */ + public function leave(ValidationContext $context, $data): void + { + if ($data !== $this) { + $context->setCurrentData($data); + } + } + + /** + * @param string $type + * @return callable + */ + protected function getCastFunction(string $type): callable + { + $f = 'toNull'; + + switch ($type) { + case 'integer': + $f = 'toInteger'; + break; + case 'number': + $f = 'toNumber'; + break; + case 'string': + $f = 'toString'; + break; + case 'array': + $f = 'toArray'; + break; + case 'object': + $f = 'toObject'; + break; + case 'boolean': + $f = 'toBoolean'; + break; + } + + return [$this, $f]; + } + + /** + * @param $value + * @return int|null + */ + public function toInteger($value): ?int + { + if ($value === null) { + return 0; + } + + return is_scalar($value) ? intval($value) : null; + } + + /** + * @param $value + * @return float|null + */ + public function toNumber($value): ?float + { + if ($value === null) { + return 0.0; + } + + return is_scalar($value) ? floatval($value) : null; + } + + /** + * @param $value + * @return string|null + */ + public function toString($value): ?string + { + if ($value === null) { + return ''; + } + + if (is_scalar($value)) { + return (string) $value; + } + + return null; + } + + /** + * @param $value + * @return array|null + */ + public function toArray($value): ?array + { + if ($value === null) { + return []; + } + + if (is_scalar($value)) { + return [$value]; + } + + if (is_array($value)) { + return array_values($value); + } + + if (is_object($value)) { + return array_values(get_object_vars($value)); + } + + return null; + } + + /** + * @param $value + * @return object|null + */ + public function toObject($value): ?object + { + if (is_object($value) || is_array($value)) { + return (object) $value; + } + + return null; + } + + /** + * @param $value + * @return bool + */ + public function toBoolean($value): bool + { + if ($value === null) { + return false; + } + if (is_string($value)) { + return !($value === ''); + } + if (is_object($value)) { + return count(get_object_vars($value)) > 0; + } + return boolval($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Pragmas/GlobalsPragma.php b/admin/vendor/opis/json-schema/src/Pragmas/GlobalsPragma.php new file mode 100644 index 0000000..e1233cf --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Pragmas/GlobalsPragma.php @@ -0,0 +1,61 @@ +globals = $globals; + } + + /** + * @inheritDoc + */ + public function enter(ValidationContext $context) + { + $resolved = (array) $this->globals->resolve($context->rootData(), $context->currentDataPath()); + if (!$resolved) { + return null; + } + + $data = $context->globals(); + $context->setGlobals($resolved, false); + return $data; + } + + /** + * @inheritDoc + */ + public function leave(ValidationContext $context, $data): void + { + if ($data === null) { + return; + } + $context->setGlobals($data, true); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Pragmas/MaxErrorsPragma.php b/admin/vendor/opis/json-schema/src/Pragmas/MaxErrorsPragma.php new file mode 100644 index 0000000..0d14626 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Pragmas/MaxErrorsPragma.php @@ -0,0 +1,55 @@ +maxErrors = $maxErrors; + } + + /** + * @inheritDoc + */ + public function enter(ValidationContext $context) + { + $data = $context->maxErrors(); + $context->setMaxErrors($this->maxErrors); + return $data; + } + + /** + * @inheritDoc + */ + public function leave(ValidationContext $context, $data): void + { + if ($data === null) { + return; + } + $context->setMaxErrors($data); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Pragmas/SlotsPragma.php b/admin/vendor/opis/json-schema/src/Pragmas/SlotsPragma.php new file mode 100644 index 0000000..7806a75 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Pragmas/SlotsPragma.php @@ -0,0 +1,52 @@ +slots = $slots; + } + + /** + * @inheritDoc + */ + public function enter(ValidationContext $context) + { + $data = $context->slots(); + $context->setSlots($data ? $this->slots + $data : $this->slots); + return $data; + } + + /** + * @inheritDoc + */ + public function leave(ValidationContext $context, $data): void + { + $context->setSlots($data); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Resolvers/ContentEncodingResolver.php b/admin/vendor/opis/json-schema/src/Resolvers/ContentEncodingResolver.php new file mode 100644 index 0000000..8c49b83 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Resolvers/ContentEncodingResolver.php @@ -0,0 +1,134 @@ + self::class . '::DecodeBinary', + 'base64' => self::class . '::DecodeBase64', + 'quoted-printable' => self::class . '::DecodeQuotedPrintable', + ]; + + $this->list = $list; + $this->defaultEncoding = $defaultEncoding; + } + + /** + * @param string $name + * @return callable|ContentEncoding|string|null + */ + public function resolve(string $name) + { + return $this->list[$name] ?? $this->defaultEncoding; + } + + /** + * @param string $name + * @param ContentEncoding $encoding + * @return ContentEncodingResolver + */ + public function register(string $name, ContentEncoding $encoding): self + { + $this->list[$name] = $encoding; + + return $this; + } + + /** + * @param string $name + * @param callable $encoding + * @return ContentEncodingResolver + */ + public function registerCallable(string $name, callable $encoding): self + { + $this->list[$name] = $encoding; + + return $this; + } + + /** + * @param string $name + * @return bool + */ + public function unregister(string $name): bool + { + if (isset($this->list[$name])) { + unset($this->list[$name]); + + return true; + } + + return false; + } + + /** + * @param callable|ContentEncoding|null $handler + * @return $this + */ + public function setDefaultHandler($handler): self + { + $this->defaultEncoding = $handler; + return $this; + } + + public function __serialize(): array + { + return [ + 'list' => $this->list, + 'defaultEncoding' => $this->defaultEncoding, + ]; + } + + public function __unserialize(array $data): void + { + $this->list = $data['list']; + $this->defaultEncoding = $data['defaultEncoding'] ?? null; + } + + public static function DecodeBinary(string $value): ?string + { + return $value; + } + + public static function DecodeBase64(string $value): ?string + { + $value = base64_decode($value, true); + + return is_string($value) ? $value : null; + } + + public static function DecodeQuotedPrintable(string $value): ?string + { + return quoted_printable_decode($value); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php b/admin/vendor/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php new file mode 100644 index 0000000..2b95799 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php @@ -0,0 +1,145 @@ + self::class . '::IsJsonEncoded', + ]; + + $this->media = $media; + $this->defaultMedia = $defaultMedia ?? self::class . '::IsEncodedAsType'; + } + + /** + * @param string $name + * @return callable|ContentMediaType|string|null + */ + public function resolve(string $name) + { + return $this->media[$name] ?? $this->defaultMedia; + } + + /** + * @param string $name + * @param ContentMediaType $media + * @return ContentMediaTypeResolver + */ + public function register(string $name, ContentMediaType $media): self + { + $this->media[$name] = $media; + + return $this; + } + + /** + * @param string $name + * @param callable $media + * @return ContentMediaTypeResolver + */ + public function registerCallable(string $name, callable $media): self + { + $this->media[$name] = $media; + + return $this; + } + + /** + * @param string $name + * @return bool + */ + public function unregister(string $name): bool + { + if (isset($this->media[$name])) { + unset($this->media[$name]); + + return true; + } + + return false; + } + + /** + * @param callable|ContentMediaType|null $handler + * @return ContentMediaTypeResolver + */ + public function setDefaultHandler($handler): self + { + $this->defaultMedia = $handler; + + return $this; + } + + public function __serialize(): array + { + return [ + 'media' => $this->media, + 'defaultMedia' => $this->defaultMedia, + ]; + } + + public function __unserialize(array $data): void + { + $this->media = $data['media']; + $this->defaultMedia = $data['defaultMedia']; + } + + public static function IsJsonEncoded(string $value, + /** @noinspection PhpUnusedParameterInspection */ string $type): bool + { + json_decode($value); + + return json_last_error() === JSON_ERROR_NONE; + } + + public static function IsEncodedAsType(string $value, string $type): bool + { + /** @var finfo|null|bool $finfo */ + static $finfo = false; + + if ($finfo === false) { + if (!class_exists(finfo::class)) { + $finfo = null; + return false; + } + $finfo = new finfo(FILEINFO_MIME_TYPE); + } elseif (!$finfo) { + return false; + } + + $r = $finfo->buffer($value); + + return $r == $type || $r == 'application/x-empty'; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Resolvers/FilterResolver.php b/admin/vendor/opis/json-schema/src/Resolvers/FilterResolver.php new file mode 100644 index 0000000..d3687ef --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Resolvers/FilterResolver.php @@ -0,0 +1,265 @@ +separator = $ns_separator; + $this->defaultNS = $default_ns; + + $this->registerDefaultFilters(); + } + + /** + * You can override this to add/remove default filters + */ + protected function registerDefaultFilters(): void + { + $this->registerMultipleTypes("schema-exists", new SchemaExistsFilter()); + $this->registerMultipleTypes("data-exists", new DataExistsFilter()); + $this->registerMultipleTypes("global-exists", new GlobalVarExistsFilter()); + $this->registerMultipleTypes("slot-exists", new SlotExistsFilter()); + $this->registerMultipleTypes("filter-exists", new FilterExistsFilter()); + $this->registerMultipleTypes("format-exists", new FormatExistsFilter()); + + $cls = DateTimeFilters::class . "::"; + $this->registerCallable("string", "min-date", $cls . "MinDate"); + $this->registerCallable("string", "max-date", $cls . "MaxDate"); + $this->registerCallable("string", "not-date", $cls . "NotDate"); + $this->registerCallable("string", "min-time", $cls . "MinTime"); + $this->registerCallable("string", "max-time", $cls . "MaxTime"); + $this->registerCallable("string", "min-datetime", $cls . "MinDateTime"); + $this->registerCallable("string", "max-datetime", $cls . "MaxDateTime"); + + $cls = CommonFilters::class . "::"; + $this->registerCallable("string", "regex", $cls . "Regex"); + $this->registerMultipleTypes("equals", $cls . "Equals"); + } + + + /** + * @param string $name + * @param string $type + * @return Filter|callable|null + */ + public function resolve(string $name, string $type) + { + [$ns, $name] = $this->parseName($name); + + if (isset($this->filters[$ns][$name])) { + return $this->filters[$ns][$name][$type] ?? null; + } + + if (!isset($this->ns[$ns])) { + return null; + } + + $this->filters[$ns][$name] = $this->ns[$ns]->resolveAll($name); + + return $this->filters[$ns][$name][$type] ?? null; + } + + /** + * @param string $name + * @return Filter[]|callable[]|null + */ + public function resolveAll(string $name): ?array + { + [$ns, $name] = $this->parseName($name); + + if (isset($this->filters[$ns][$name])) { + return $this->filters[$ns][$name]; + } + + if (!isset($this->ns[$ns])) { + return null; + } + + return $this->filters[$ns][$name] = $this->ns[$ns]->resolveAll($name); + } + + /** + * @param string $type + * @param string $name + * @param Filter $filter + * @return FilterResolver + */ + public function register(string $type, string $name, Filter $filter): self + { + [$ns, $name] = $this->parseName($name); + + $this->filters[$ns][$name][$type] = $filter; + + return $this; + } + + /** + * @param string $name + * @param string|null $type + * @return bool + */ + public function unregister(string $name, ?string $type = null): bool + { + [$ns, $name] = $this->parseName($name); + if (!isset($this->filters[$ns][$name])) { + return false; + } + + if ($type === null) { + unset($this->filters[$ns][$name]); + + return true; + } + + if (isset($this->filters[$ns][$name][$type])) { + unset($this->filters[$ns][$name][$type]); + + return true; + } + + return false; + } + + /** + * @param string $name + * @param callable|Filter $filter + * @param array|null $types + * @return FilterResolver + */ + public function registerMultipleTypes(string $name, $filter, ?array $types = null): self + { + [$ns, $name] = $this->parseName($name); + + $types = $types ?? Helper::JSON_TYPES; + + foreach ($types as $type) { + $this->filters[$ns][$name][$type] = $filter; + } + + return $this; + } + + /** + * @param string $type + * @param string $name + * @param callable $filter + * @return FilterResolver + */ + public function registerCallable(string $type, string $name, callable $filter): self + { + [$ns, $name] = $this->parseName($name); + + $this->filters[$ns][$name][$type] = $filter; + + return $this; + } + + /** + * @param string $ns + * @param FilterResolver $resolver + * @return FilterResolver + */ + public function registerNS(string $ns, FilterResolver $resolver): self + { + $this->ns[$ns] = $resolver; + + return $this; + } + + /** + * @param string $ns + * @return bool + */ + public function unregisterNS(string $ns): bool + { + if (isset($this->filters[$ns])) { + unset($this->filters[$ns]); + unset($this->ns[$ns]); + + return true; + } + + if (isset($this->ns[$ns])) { + unset($this->ns[$ns]); + + return true; + } + + return false; + } + + public function __serialize(): array + { + return [ + 'separator' => $this->separator, + 'defaultNS' => $this->defaultNS, + 'ns' => $this->ns, + 'filters' => $this->filters, + ]; + } + + public function __unserialize(array $data): void + { + $this->separator = $data['separator']; + $this->defaultNS = $data['defaultNS']; + $this->ns = $data['ns']; + $this->filters = $data['filters']; + } + + /** + * @param string $name + * @return array + */ + protected function parseName(string $name): array + { + $name = strtolower($name); + + if (strpos($name, $this->separator) === false) { + return [$this->defaultNS, $name]; + } + + return explode($this->separator, $name, 2); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Resolvers/FormatResolver.php b/admin/vendor/opis/json-schema/src/Resolvers/FormatResolver.php new file mode 100644 index 0000000..61a3fdc --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Resolvers/FormatResolver.php @@ -0,0 +1,143 @@ +formats = [ + 'string' => [ + 'date' => DateTimeFormats::class . '::date', + 'time' => DateTimeFormats::class . '::time', + 'date-time' => DateTimeFormats::class . '::dateTime', + 'duration' => DateTimeFormats::class . '::duration', + + 'uri' => UriFormats::class . '::uri', + 'uri-reference' => UriFormats::class . '::uriReference', + 'uri-template' => UriFormats::class . '::uriTemplate', + + 'regex' => Helper::class . '::isValidPattern', + 'ipv4' => MiscFormats::class . '::ipv4', + 'ipv6' => MiscFormats::class . '::ipv6', + 'uuid' => MiscFormats::class . '::uuid', + + 'email' => MiscFormats::class . '::email', + 'hostname' => Uri::class . '::isValidHost', + + 'json-pointer' => JsonPointer::class . '::isAbsolutePointer', + 'relative-json-pointer' => JsonPointer::class . '::isRelativePointer', + + 'idn-hostname' => IriFormats::class . '::idnHostname', + 'idn-email' => IriFormats::class . '::idnEmail', + 'iri' => IriFormats::class . '::iri', + 'iri-reference' => IriFormats::class . '::iriReference', + ], + ]; + } + + /** + * @param string $name + * @param string $type + * @return callable|Format|null + */ + public function resolve(string $name, string $type) + { + return $this->formats[$type][$name] ?? null; + } + + /** + * @param string $name + * @return Format[]|callable[]|null + */ + public function resolveAll(string $name): ?array + { + $list = null; + + foreach ($this->formats as $type => $items) { + if (isset($items[$name])) { + $list[$type] = $items[$name]; + } + } + + return $list; + } + + /** + * @param string $type + * @param string $name + * @param Format $format + * @return FormatResolver + */ + public function register(string $type, string $name, Format $format): self + { + $this->formats[$type][$name] = $format; + + return $this; + } + + /** + * @param string $type + * @param string $name + * @param callable $format + * @return FormatResolver + */ + public function registerCallable(string $type, string $name, callable $format): self + { + $this->formats[$type][$name] = $format; + + return $this; + } + + /** + * @param string $type + * @param string $name + * @return bool + */ + public function unregister(string $type, string $name): bool + { + if (isset($this->formats[$type][$name])) { + unset($this->formats[$type][$name]); + + return true; + } + + return false; + } + + public function __serialize(): array + { + return ['formats' => $this->formats]; + } + + public function __unserialize(array $data): void + { + $this->formats = $data['formats']; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Resolvers/SchemaResolver.php b/admin/vendor/opis/json-schema/src/Resolvers/SchemaResolver.php new file mode 100644 index 0000000..93c7d0a --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Resolvers/SchemaResolver.php @@ -0,0 +1,342 @@ +isAbsolute()) { + return null; + } + + $scheme = $uri->scheme(); + if (isset($this->protocols[$scheme])) { + return ($this->protocols[$scheme])($uri); + } + + $id = (string) $uri; + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + $path = $this->resolvePath($uri); + + if ($path === null || !is_file($path)) { + return null; + } + + $data = file_get_contents($path); + if (!is_string($data)) { + return null; + } + + $data = json_decode($data, false); + + return $data; + } + + /** + * @param bool|object|string $schema + * @param string|null $id + * @return bool + */ + public function registerRaw($schema, ?string $id = null): bool + { + if (is_string($schema)) { + $schema = json_decode($schema, false); + } + + if ($id !== null && strpos($id, '#') === false) { + $id .= '#'; + } + + if (is_bool($schema)) { + if ($id === null) { + return false; + } + $this->raw[$id] = $schema; + return true; + } + + if (!is_object($schema)) { + return false; + } + + + if ($id === null) { + if (!isset($schema->{'$id'}) || !is_string($schema->{'$id'})) { + return false; + } + + $id = $schema->{'$id'}; + if (strpos($id, '#') === false) { + $id .= '#'; + } + } + + $this->raw[$id] = $schema; + + return true; + } + + /** + * @param string $id + * @return bool + */ + public function unregisterRaw(string $id): bool + { + if (strpos($id, '#') === false) { + $id .= '#'; + } + + if (isset($this->raw[$id])) { + unset($this->raw[$id]); + return true; + } + + return false; + } + + /** + * @param string $id + * @param string $file + * @return SchemaResolver + */ + public function registerFile(string $id, string $file): self + { + if (strpos($id, '#') === false) { + $id .= '#'; + } + + $this->files[$id] = $file; + + return $this; + } + + /** + * @param string $id + * @return bool + */ + public function unregisterFile(string $id): bool + { + if (strpos($id, '#') === false) { + $id .= '#'; + } + + if (!isset($this->files[$id])) { + return false; + } + + unset($this->files[$id]); + + return true; + } + + /** + * @param string $scheme + * @param callable $handler + * @return SchemaResolver + */ + public function registerProtocol(string $scheme, callable $handler): self + { + $this->protocols[$scheme] = $handler; + + return $this; + } + + /** + * @param string $scheme + * @return bool + */ + public function unregisterProtocol(string $scheme): bool + { + if (isset($this->protocols[$scheme])) { + unset($this->protocols[$scheme]); + + return true; + } + + return false; + } + + /** + * @param string $scheme + * @param string $host + * @param string|null $dir + * @return SchemaResolver + */ + public function registerProtocolDir(string $scheme, string $host, ?string $dir): self + { + if ($dir === null) { + unset($this->dirs[$scheme][$host]); + } else { + $this->dirs[$scheme][$host] = rtrim($dir, '/'); + } + + return $this; + } + + /** + * @param string $scheme + * @return bool + */ + public function unregisterProtocolDirs(string $scheme): bool + { + if (isset($this->dirs[$scheme])) { + unset($this->dirs[$scheme]); + + return true; + } + + return false; + } + + /** + * @param string $prefix + * @param string $dir + * @return SchemaResolver + */ + public function registerPrefix(string $prefix, string $dir): self + { + $this->prefixes[$prefix] = rtrim($dir, '/'); + + // Sort + uksort($this->prefixes, [$this, 'sortPrefixKeys']); + + return $this; + } + + /** + * @param string $prefix + * @return SchemaResolver + */ + public function unregisterPrefix(string $prefix): self + { + if (isset($this->prefixes[$prefix])) { + unset($this->prefixes[$prefix]); + // Sort + uksort($this->prefixes, [$this, 'sortPrefixKeys']); + } + + return $this; + } + + + public function __serialize(): array + { + return [ + 'raw' => $this->raw, + 'protocols' => $this->protocols, + 'prefixes' => $this->prefixes, + 'dirs' => $this->dirs, + ]; + } + + public function __unserialize(array $data): void + { + $this->raw = $data['raw']; + $this->protocols = $data['protocols']; + $this->prefixes = $data['prefixes']; + $this->dirs = $data['dirs']; + } + + /** + * @param string $a + * @param string $b + * @return int + */ + protected function sortPrefixKeys(string $a, string $b): int + { + $la = strlen($a); + $lb = strlen($b); + + if ($lb > $la) { + return 1; + } + + if ($lb === $la) { + return $b < $a ? 1 : ($b === $a ? 0 : -1); + } + + return -1; + } + + /** + * @param Uri $uri + * @return string|null + */ + protected function resolvePath(Uri $uri): ?string + { + $id = (string)$uri; + + if (isset($this->files[$id])) { + return $this->files[$id]; + } + + $scheme = $uri->scheme(); + + if (isset($this->dirs[$scheme])) { + $host = (string)$uri->host(); + if (isset($this->dirs[$scheme][$host])) { + return $this->dirs[$scheme][$host] . '/' . ltrim($uri->path(), '/'); + } + unset($host); + } + + $path = null; + foreach ($this->prefixes as $prefix => $dir) { + if ($prefix === '' || strpos($id, $prefix) === 0) { + $path = substr($id, strlen($prefix)); + if ($path === false || $path === '') { + $path = null; + continue; + } + $path = Uri::parseComponents($path); + if ($path && isset($path['path'])) { + $path = $dir . '/' . ltrim($path['path'], '/'); + break; + } + $path = null; + } + } + + return $path; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schema.php b/admin/vendor/opis/json-schema/src/Schema.php new file mode 100644 index 0000000..821a6a0 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schema.php @@ -0,0 +1,28 @@ +dataCache = new SplObjectStorage(); + $this->parser = $parser ?? new SchemaParser(); + $this->resolver = $resolver; + $this->decodeJsonString = $decodeJsonString; + } + + public function baseUri(): ?Uri + { + return $this->base; + } + + public function setBaseUri(?Uri $uri): self + { + $this->base = $uri; + return $this; + } + + public function parser(): SchemaParser + { + return $this->parser; + } + + public function setParser(SchemaParser $parser): self + { + $this->parser = $parser; + + return $this; + } + + public function resolver(): ?SchemaResolver + { + return $this->resolver; + } + + public function setResolver(?SchemaResolver $resolver): self + { + $this->resolver = $resolver; + + return $this; + } + + /** + * @param object $data + * @param null $id + * @param string|null $draft + * @return Schema + */ + public function loadObjectSchema(object $data, $id = null, ?string $draft = null): Schema + { + // Check if already loaded + if ($schema = $this->checkExistingObject($data)) { + return $schema; + } + + if (!$id) { + $id = $this->createSchemaId($data); + } + + $handle_id = fn (Uri $id): ?Schema => $this->checkExistingUri($id); + + $handle_object = function (object $data, Uri $id, string $draft): ?Schema { + $this->handleObject($data, $id, null, null, [], $draft, (string)$id); + + return $this->checkExistingObject($data); + }; + + return $this->parser->parseRootSchema($data, Uri::parse($id, true), $handle_id, $handle_object, $draft); + } + + /** + * @param bool $data + * @param null $id + * @param string|null $draft + * @return Schema + */ + public function loadBooleanSchema(bool $data, $id = null, ?string $draft = null): Schema + { + if (!$id) { + $id = $this->createSchemaId($data); + } + + return $this->parser->parseSchema(new SchemaInfo($data, Uri::parse($id, true), null, null, [], $draft)); + } + + /** + * @param Uri $uri + * @return Schema|null + */ + public function loadSchemaById(Uri $uri): ?Schema + { + if (!$uri->isAbsolute()) { + if ($this->base === null || !$this->base->isAbsolute()) { + return null; + } + $uri = $this->base->resolveRef($uri); + } + + $fragment = $uri->fragment(); + if ($fragment === null) { + $uri = Uri::merge($uri, null, true); + $fragment = ''; + } + + $schema = $this->checkExistingUri($uri); + + if ($schema !== null) { + return $schema; + } + + if ($fragment === '') { + return $this->resolve($uri); + } + + $root = Uri::merge('#', $uri); + + // Check if already resolved + if (($schema = $this->checkExistingUri($root)) === null) { + // Try to resolve + if (($schema = $this->resolve($root)) === null) { + // Schema not found + return null; + } + } + + // Resolve json pointer + if ($fragment !== '' && $schema && $schema->info()->isObject() && + ($pointer = JsonPointer::parse($fragment)) && $pointer->isAbsolute()) { + $object = $pointer->data($schema->info()->data()); + if (is_bool($object)) { + $schema = $this->loadBooleanSchema($object, $uri, $schema->info()->draft()); + } elseif (is_object($object)) { + $schema = $this->loadObjectSchema($object, $uri, $schema->info()->draft()); + } else { + $schema = null; + } + if ($schema) { + $key = $this->cacheKey((string) $uri); + $this->uriCache[$key] = $schema; + return $schema; + } + } + + // Check fragment + return $this->checkExistingUri($uri); + } + + /** + * Clears internal cache + */ + public function clearCache(): void + { + $this->dataCache->removeAll($this->dataCache); + $this->uriCache = []; + } + + /** + * @param Uri $uri + * @return null|Schema + */ + protected function resolve(Uri $uri): ?Schema + { + if ($this->resolver === null) { + return null; + } + + $data = $this->resolver->resolve($uri); + + if ($this->decodeJsonString && is_string($data)) { + $data = json_decode($data, false); + } + + if (is_bool($data)) { + $this->handleBoolean($data, $uri, null, null, [], $this->parser->defaultDraftVersion(), (string)$uri); + + return $this->checkExistingUri($uri); + } + + if (is_object($data)) { + if ($data instanceof Schema) { + return $data; + } + + $this->handleObject($data, $uri, null, null, [], $this->parser->defaultDraftVersion(), (string)$uri); + + return $this->checkExistingObject($data); + } + + return null; + } + + /** + * @param object $data + * @return null|Schema + */ + protected function checkExistingObject(object $data): ?Schema + { + if (!$this->dataCache->offsetExists($data)) { + return null; + } + + $schema = $this->dataCache[$data]; + + if ($schema instanceof LazySchema) { + $schema = $schema->schema(); + $this->dataCache[$data] = $schema; + } elseif (!($schema instanceof Schema)) { + $schema = null; + } + + return $schema; + } + + /** + * @param Uri $uri + * @return null|Schema + */ + protected function checkExistingUri(Uri $uri): ?Schema + { + if ($uri->fragment() === null || !$uri->isAbsolute()) { + return null; + } + + $key = $this->cacheKey((string)$uri); + + if (!isset($this->uriCache[$key])) { + return null; + } + + $schema = $this->uriCache[$key]; + + if (!($schema instanceof Schema)) { + return $this->uriCache[$key] = $this->checkExistingObject($schema); + } + + if ($schema instanceof LazySchema) { + $schema = $schema->schema(); + $this->uriCache[$key] = $schema; + } + + return $schema; + } + + /** + * @param bool $data + * @param Uri|null $id + * @param Uri|null $base + * @param Uri|null $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleBoolean( + bool $data, + ?Uri $id, + ?Uri $base, + ?Uri $root, + array $path, + string $draft, + string $pointer + ) + { + $key = $this->cacheKey($pointer); + if (isset($this->uriCache[$key])) { + return; + } + + $this->uriCache[$key] = $this->parser->parseSchema(new SchemaInfo($data, $id, $base, $root, $path, $draft)); + } + + /** + * @param array $data + * @param Uri $base + * @param Uri $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleArray(array $data, Uri $base, Uri $root, array $path, string $draft, string $pointer) + { + foreach ($data as $key => $value) { + if (!is_int($key)) { + continue; + } + + if (is_bool($value)) { + $this->handleBoolean($value, null, $base, $root, array_merge($path, [$key]), $draft, + $pointer . '/' . $key); + } elseif (is_array($value)) { + $this->handleArray($value, $base, $root, array_merge($path, [$key]), $draft, $pointer . '/' . $key); + } elseif (is_object($value)) { + $this->handleObject($value, null, $base, $root, array_merge($path, [$key]), $draft, + $pointer . '/' . $key); + } + } + } + + /** + * @param object $data + * @param Uri|null $id + * @param Uri|null $base + * @param Uri|null $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleObject( + object $data, + ?Uri $id, + ?Uri $base, + ?Uri $root, + array $path, + string $draft, + string $pointer + ) + { + $schema_id = $this->parser->parseId($data); + $schema_anchor = $this->parser->parseAnchor($data, $draft); + $draft = $this->parser->parseSchemaDraft($data) ?? $draft; + + if ($schema_id !== null) { + $id = Uri::merge($schema_id, $base, true); + } elseif ($schema_anchor !== null) { + $id = Uri::merge('#' . $schema_anchor, $base, true); + } + + $lazy = new LazySchema(new SchemaInfo($data, $id, $base, $root, $path, $draft), $this->parser); + + if ($id && $id->isAbsolute()) { + $key = $this->cacheKey((string)$id); + if (isset($this->uriCache[$key])) { + throw new DuplicateSchemaIdException($id, $data); + } + $this->uriCache[$key] = $lazy; + } + + // When $id and $anchor are both present add a reference to the same lazy object + if ($schema_id !== null && $schema_anchor !== null) { + $anchor_id = Uri::merge('#' . $schema_anchor, $id, true); + $key = $this->cacheKey((string)$anchor_id); + if (isset($this->uriCache[$key])) { + throw new DuplicateSchemaIdException($anchor_id, $data); + } + $this->uriCache[$key] = $lazy; + } + + $this->dataCache[$data] = $lazy; + $this->uriCache[$this->cacheKey($pointer)] = $lazy; + + if ($root === null) { + $root = $id; + } + + if ($base === null) { + $base = $id ?? $root; + } elseif ($id !== null) { + $base = $id; + } + + foreach ($data as $key => $value) { + if (!is_string($key)) { + continue; + } + if (is_bool($value)) { + $this->handleBoolean($value, null, $base, $root, array_merge($path, [$key]), $draft, + $pointer . '/' . JsonPointer::encodePath($key)); + } elseif (is_array($value)) { + $this->handleArray($value, $base, $root, array_merge($path, [$key]), $draft, + $pointer . '/' . JsonPointer::encodePath($key)); + } elseif (is_object($value)) { + $this->handleObject($value, null, $base, $root, array_merge($path, [$key]), $draft, + $pointer . '/' . JsonPointer::encodePath($key)); + } + } + } + + /** + * @param string $path + * @return string + */ + protected function cacheKey(string $path): string + { + return isset($path[32]) ? md5($path) : $path; + } + + /** + * @param bool|object $data + * @return string + */ + protected function createSchemaId($data): string + { + if (is_bool($data)) { + $data = $data ? 'true' : 'false'; + } else { + $data = spl_object_hash($data); + } + + return "schema:///{$data}.json"; + } +} diff --git a/admin/vendor/opis/json-schema/src/SchemaValidator.php b/admin/vendor/opis/json-schema/src/SchemaValidator.php new file mode 100644 index 0000000..a8b6e3e --- /dev/null +++ b/admin/vendor/opis/json-schema/src/SchemaValidator.php @@ -0,0 +1,29 @@ +info = $info; + } + + /** + * @inheritDoc + */ + public function info(): SchemaInfo + { + return $this->info; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schemas/BooleanSchema.php b/admin/vendor/opis/json-schema/src/Schemas/BooleanSchema.php new file mode 100644 index 0000000..2c3a209 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schemas/BooleanSchema.php @@ -0,0 +1,49 @@ +data = $info->data(); + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + if ($this->data) { + return null; + } + + return new ValidationError('', $this, DataInfo::fromContext($context), 'Data not allowed'); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schemas/EmptySchema.php b/admin/vendor/opis/json-schema/src/Schemas/EmptySchema.php new file mode 100644 index 0000000..677f6b8 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schemas/EmptySchema.php @@ -0,0 +1,53 @@ +keywordValidator = $keywordValidator; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + if (!$this->keywordValidator) { + return null; + } + + $context->pushSharedObject($this); + $error = $this->keywordValidator->validate($context); + $context->popSharedObject(); + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schemas/ExceptionSchema.php b/admin/vendor/opis/json-schema/src/Schemas/ExceptionSchema.php new file mode 100644 index 0000000..9674597 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schemas/ExceptionSchema.php @@ -0,0 +1,48 @@ +exception = $exception; + } + + /** + * @inheritDoc + * @throws SchemaException + */ + public function validate(ValidationContext $context): ?ValidationError + { + throw $this->exception; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schemas/LazySchema.php b/admin/vendor/opis/json-schema/src/Schemas/LazySchema.php new file mode 100644 index 0000000..254d9bf --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schemas/LazySchema.php @@ -0,0 +1,60 @@ +parser = $parser; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + return $this->schema()->validate($context); + } + + /** + * @return Schema + */ + public function schema(): Schema + { + if ($this->schema === null) { + $this->schema = $this->parser->parseSchema($this->info); + } + + return $this->schema; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Schemas/ObjectSchema.php b/admin/vendor/opis/json-schema/src/Schemas/ObjectSchema.php new file mode 100644 index 0000000..1b5da2c --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Schemas/ObjectSchema.php @@ -0,0 +1,148 @@ +types = $types; + $this->before = $before; + $this->after = $after; + $this->keywordValidator = $keywordValidator; + + if ($keywordValidator) { + while ($next = $keywordValidator->next()) { + $keywordValidator = $next; + } + $keywordValidator->setNext(new CallbackKeywordValidator([$this, 'doValidate'])); + } + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context): ?ValidationError + { + $context->pushSharedObject($this); + $error = $this->keywordValidator ? $this->keywordValidator->validate($context) : $this->doValidate($context); + $context->popSharedObject(); + + return $error; + } + + /** + * @param ValidationContext $context + * @return null|ValidationError + *@internal + */ + public function doValidate(ValidationContext $context): ?ValidationError + { + if ($this->before && ($error = $this->applyKeywords($this->before, $context))) { + return $error; + } + + if ($this->types && ($type = $context->currentDataType())) { + if (isset($this->types[$type]) && ($error = $this->applyKeywords($this->types[$type], $context))) { + return $error; + } + + if (($type = Helper::getJsonSuperType($type)) && isset($this->types[$type])) { + if ($error = $this->applyKeywords($this->types[$type], $context)) { + return $error; + } + } + + unset($type); + } + + if ($this->after && ($error = $this->applyKeywords($this->after, $context))) { + return $error; + } + + return null; + } + + /** + * @param Keyword[] $keywords + * @param ValidationContext $context + * @return ValidationError|null + */ + protected function applyKeywords(array $keywords, ValidationContext $context): ?ValidationError + { + if ($context->stopAtFirstError()) { + foreach ($keywords as $keyword) { + if ($error = $keyword->validate($context, $this)) { + return $error; + } + } + return null; + } + + /** @var null|ValidationError[] $error_list */ + $error_list = null; + + foreach ($keywords as $keyword) { + if ($error = $keyword->validate($context, $this)) { + $error_list ??= []; + $error_list[] = $error; + } + } + + if (!$error_list) { + return null; + } + + if (count($error_list) === 1) { + return $error_list[0]; + } + + return new ValidationError( + '', + $this, + DataInfo::fromContext($context), + 'Data must match schema', + [], + $error_list + ); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Uri.php b/admin/vendor/opis/json-schema/src/Uri.php new file mode 100644 index 0000000..3d6802f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Uri.php @@ -0,0 +1,94 @@ +__toString(); + } + + /** + * @param string $uri + * @param bool $ensure_fragment + * @return static|null + */ + public static function parse(string $uri, bool $ensure_fragment = false): ?self + { + if ($ensure_fragment && strpos($uri, '#') === false) { + $uri .= '#'; + } + + return self::create($uri); + } + + /** + * @param string|array|static $uri + * @param string|array|static $base + * @param bool $ensure_fragment + * @return static|null + */ + public static function merge($uri, $base, bool $ensure_fragment = false): ?self + { + $uri = self::resolveComponents($uri); + + if ($uri === null) { + return null; + } + + if ($ensure_fragment && !isset($uri['fragment'])) { + $uri['fragment'] = ''; + } + + $base = self::resolveComponents($base); + + if (!$base) { + return new self($uri); + } + + return new self(self::mergeComponents($uri, $base)); + } + + /** + * @param bool $value + */ + public static function useNormalizedComponents(bool $value): void + { + self::$useNormalizedComponents = $value; + } +} diff --git a/admin/vendor/opis/json-schema/src/ValidationContext.php b/admin/vendor/opis/json-schema/src/ValidationContext.php new file mode 100644 index 0000000..8918f1b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/ValidationContext.php @@ -0,0 +1,715 @@ +sender = $sender; + $this->rootData = $data; + $this->loader = $loader; + $this->parent = $parent; + $this->globals = $globals; + $this->slots = null; + $this->maxErrors = $max_errors; + $this->stopAtFirstError = $stop_at_first_error; + $this->currentData = [ + [$data, false], + ]; + + if ($slots) { + $this->setSlots($slots); + } + } + + /** + * @param $data + * @param Schema|null $sender + * @param array|null $globals + * @param array|null $slots + * @param int|null $max_errors + * @return self + */ + public function newInstance( + $data, + ?Schema $sender, + ?array $globals = null, + ?array $slots = null, + ?int $max_errors = null, + ?bool $stop_at_first_error = null + ): self { + return new self( + $data, + $this->loader, + $this, + $sender, + $globals ?? $this->globals, + $slots ?? $this->slots, + $max_errors ?? $this->maxErrors, + $stop_at_first_error ?? $this->stopAtFirstError + ); + } + + public function create( + Schema $sender, + ?Variables $mapper = null, + ?Variables $globals = null, + ?array $slots = null, + ?int $maxErrors = null, + ?bool $stop_at_first_error = null + ): self { + if ($globals) { + $globals = $globals->resolve($this->rootData(), $this->currentDataPath()); + if (!is_array($globals)) { + $globals = (array)$globals; + } + $globals += $this->globals; + } else { + $globals = $this->globals; + } + + if ($mapper) { + $data = $mapper->resolve($this->rootData(), $this->currentDataPath()); + } else { + $data = $this->currentData(); + } + + return new self($data, $this->loader, $this, $sender, $globals, $slots ?? $this->slots, + $maxErrors ?? $this->maxErrors, $stop_at_first_error ?? $this->stopAtFirstError); + } + + public function sender(): ?Schema + { + return $this->sender; + } + + /** + * @return self|null + */ + public function parent(): ?self + { + return $this->parent; + } + + /** + * @return SchemaLoader + */ + public function loader(): SchemaLoader + { + return $this->loader; + } + + /** + * @return mixed + */ + public function rootData() + { + return $this->rootData; + } + + /** + * @return mixed + */ + public function currentData() + { + return $this->currentData[$this->pathIndex][0]; + } + + /** + * @param $value + */ + public function setCurrentData($value): void + { + $this->currentData[$this->pathIndex][0] = $value; + $this->currentData[$this->pathIndex][1] = false; + } + + /** + * @return string|null + */ + public function currentDataType(): ?string + { + $type = $this->currentData[$this->pathIndex][1]; + if ($type === false) { + $type = Helper::getJsonType($this->currentData[$this->pathIndex][0]); + $this->currentData[$this->pathIndex][1] = $type; + } + + return $type; + } + + public function fullDataPath(): array + { + if ($this->fullPath === null) { + if ($this->parent === null) { + return $this->currentDataPath; + } + $this->fullPath = array_merge($this->parent->fullDataPath(), $this->currentDataPath); + } + + return $this->fullPath; + } + + /** + * @return int[]|string[] + */ + public function currentDataPath(): array + { + return $this->currentDataPath; + } + + /** + * @param string|int $key + * @return $this + */ + public function pushDataPath($key): self + { + $this->currentDataPath[] = $key; + if ($this->fullPath !== null) { + $this->fullPath[] = $key; + } + + $data = $this->currentData[$this->pathIndex][0]; + + if (is_array($data)) { + $data = $data[$key] ?? null; + } elseif (is_object($data)) { + $data = $data->{$key} ?? null; + } else { + $data = null; + } + + $this->currentData[] = [$data, false]; + $this->pathIndex++; + + return $this; + } + + /** + * @return $this + */ + public function popDataPath(): self + { + if ($this->pathIndex < 1) { + return $this; + } + + if ($this->fullPath !== null) { + array_pop($this->fullPath); + } + array_pop($this->currentDataPath); + array_pop($this->currentData); + $this->pathIndex--; + + return $this; + } + + /** + * @return array + */ + public function globals(): array + { + return $this->globals; + } + + /** + * @param array $globals + * @param bool $overwrite + * @return $this + */ + public function setGlobals(array $globals, bool $overwrite = false): self + { + if ($overwrite) { + $this->globals = $globals; + } elseif ($globals) { + $this->globals = $globals + $this->globals; + } + + return $this; + } + + /** + * @return object[]|Schema[]|string[]|null + */ + public function slots(): ?array + { + return $this->slots; + } + + /** + * @param array|null $slots + * @return $this + */ + public function setSlots(?array $slots): self + { + if ($slots) { + $list = []; + + foreach ($slots as $name => $value) { + if (is_bool($value)) { + $value = $this->loader->loadBooleanSchema($value); + } elseif (is_object($value)) { + if ($value instanceof Schema) { + $list[$name] = $value; + continue; + } + $value = $this->loader->loadObjectSchema($value); + } elseif (is_string($value)) { + if (isset($this->slots[$value])) { + $value = $this->slots[$value]; + } elseif ($this->parent) { + $value = $this->parent->slot($value); + } + } + + if ($value instanceof Schema) { + $list[$name] = $value; + } + } + + $this->slots = $list; + } else { + $this->slots = null; + } + + return $this; + } + + /** + * @param string $name + * @return Schema|null + */ + public function slot(string $name): ?Schema + { + return $this->slots[$name] ?? null; + } + + /** + * @return int + */ + public function maxErrors(): int + { + return $this->maxErrors; + } + + /** + * @param int $max + * @return $this + */ + public function setMaxErrors(int $max): self + { + $this->maxErrors = $max; + + return $this; + } + + + public function stopAtFirstError(): bool + { + return $this->stopAtFirstError; + } + + public function setStopAtFirstError(bool $stop): self + { + $this->stopAtFirstError = $stop; + + return $this; + } + + /* --------------------- */ + + /** + * @param Schema $schema + * @return $this + */ + public function pushSharedObject(Schema $schema): self + { + $unevaluated = !in_array($schema->info()->draft(), ['06', '07']); + if ($unevaluated && ($parser = $this->loader->parser()) && !$parser->option('allowUnevaluated', true)) { + $unevaluated = false; + } + + $this->shared[] = [ + 'schema' => $schema, + 'unevaluated' => $unevaluated, + 'object' => null, + ]; + $this->sharedIndex++; + + return $this; + } + + /** + * @return $this + */ + public function popSharedObject(): self + { + if ($this->sharedIndex < 0) { + return $this; + } + + $data = array_pop($this->shared); + $this->sharedIndex--; + + if ($data['unevaluated'] && $data['object']) { + if ($this->sharedIndex >= 0) { + $this->mergeUnevaluated($data['object']); + } elseif ($this->parent && $this->parent->sharedIndex >= 0) { + $this->parent->mergeUnevaluated($data['object']); + } + } + + return $this; + } + + /** + * @return object|null + */ + public function sharedObject(): ?object + { + if ($this->sharedIndex < 0) { + return null; + } + + return $this->shared[$this->sharedIndex]['object'] ??= (object)[]; + } + + public function schema(): ?Schema + { + return $this->shared[$this->sharedIndex]['schema'] ?? null; + } + + public function trackUnevaluated(): bool + { + return $this->shared[$this->sharedIndex]['unevaluated'] ?? false; + } + + protected function mergeUnevaluated(object $obj): void + { + switch ($this->currentDataType()) { + case 'object': + if (isset($obj->evaluatedProperties)) { + $this->addEvaluatedProperties($obj->evaluatedProperties); + } + break; + case 'array': + if (isset($obj->evaluatedItems)) { + $this->addEvaluatedItems($obj->evaluatedItems); + } + break; + } + } + + /* ----------------*/ + + public function getStringLength(): ?int + { + if ($this->currentDataType() !== 'string') { + return null; + } + + $shared = $this->sharedObject(); + + if (!isset($shared->stringLength)) { + $shared->stringLength = UnicodeString::getStringLength($this->currentData()); + } + + return $shared->stringLength; + } + + public function setDecodedContent(string $content): bool + { + if ($this->currentDataType() !== 'string') { + return false; + } + + $this->sharedObject()->decodedContent = $content; + + return true; + } + + public function getDecodedContent(): ?string + { + if ($this->currentDataType() !== 'string') { + return null; + } + return $this->sharedObject()->decodedContent ?? $this->currentData(); + } + + public function getObjectProperties(): ?array + { + if ($this->currentDataType() !== 'object') { + return null; + } + + return $this->sharedObject()->objectProperties ??= array_keys(get_object_vars($this->currentData())); + } + + public function addCheckedProperties(?array $properties): bool + { + if (!$properties) { + return false; + } + + $shared = $this->sharedObject(); + + if (!isset($shared->checkedProperties)) { + $shared->checkedProperties = $properties; + } else { + $shared->checkedProperties = array_values(array_unique(array_merge($shared->checkedProperties, $properties))); + } + + return true; + } + + public function getCheckedProperties(): ?array + { + return $this->sharedObject()->checkedProperties ?? null; + } + + public function getUncheckedProperties(): ?array + { + $properties = $this->getObjectProperties(); + if (!$properties) { + return $properties; + } + + $checked = $this->sharedObject()->checkedProperties ?? null; + if (!$checked) { + return $properties; + } + + return array_values(array_diff($properties, $checked)); + } + + public function markAllAsEvaluatedProperties(): bool + { + return $this->addEvaluatedProperties($this->getObjectProperties()); + } + + public function addEvaluatedProperties(?array $properties): bool + { + if (!$properties || !($this->currentDataType() === 'object') || !$this->trackUnevaluated()) { + return false; + } + + $shared = $this->sharedObject(); + + if (!isset($shared->evaluatedProperties)) { + $shared->evaluatedProperties = $properties; + } else { + $shared->evaluatedProperties = array_values(array_unique(array_merge($shared->evaluatedProperties, $properties))); + } + + return true; + } + + public function getEvaluatedProperties(): ?array + { + return $this->sharedObject()->evaluatedProperties ?? null; + } + + public function getUnevaluatedProperties(): ?array + { + $properties = $this->getObjectProperties(); + if (!$properties) { + return $properties; + } + + $evaluated = $this->sharedObject()->evaluatedProperties ?? null; + if (!$evaluated) { + return $properties; + } + + return array_values(array_diff($properties, $evaluated)); + } + + public function markAllAsEvaluatedItems(): bool + { + return $this->addEvaluatedItems(range(0, count($this->currentData()))); + } + + public function markCountAsEvaluatedItems(int $count): bool + { + if (!$count) { + return false; + } + + return $this->addEvaluatedItems(range(0, $count)); + } + + public function addEvaluatedItems(?array $items): bool + { + if (!$items || !($this->currentDataType() === 'array') || !$this->trackUnevaluated()) { + return false; + } + + $shared = $this->sharedObject(); + + if (!isset($shared->evaluatedItems)) { + $shared->evaluatedItems = $items; + } else { + $shared->evaluatedItems = array_values(array_unique(array_merge($shared->evaluatedItems, $items), SORT_NUMERIC)); + } + + return true; + } + + public function getEvaluatedItems(): ?array + { + return $this->sharedObject()->evaluatedItems ?? null; + } + + public function getUnevaluatedItems(): ?array + { + if ($this->currentDataType() !== 'array') { + return null; + } + + $items = array_keys($this->currentData()); + if (!$items) { + return $items; + } + + $evaluated = $this->sharedObject()->evaluatedItems ?? null; + if (!$evaluated) { + return $items; + } + + return array_values(array_diff($items, $evaluated)); + } + + public function validateSchemaWithoutEvaluated( + Schema $schema, + ?int $maxErrors = null, + bool $reset_on_error_only = false, + ?ArrayObject $array = null + ): ?ValidationError { + $currentMaxErrors = $this->maxErrors; + + $this->maxErrors = $maxErrors ?? $currentMaxErrors; + + if ($this->trackUnevaluated()) { + $shared = $this->sharedObject(); + + $props = $shared->evaluatedProperties ?? null; + $items = $shared->evaluatedItems ?? null; + + $error = $schema->validate($this); + + if ($array) { + $value = null; + + if ($shared->evaluatedProperties ?? null) { + if ($props) { + if ($diff = array_diff($shared->evaluatedProperties, $props)) { + $value['properties'] = $diff; + } + } else { + $value['properties'] = $shared->evaluatedProperties; + } + } + + if ($shared->evaluatedItems ?? null) { + if ($items) { + if ($diff = array_diff($shared->evaluatedItems, $items)) { + $value['items'] = $diff; + } + } else { + $value['items'] = $shared->evaluatedItems; + } + } + + if ($value) { + $array[] = $value; + } + } + + if ($reset_on_error_only) { + if ($error) { + $shared->evaluatedProperties = $props; + $shared->evaluatedItems = $items; + } + } else { + $shared->evaluatedProperties = $props; + $shared->evaluatedItems = $items; + } + } else { + $error = $schema->validate($this); + } + + $this->maxErrors = $currentMaxErrors; + + return $error; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/ValidationResult.php b/admin/vendor/opis/json-schema/src/ValidationResult.php new file mode 100644 index 0000000..08ca63f --- /dev/null +++ b/admin/vendor/opis/json-schema/src/ValidationResult.php @@ -0,0 +1,53 @@ +error = $error; + } + + public function error(): ?ValidationError + { + return $this->error; + } + + public function isValid(): bool + { + return $this->error === null; + } + + public function hasError(): bool + { + return $this->error !== null; + } + + public function __toString(): string + { + if ($this->error) { + return $this->error->message(); + } + return ''; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Validator.php b/admin/vendor/opis/json-schema/src/Validator.php new file mode 100644 index 0000000..f80a70b --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Validator.php @@ -0,0 +1,317 @@ +loader = $loader ?? new SchemaLoader(new SchemaParser(), new SchemaResolver(), true); + $this->maxErrors = $max_errors; + $this->stopAtFirstError = $stop_at_first_error; + } + + /** + * @param $data + * @param bool|string|Uri|Schema|object $schema + * @param array|null $globals + * @param array|null $slots + * @return ValidationResult + */ + public function validate($data, $schema, ?array $globals = null, ?array $slots = null): ValidationResult + { + if (is_string($schema)) { + if ($uri = Uri::parse($schema, true)) { + $schema = $uri; + } else { + $schema = json_decode($schema, false); + } + } + + $error = null; + if (is_bool($schema)) { + $error = $this->dataValidation($data, $schema, $globals, $slots); + } elseif (is_object($schema)) { + if ($schema instanceof Uri) { + $error = $this->uriValidation($data, $schema, $globals, $slots); + } elseif ($schema instanceof Schema) { + $error = $this->schemaValidation($data, $schema, $globals, $slots); + } else { + $error = $this->dataValidation($data, $schema, $globals, $slots); + } + } else { + throw new InvalidArgumentException("Invalid schema"); + } + + return new ValidationResult($error); + } + + /** + * @param $data + * @param Uri|string $uri + * @param array|null $globals + * @param array|null $slots + * @return null|ValidationError + */ + public function uriValidation($data, $uri, ?array $globals = null, ?array $slots = null): ?ValidationError + { + if (is_string($uri)) { + $uri = Uri::parse($uri, true); + } + + if (!($uri instanceof Uri)) { + throw new InvalidArgumentException("Invalid uri"); + } + + if ($uri->fragment() === null) { + $uri = Uri::merge($uri, null, true); + } + + $schema = $this->loader->loadSchemaById($uri); + + if ($schema === null) { + throw new RuntimeException("Schema not found: $uri"); + } + + return $this->schemaValidation($data, $schema, $globals, $slots); + } + + /** + * @param $data + * @param string|object|bool $schema + * @param array|null $globals + * @param array|null $slots + * @param string|null $id + * @param string|null $draft + * @return ValidationError|null + */ + public function dataValidation( + $data, + $schema, + ?array $globals = null, + ?array $slots = null, + ?string $id = null, + ?string $draft = null + ): ?ValidationError + { + if (is_string($schema)) { + $schema = json_decode($schema, false); + } + + if ($schema === true) { + return null; + } + + if ($schema === false) { + $schema = $this->loader->loadBooleanSchema(false, $id, $draft); + } else { + if (!is_object($schema)) { + throw new InvalidArgumentException("Invalid schema"); + } + + $schema = $this->loader->loadObjectSchema($schema, $id, $draft); + } + + return $this->schemaValidation($data, $schema, $globals, $slots); + } + + /** + * @param $data + * @param Schema $schema + * @param array|null $globals + * @param array|null $slots + * @return null|ValidationError + */ + public function schemaValidation( + $data, + Schema $schema, + ?array $globals = null, + ?array $slots = null + ): ?ValidationError + { + return $schema->validate($this->createContext($data, $globals, $slots)); + } + + /** + * @param $data + * @param array|null $globals + * @param array|null $slots + * @return ValidationContext + */ + public function createContext($data, ?array $globals = null, ?array $slots = null): ValidationContext + { + if ($slots) { + $slots = $this->parseSlots($slots); + } + + return new ValidationContext( + $data, + $this->loader, + null, + null, + $globals ?? [], + $slots, + $this->maxErrors, + $this->stopAtFirstError, + ); + } + + /** + * @return SchemaParser + */ + public function parser(): SchemaParser + { + return $this->loader->parser(); + } + + /** + * @param SchemaParser $parser + * @return Validator + */ + public function setParser(SchemaParser $parser): self + { + $this->loader->setParser($parser); + + return $this; + } + + /** + * @return SchemaResolver|null + */ + public function resolver(): ?SchemaResolver + { + return $this->loader->resolver(); + } + + /** + * @param SchemaResolver|null $resolver + * @return Validator + */ + public function setResolver(?SchemaResolver $resolver): self + { + $this->loader->setResolver($resolver); + + return $this; + } + + /** + * @return SchemaLoader + */ + public function loader(): SchemaLoader + { + return $this->loader; + } + + /** + * @param SchemaLoader $loader + * @return Validator + */ + public function setLoader(SchemaLoader $loader): self + { + $this->loader = $loader; + + return $this; + } + + /** + * @return int + */ + public function getMaxErrors(): int + { + return $this->maxErrors; + } + + /** + * @param int $max_errors + * @return Validator + */ + public function setMaxErrors(int $max_errors): self + { + $this->maxErrors = $max_errors; + + return $this; + } + + /** + * @return bool + */ + public function getStopAtFirstError(): bool + { + return $this->stopAtFirstError; + } + + /** + * @param bool $stop + * @return $this + */ + public function setStopAtFirstError(bool $stop): self + { + $this->stopAtFirstError = $stop; + return $this; + } + + /** + * @param array $slots + * @return array + */ + protected function parseSlots(array $slots): array + { + foreach ($slots as $name => &$value) { + if (!is_string($name)) { + unset($slots[$name]); + continue; + } + + if (is_string($value)) { + $value = Uri::parse($value, true); + } + + if ($value instanceof Uri) { + $value = $this->loader->loadSchemaById($value); + } elseif (is_bool($value)) { + $value = $this->loader->loadBooleanSchema($value); + } + + if (!is_object($value)) { + unset($slots[$name]); + } + + unset($value); + } + + return $slots; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Variables.php b/admin/vendor/opis/json-schema/src/Variables.php new file mode 100644 index 0000000..a2682cb --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Variables.php @@ -0,0 +1,28 @@ +pointer = $pointer; + $this->each = $each; + $this->hasDefault = func_num_args() === 3; + $this->defaultValue = $default; + } + + /** + * @return JsonPointer + */ + public function pointer(): JsonPointer + { + return $this->pointer; + } + + /** + * @return null|Variables + */ + public function each(): ?Variables + { + return $this->each; + } + + /** + * @return bool + */ + public function hasDefaultValue(): bool + { + return $this->hasDefault; + } + + /** + * @return mixed|null + */ + public function defaultValue() + { + return $this->defaultValue; + } + + /** + * @inheritDoc + */ + public function resolve($data, array $path = []) + { + $resolved = $this->pointer->data($data, $path, $this); + if ($resolved === $this) { + return $this->defaultValue; + } + + if ($this->each && (is_array($resolved) || is_object($resolved))) { + $path = $this->pointer->absolutePath($path); + foreach ($resolved as $key => &$value) { + $path[] = $key; + $value = $this->each->resolve($data, $path); + array_pop($path); + unset($value); + } + } + + return $resolved; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/json-schema/src/Variables/VariablesContainer.php b/admin/vendor/opis/json-schema/src/Variables/VariablesContainer.php new file mode 100644 index 0000000..163c9b5 --- /dev/null +++ b/admin/vendor/opis/json-schema/src/Variables/VariablesContainer.php @@ -0,0 +1,162 @@ +keys = [ + 'ref' => $ref_key, + 'each' => $each_key, + 'default' => $default_key, + ]; + + if ($lazy) { + $this->vars = $data; + } else { + $this->parsed = true; + $this->vars = $this->parse($data); + } + } + + /** + * @inheritdoc + */ + public function resolve($data, array $path = []) + { + if (!$this->parsed) { + $this->vars = $this->parse($this->vars); + $this->parsed = true; + } + + if (!$this->hasRefs) { + // Nothing to resolve + return $this->vars; + } + + return $this->deepClone($this->vars, $data, $path); + } + + /** + * @param $vars + * @param $data + * @param string[]|int[] $path + * @return array|object|mixed + */ + private function deepClone($vars, $data, array $path) + { + $toObject = false; + if (is_object($vars)) { + if ($vars instanceof Variables) { + return $vars->resolve($data, $path); + } + $vars = get_object_vars($vars); + $toObject = true; + } elseif (!is_array($vars)) { + return $vars; + } + + foreach ($vars as &$var) { + if ($var !== null && !is_scalar($var)) { + $var = $this->deepClone($var, $data, $path); + } + unset($var); + } + + return $toObject ? (object)$vars : $vars; + } + + /** + * @param mixed $data + * @return mixed + */ + private function parse($data) + { + if (is_array($data)) { + return array_map([$this, 'parse'], $data); + } + + if (!is_object($data)) { + return $data; + } + + if ($vars = $this->parseRef($data)) { + $this->hasRefs = true; + + return $vars; + } + + return (object)array_map([$this, 'parse'], get_object_vars($data)); + } + + /** + * @param object $data + * @return null|Variables + */ + private function parseRef(object $data): ?Variables + { + if (!property_exists($data, $this->keys['ref'])) { + return null; + } + + $ref = $data->{$this->keys['ref']}; + if (!is_string($ref)) { + return null; + } + + $pointer = JsonPointer::parse($ref); + if ($pointer === null) { + return null; + } + + $each = null; + if (property_exists($data, $this->keys['each']) && is_object($data->{$this->keys['each']})) { + $each = new self($data->{$this->keys['each']}, !$this->parsed, $this->keys['ref'], $this->keys['each'], $this->keys['default']); + } + + if (property_exists($data, $this->keys['default'])) { + return new RefVariablesContainer($pointer, $each, $data->{$this->keys['default']}); + } + + return new RefVariablesContainer($pointer, $each); + } +} \ No newline at end of file diff --git a/admin/vendor/opis/string/.editorconfig b/admin/vendor/opis/string/.editorconfig new file mode 100644 index 0000000..131e7a2 --- /dev/null +++ b/admin/vendor/opis/string/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +charset = utf-8 diff --git a/admin/vendor/opis/string/CHANGELOG.md b/admin/vendor/opis/string/CHANGELOG.md new file mode 100644 index 0000000..88a33dc --- /dev/null +++ b/admin/vendor/opis/string/CHANGELOG.md @@ -0,0 +1,10 @@ +CHANGELOG +------------- + +### v2.1.0, 2025.10.17 + +* Added static `getStringLength()` + +### v2.0.0, 2021.04.13 + +* The library was fully refactored diff --git a/admin/vendor/opis/string/LICENSE b/admin/vendor/opis/string/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/admin/vendor/opis/string/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/admin/vendor/opis/string/NOTICE b/admin/vendor/opis/string/NOTICE new file mode 100644 index 0000000..d63600e --- /dev/null +++ b/admin/vendor/opis/string/NOTICE @@ -0,0 +1,9 @@ +Opis String +Copyright 2018-2021 Zindex Software + +This product includes software developed at +Zindex Software (http://zindex.software). + +This software was originally developed by Marius Sarca and Sorin Sarca +(Copyright 2016-2018). The copyright info was changed with the permission +of the original authors. diff --git a/admin/vendor/opis/string/README.md b/admin/vendor/opis/string/README.md new file mode 100644 index 0000000..4059919 --- /dev/null +++ b/admin/vendor/opis/string/README.md @@ -0,0 +1,51 @@ +Opis String +=========== +[![Tests](https://github.com/opis/string/workflows/Tests/badge.svg)](https://github.com/opis/string/actions) +[![Packagist Version](https://img.shields.io/packagist/v/opis/string?label=Version)](https://packagist.org/packages/opis/string) +[![Packagist Downloads](https://img.shields.io/packagist/dt/opis/string?label=Downloads)](https://packagist.org/packages/opis/string) +[![Packagist License](https://img.shields.io/packagist/l/opis/string?color=teal&label=License)](https://packagist.org/packages/opis/string) + + +Multibyte strings +---------------------------- + +**Opis String** is a tiny library that allows you to work with multibyte encoded strings in an object-oriented manner. +The library has no dependencies to *mb_string* or similar PHP extensions. + +## Documentation + +The full documentation for this library can be found [here][documentation]. + +## License + +**Opis String** is licensed under the [Apache License, Version 2.0][license]. + +## Requirements + +* PHP ^7.4 || ^8.0 +* ext-json +* ext-iconv + +## Installation + +**Opis String** is available on [Packagist], and it can be installed from a +command line interface by using [Composer]. + +```bash +composer require opis/string +``` + +Or you could directly reference it into your `composer.json` file as a dependency + +```json +{ + "require": { + "opis/string": "^2.1" + } +} +``` + +[documentation]: https://opis.io/string +[license]: https://www.apache.org/licenses/LICENSE-2.0 "Apache License" +[Packagist]: https://packagist.org/packages/opis/string "Packagist" +[Composer]: https://getcomposer.org "Composer" diff --git a/admin/vendor/opis/string/composer.json b/admin/vendor/opis/string/composer.json new file mode 100644 index 0000000..e5040ab --- /dev/null +++ b/admin/vendor/opis/string/composer.json @@ -0,0 +1,43 @@ +{ + "name": "opis/string", + "description": "Multibyte strings as objects", + "keywords": ["opis", "string", "utf-8", "multi-byte", "string manipulation"], + "homepage": "https://opis.io/string", + "license": "Apache-2.0", + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + "ext-iconv": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Opis\\String\\Test\\": "tests/" + } + }, + "scripts": { + "tests": "./vendor/bin/phpunit --verbose --color" + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/admin/vendor/opis/string/res/ascii.php b/admin/vendor/opis/string/res/ascii.php new file mode 100644 index 0000000..000a19f --- /dev/null +++ b/admin/vendor/opis/string/res/ascii.php @@ -0,0 +1,751 @@ + 0x28, +0xAA => 0x61, +0xB0 => 0x30, +0xB2 => 0x32, +0xB3 => 0x33, +0xB5 => 0x75, +0xB9 => 0x31, +0xBA => 0x6F, +0xC0 => 0x41, +0xC1 => 0x41, +0xC2 => 0x41, +0xC3 => 0x41, +0xC4 => 0x41, +0xC5 => 0x41, +0xC6 => 0x41, +0xC7 => 0x43, +0xC8 => 0x45, +0xC9 => 0x45, +0xCA => 0x45, +0xCB => 0x45, +0xCC => 0x49, +0xCD => 0x49, +0xCE => 0x49, +0xCF => 0x49, +0xD0 => 0x44, +0xD1 => 0x4E, +0xD2 => 0x4F, +0xD3 => 0x4F, +0xD4 => 0x4F, +0xD5 => 0x4F, +0xD6 => 0x4F, +0xD8 => 0x4F, +0xD9 => 0x55, +0xDA => 0x55, +0xDB => 0x55, +0xDC => 0x55, +0xDD => 0x59, +0xDE => 0x54, +0xDF => 0x73, +0xE0 => 0x61, +0xE1 => 0x61, +0xE2 => 0x61, +0xE3 => 0x61, +0xE4 => 0x61, +0xE5 => 0x61, +0xE6 => 0x61, +0xE7 => 0x63, +0xE8 => 0x65, +0xE9 => 0x65, +0xEA => 0x65, +0xEB => 0x65, +0xEC => 0x69, +0xED => 0x69, +0xEE => 0x69, +0xEF => 0x69, +0xF0 => 0x64, +0xF1 => 0x6E, +0xF2 => 0x6F, +0xF3 => 0x6F, +0xF4 => 0x6F, +0xF5 => 0x6F, +0xF6 => 0x6F, +0xF8 => 0x6F, +0xF9 => 0x75, +0xFA => 0x75, +0xFB => 0x75, +0xFC => 0x75, +0xFD => 0x79, +0xFE => 0x74, +0xFF => 0x79, +0x100 => 0x41, +0x101 => 0x61, +0x102 => 0x41, +0x103 => 0x61, +0x104 => 0x41, +0x105 => 0x61, +0x106 => 0x43, +0x107 => 0x63, +0x108 => 0x43, +0x109 => 0x63, +0x10A => 0x43, +0x10B => 0x63, +0x10C => 0x43, +0x10D => 0x63, +0x10E => 0x44, +0x10F => 0x64, +0x110 => 0x44, +0x111 => 0x64, +0x112 => 0x45, +0x113 => 0x65, +0x114 => 0x45, +0x115 => 0x65, +0x116 => 0x45, +0x117 => 0x65, +0x118 => 0x45, +0x119 => 0x65, +0x11A => 0x45, +0x11B => 0x65, +0x11C => 0x47, +0x11D => 0x67, +0x11E => 0x47, +0x11F => 0x67, +0x120 => 0x47, +0x121 => 0x67, +0x122 => 0x47, +0x123 => 0x67, +0x124 => 0x48, +0x125 => 0x68, +0x126 => 0x48, +0x127 => 0x68, +0x128 => 0x49, +0x129 => 0x69, +0x12A => 0x49, +0x12B => 0x69, +0x12C => 0x49, +0x12D => 0x69, +0x12E => 0x49, +0x12F => 0x69, +0x130 => 0x49, +0x131 => 0x69, +0x132 => 0x49, +0x133 => 0x69, +0x134 => 0x4A, +0x135 => 0x6A, +0x136 => 0x6B, +0x137 => 0x6B, +0x138 => 0x6B, +0x139 => 0x4C, +0x13A => 0x6C, +0x13B => 0x4C, +0x13C => 0x6C, +0x13D => 0x4C, +0x13E => 0x6C, +0x13F => 0x4C, +0x140 => 0x6C, +0x141 => 0x4C, +0x142 => 0x6C, +0x143 => 0x4E, +0x144 => 0x6E, +0x145 => 0x4E, +0x146 => 0x6E, +0x147 => 0x4E, +0x148 => 0x6E, +0x149 => 0x6E, +0x14A => 0x4E, +0x14B => 0x6E, +0x14C => 0x4F, +0x14D => 0x6F, +0x14E => 0x4F, +0x14F => 0x6F, +0x150 => 0x4F, +0x151 => 0x6F, +0x152 => 0x4F, +0x153 => 0x6F, +0x154 => 0x52, +0x155 => 0x72, +0x156 => 0x52, +0x157 => 0x72, +0x158 => 0x52, +0x159 => 0x72, +0x15A => 0x53, +0x15B => 0x73, +0x15C => 0x53, +0x15D => 0x73, +0x15E => 0x53, +0x15F => 0x73, +0x160 => 0x53, +0x161 => 0x73, +0x162 => 0x54, +0x163 => 0x74, +0x164 => 0x54, +0x165 => 0x74, +0x166 => 0x54, +0x167 => 0x74, +0x168 => 0x55, +0x169 => 0x75, +0x16A => 0x55, +0x16B => 0x75, +0x16C => 0x55, +0x16D => 0x75, +0x16E => 0x55, +0x16F => 0x75, +0x170 => 0x55, +0x171 => 0x75, +0x172 => 0x55, +0x173 => 0x75, +0x174 => 0x57, +0x175 => 0x77, +0x176 => 0x59, +0x177 => 0x79, +0x178 => 0x59, +0x179 => 0x5A, +0x17A => 0x7A, +0x17B => 0x5A, +0x17C => 0x7A, +0x17D => 0x5A, +0x17E => 0x7A, +0x17F => 0x73, +0x189 => 0x44, +0x18A => 0x44, +0x18B => 0x44, +0x18C => 0x64, +0x18F => 0x45, +0x192 => 0x66, +0x1A0 => 0x4F, +0x1A1 => 0x6F, +0x1AF => 0x55, +0x1B0 => 0x75, +0x1CD => 0x41, +0x1CE => 0x61, +0x1CF => 0x49, +0x1D0 => 0x69, +0x1D1 => 0x4F, +0x1D2 => 0x6F, +0x1D3 => 0x55, +0x1D4 => 0x75, +0x1D5 => 0x55, +0x1D6 => 0x75, +0x1D7 => 0x55, +0x1D8 => 0x75, +0x1D9 => 0x55, +0x1DA => 0x75, +0x1DB => 0x55, +0x1DC => 0x75, +0x1FA => 0x41, +0x1FB => 0x61, +0x1FC => 0x41, +0x1FD => 0x61, +0x1FE => 0x4F, +0x1FF => 0x6F, +0x218 => 0x53, +0x219 => 0x73, +0x21A => 0x54, +0x21B => 0x74, +0x221 => 0x64, +0x256 => 0x64, +0x257 => 0x64, +0x259 => 0x65, +0x386 => 0x41, +0x388 => 0x45, +0x389 => 0x48, +0x38A => 0x49, +0x38C => 0x4F, +0x38E => 0x59, +0x38F => 0x57, +0x390 => 0x69, +0x391 => 0x41, +0x392 => 0x42, +0x393 => 0x47, +0x394 => 0x44, +0x395 => 0x45, +0x396 => 0x5A, +0x397 => 0x48, +0x398 => 0x4F, +0x399 => 0x49, +0x39A => 0x4B, +0x39B => 0x4C, +0x39C => 0x4D, +0x39D => 0x4E, +0x39E => 0x58, +0x39F => 0x4F, +0x3A0 => 0x50, +0x3A1 => 0x52, +0x3A3 => 0x53, +0x3A4 => 0x54, +0x3A5 => 0x59, +0x3A6 => 0x46, +0x3A7 => 0x58, +0x3A8 => 0x50, +0x3A9 => 0x57, +0x3AA => 0x49, +0x3AB => 0x59, +0x3AC => 0x61, +0x3AD => 0x65, +0x3AE => 0x68, +0x3AF => 0x69, +0x3B0 => 0x79, +0x3B1 => 0x61, +0x3B2 => 0x62, +0x3B3 => 0x67, +0x3B4 => 0x64, +0x3B5 => 0x65, +0x3B6 => 0x7A, +0x3B7 => 0x68, +0x3B8 => 0x6F, +0x3B9 => 0x69, +0x3BA => 0x6B, +0x3BB => 0x6C, +0x3BC => 0x6D, +0x3BD => 0x6E, +0x3BE => 0x78, +0x3BF => 0x6F, +0x3C0 => 0x70, +0x3C1 => 0x72, +0x3C2 => 0x73, +0x3C3 => 0x73, +0x3C4 => 0x74, +0x3C5 => 0x79, +0x3C6 => 0x66, +0x3C7 => 0x78, +0x3C8 => 0x70, +0x3C9 => 0x77, +0x3CA => 0x69, +0x3CB => 0x79, +0x3CC => 0x6F, +0x3CD => 0x79, +0x3CE => 0x77, +0x3D0 => 0x76, +0x3D1 => 0x74, +0x3D2 => 0x49, +0x401 => 0x45, +0x402 => 0x44, +0x404 => 0x45, +0x406 => 0x49, +0x407 => 0x49, +0x408 => 0x6A, +0x409 => 0x4C, +0x40A => 0x4E, +0x40F => 0x44, +0x410 => 0x41, +0x411 => 0x42, +0x412 => 0x56, +0x413 => 0x47, +0x414 => 0x44, +0x415 => 0x45, +0x416 => 0x5A, +0x417 => 0x5A, +0x418 => 0x49, +0x419 => 0x59, +0x41A => 0x4B, +0x41B => 0x4C, +0x41C => 0x4D, +0x41D => 0x4E, +0x41E => 0x4F, +0x41F => 0x50, +0x420 => 0x52, +0x421 => 0x53, +0x422 => 0x54, +0x423 => 0x55, +0x424 => 0x46, +0x425 => 0x4B, +0x426 => 0x54, +0x427 => 0x43, +0x428 => 0x53, +0x429 => 0x53, +0x42A => 0x62, +0x42B => 0x59, +0x42C => 0x62, +0x42D => 0x45, +0x42E => 0x59, +0x42F => 0x59, +0x430 => 0x61, +0x431 => 0x62, +0x432 => 0x76, +0x433 => 0x67, +0x434 => 0x64, +0x435 => 0x65, +0x436 => 0x7A, +0x437 => 0x7A, +0x438 => 0x69, +0x439 => 0x79, +0x43A => 0x6B, +0x43B => 0x6C, +0x43C => 0x6D, +0x43D => 0x6E, +0x43E => 0x6F, +0x43F => 0x70, +0x440 => 0x72, +0x441 => 0x73, +0x442 => 0x74, +0x443 => 0x75, +0x444 => 0x66, +0x445 => 0x6B, +0x446 => 0x74, +0x447 => 0x63, +0x448 => 0x73, +0x449 => 0x73, +0x44B => 0x79, +0x44D => 0x65, +0x44E => 0x79, +0x44F => 0x79, +0x451 => 0x65, +0x452 => 0x64, +0x454 => 0x65, +0x456 => 0x69, +0x457 => 0x69, +0x458 => 0x6A, +0x459 => 0x6C, +0x45A => 0x6E, +0x45F => 0x64, +0x490 => 0x47, +0x491 => 0x67, +0x4E8 => 0x4F, +0x622 => 0x61, +0x623 => 0x61, +0x624 => 0x6F, +0x625 => 0x65, +0x626 => 0x65, +0x627 => 0x61, +0x628 => 0x62, +0x62A => 0x74, +0x62B => 0x74, +0x62C => 0x6A, +0x62D => 0x68, +0x62E => 0x6B, +0x62F => 0x64, +0x630 => 0x74, +0x631 => 0x72, +0x632 => 0x7A, +0x633 => 0x73, +0x634 => 0x73, +0x635 => 0x73, +0x636 => 0x64, +0x637 => 0x74, +0x638 => 0x74, +0x639 => 0x61, +0x63A => 0x67, +0x641 => 0x66, +0x642 => 0x6B, +0x643 => 0x6B, +0x644 => 0x6C, +0x645 => 0x6D, +0x646 => 0x6E, +0x647 => 0x68, +0x648 => 0x6F, +0x64A => 0x79, +0x664 => 0x34, +0x665 => 0x35, +0x666 => 0x36, +0x67E => 0x70, +0x686 => 0x63, +0x698 => 0x7A, +0x6A9 => 0x6B, +0x6AF => 0x67, +0x6CC => 0x69, +0x6F0 => 0x30, +0x6F1 => 0x31, +0x6F2 => 0x32, +0x6F3 => 0x33, +0x6F4 => 0x34, +0x6F5 => 0x35, +0x6F6 => 0x36, +0x6F7 => 0x37, +0x6F8 => 0x38, +0x6F9 => 0x39, +0x905 => 0x61, +0x906 => 0x61, +0x907 => 0x69, +0x908 => 0x69, +0x909 => 0x75, +0x90A => 0x75, +0x90D => 0x65, +0x90F => 0x65, +0x910 => 0x61, +0x911 => 0x6F, +0x912 => 0x6F, +0x913 => 0x6F, +0x92C => 0x42, +0x932 => 0x4C, +0x1000 => 0x6B, +0x1002 => 0x67, +0x1005 => 0x73, +0x1007 => 0x7A, +0x1009 => 0x75, +0x100A => 0x69, +0x100B => 0x74, +0x100D => 0x64, +0x1010 => 0x74, +0x1012 => 0x64, +0x1014 => 0x6E, +0x1015 => 0x70, +0x1017 => 0x62, +0x1019 => 0x6D, +0x101A => 0x79, +0x101C => 0x6C, +0x101D => 0x77, +0x101F => 0x68, +0x1021 => 0x61, +0x1023 => 0x69, +0x1027 => 0x65, +0x102B => 0x61, +0x102C => 0x61, +0x102D => 0x6F, +0x102E => 0x69, +0x102F => 0x75, +0x1030 => 0x75, +0x1031 => 0x65, +0x1032 => 0x65, +0x103D => 0x77, +0x103E => 0x68, +0x10D0 => 0x61, +0x10D1 => 0x62, +0x10D2 => 0x67, +0x10D3 => 0x64, +0x10D4 => 0x65, +0x10D5 => 0x76, +0x10D6 => 0x7A, +0x10D7 => 0x74, +0x10D8 => 0x69, +0x10D9 => 0x6B, +0x10DA => 0x6C, +0x10DB => 0x6D, +0x10DC => 0x6E, +0x10DD => 0x6F, +0x10DE => 0x70, +0x10DF => 0x7A, +0x10E0 => 0x72, +0x10E1 => 0x73, +0x10E2 => 0x74, +0x10E3 => 0x75, +0x10E4 => 0x66, +0x10E5 => 0x6B, +0x10E6 => 0x67, +0x10E7 => 0x71, +0x10E8 => 0x73, +0x10E9 => 0x63, +0x10EA => 0x74, +0x10EB => 0x64, +0x10EC => 0x74, +0x10ED => 0x63, +0x10EE => 0x6B, +0x10EF => 0x6A, +0x10F0 => 0x68, +0x1D05 => 0x44, +0x1D06 => 0x44, +0x1D6D => 0x64, +0x1D81 => 0x64, +0x1D91 => 0x64, +0x1E9E => 0x53, +0x1EA0 => 0x41, +0x1EA1 => 0x61, +0x1EA2 => 0x41, +0x1EA3 => 0x61, +0x1EA4 => 0x41, +0x1EA5 => 0x61, +0x1EA6 => 0x41, +0x1EA7 => 0x61, +0x1EA8 => 0x41, +0x1EA9 => 0x61, +0x1EAA => 0x41, +0x1EAB => 0x61, +0x1EAC => 0x41, +0x1EAD => 0x61, +0x1EAE => 0x41, +0x1EAF => 0x61, +0x1EB0 => 0x41, +0x1EB1 => 0x61, +0x1EB2 => 0x41, +0x1EB3 => 0x61, +0x1EB4 => 0x41, +0x1EB5 => 0x61, +0x1EB6 => 0x41, +0x1EB7 => 0x61, +0x1EB8 => 0x45, +0x1EB9 => 0x65, +0x1EBA => 0x45, +0x1EBB => 0x65, +0x1EBC => 0x45, +0x1EBD => 0x65, +0x1EBE => 0x45, +0x1EBF => 0x65, +0x1EC0 => 0x45, +0x1EC1 => 0x65, +0x1EC2 => 0x45, +0x1EC3 => 0x65, +0x1EC4 => 0x45, +0x1EC5 => 0x65, +0x1EC6 => 0x45, +0x1EC7 => 0x65, +0x1EC8 => 0x49, +0x1EC9 => 0x69, +0x1ECA => 0x49, +0x1ECB => 0x69, +0x1ECC => 0x4F, +0x1ECD => 0x6F, +0x1ECE => 0x4F, +0x1ECF => 0x6F, +0x1ED0 => 0x4F, +0x1ED1 => 0x6F, +0x1ED2 => 0x4F, +0x1ED3 => 0x6F, +0x1ED4 => 0x4F, +0x1ED5 => 0x6F, +0x1ED6 => 0x4F, +0x1ED7 => 0x6F, +0x1ED8 => 0x4F, +0x1ED9 => 0x6F, +0x1EDA => 0x4F, +0x1EDB => 0x6F, +0x1EDC => 0x4F, +0x1EDD => 0x6F, +0x1EDE => 0x4F, +0x1EDF => 0x6F, +0x1EE0 => 0x4F, +0x1EE1 => 0x6F, +0x1EE2 => 0x4F, +0x1EE3 => 0x6F, +0x1EE4 => 0x55, +0x1EE5 => 0x75, +0x1EE6 => 0x55, +0x1EE7 => 0x75, +0x1EE8 => 0x55, +0x1EE9 => 0x75, +0x1EEA => 0x55, +0x1EEB => 0x75, +0x1EEC => 0x55, +0x1EED => 0x75, +0x1EEE => 0x55, +0x1EEF => 0x75, +0x1EF0 => 0x55, +0x1EF1 => 0x75, +0x1EF2 => 0x59, +0x1EF3 => 0x79, +0x1EF4 => 0x59, +0x1EF5 => 0x79, +0x1EF6 => 0x59, +0x1EF7 => 0x79, +0x1EF8 => 0x59, +0x1EF9 => 0x79, +0x1F00 => 0x61, +0x1F01 => 0x61, +0x1F02 => 0x61, +0x1F03 => 0x61, +0x1F04 => 0x61, +0x1F05 => 0x61, +0x1F06 => 0x61, +0x1F07 => 0x61, +0x1F08 => 0x41, +0x1F09 => 0x41, +0x1F0A => 0x41, +0x1F0B => 0x41, +0x1F0C => 0x41, +0x1F0D => 0x41, +0x1F0E => 0x41, +0x1F0F => 0x41, +0x1F10 => 0x65, +0x1F11 => 0x65, +0x1F12 => 0x65, +0x1F13 => 0x65, +0x1F14 => 0x65, +0x1F15 => 0x65, +0x1F18 => 0x45, +0x1F19 => 0x45, +0x1F1A => 0x45, +0x1F1B => 0x45, +0x1F1C => 0x45, +0x1F1D => 0x45, +0x1F30 => 0x69, +0x1F31 => 0x69, +0x1F32 => 0x69, +0x1F33 => 0x69, +0x1F34 => 0x69, +0x1F35 => 0x69, +0x1F36 => 0x69, +0x1F37 => 0x69, +0x1F38 => 0x49, +0x1F39 => 0x49, +0x1F3B => 0x49, +0x1F3C => 0x49, +0x1F3D => 0x49, +0x1F3E => 0x49, +0x1F3F => 0x49, +0x1F40 => 0x6F, +0x1F41 => 0x6F, +0x1F42 => 0x6F, +0x1F43 => 0x6F, +0x1F44 => 0x6F, +0x1F45 => 0x6F, +0x1F48 => 0x4F, +0x1F49 => 0x4F, +0x1F4A => 0x4F, +0x1F4B => 0x4F, +0x1F4C => 0x4F, +0x1F4D => 0x4F, +0x1F70 => 0x61, +0x1F72 => 0x65, +0x1F76 => 0x69, +0x1F78 => 0x6F, +0x1F80 => 0x61, +0x1F81 => 0x61, +0x1F82 => 0x61, +0x1F83 => 0x61, +0x1F84 => 0x61, +0x1F85 => 0x61, +0x1F86 => 0x61, +0x1F87 => 0x61, +0x1F88 => 0x41, +0x1F89 => 0x41, +0x1F8A => 0x41, +0x1F8B => 0x41, +0x1F8C => 0x41, +0x1F8D => 0x41, +0x1F8E => 0x41, +0x1F8F => 0x41, +0x1FB0 => 0x61, +0x1FB1 => 0x61, +0x1FB2 => 0x61, +0x1FB3 => 0x61, +0x1FB4 => 0x61, +0x1FB6 => 0x61, +0x1FB7 => 0x61, +0x1FB8 => 0x41, +0x1FB9 => 0x41, +0x1FBA => 0x41, +0x1FBC => 0x41, +0x1FC8 => 0x45, +0x1FD0 => 0x69, +0x1FD1 => 0x69, +0x1FD2 => 0x69, +0x1FD6 => 0x69, +0x1FD7 => 0x69, +0x1FD8 => 0x49, +0x1FD9 => 0x49, +0x1FDA => 0x49, +0x1FE8 => 0x59, +0x1FE9 => 0x59, +0x1FEA => 0x59, +0x1FF8 => 0x4F, +0x2000 => 0x20, +0x2001 => 0x20, +0x2002 => 0x20, +0x2003 => 0x20, +0x2004 => 0x20, +0x2005 => 0x20, +0x2006 => 0x20, +0x2007 => 0x20, +0x2008 => 0x20, +0x2009 => 0x20, +0x200A => 0x20, +0x202F => 0x20, +0x205F => 0x20, +0x2074 => 0x34, +0x2075 => 0x35, +0x2076 => 0x36, +0x2077 => 0x37, +0x2078 => 0x38, +0x2079 => 0x39, +0x2080 => 0x30, +0x2081 => 0x31, +0x2082 => 0x32, +0x2083 => 0x33, +0x2084 => 0x34, +0x2085 => 0x35, +0x2086 => 0x36, +0x2087 => 0x37, +0x2088 => 0x38, +0x2089 => 0x39, +0x3000 => 0x20, +]; diff --git a/admin/vendor/opis/string/res/fold.php b/admin/vendor/opis/string/res/fold.php new file mode 100644 index 0000000..e93f981 --- /dev/null +++ b/admin/vendor/opis/string/res/fold.php @@ -0,0 +1,1417 @@ + 0x61, +0x42 => 0x62, +0x43 => 0x63, +0x44 => 0x64, +0x45 => 0x65, +0x46 => 0x66, +0x47 => 0x67, +0x48 => 0x68, +0x49 => 0x69, +0x4A => 0x6A, +0x4B => 0x6B, +0x4C => 0x6C, +0x4D => 0x6D, +0x4E => 0x6E, +0x4F => 0x6F, +0x50 => 0x70, +0x51 => 0x71, +0x52 => 0x72, +0x53 => 0x73, +0x54 => 0x74, +0x55 => 0x75, +0x56 => 0x76, +0x57 => 0x77, +0x58 => 0x78, +0x59 => 0x79, +0x5A => 0x7A, +0xB5 => 0x3BC, +0xC0 => 0xE0, +0xC1 => 0xE1, +0xC2 => 0xE2, +0xC3 => 0xE3, +0xC4 => 0xE4, +0xC5 => 0xE5, +0xC6 => 0xE6, +0xC7 => 0xE7, +0xC8 => 0xE8, +0xC9 => 0xE9, +0xCA => 0xEA, +0xCB => 0xEB, +0xCC => 0xEC, +0xCD => 0xED, +0xCE => 0xEE, +0xCF => 0xEF, +0xD0 => 0xF0, +0xD1 => 0xF1, +0xD2 => 0xF2, +0xD3 => 0xF3, +0xD4 => 0xF4, +0xD5 => 0xF5, +0xD6 => 0xF6, +0xD8 => 0xF8, +0xD9 => 0xF9, +0xDA => 0xFA, +0xDB => 0xFB, +0xDC => 0xFC, +0xDD => 0xFD, +0xDE => 0xFE, +0x100 => 0x101, +0x102 => 0x103, +0x104 => 0x105, +0x106 => 0x107, +0x108 => 0x109, +0x10A => 0x10B, +0x10C => 0x10D, +0x10E => 0x10F, +0x110 => 0x111, +0x112 => 0x113, +0x114 => 0x115, +0x116 => 0x117, +0x118 => 0x119, +0x11A => 0x11B, +0x11C => 0x11D, +0x11E => 0x11F, +0x120 => 0x121, +0x122 => 0x123, +0x124 => 0x125, +0x126 => 0x127, +0x128 => 0x129, +0x12A => 0x12B, +0x12C => 0x12D, +0x12E => 0x12F, +0x132 => 0x133, +0x134 => 0x135, +0x136 => 0x137, +0x139 => 0x13A, +0x13B => 0x13C, +0x13D => 0x13E, +0x13F => 0x140, +0x141 => 0x142, +0x143 => 0x144, +0x145 => 0x146, +0x147 => 0x148, +0x14A => 0x14B, +0x14C => 0x14D, +0x14E => 0x14F, +0x150 => 0x151, +0x152 => 0x153, +0x154 => 0x155, +0x156 => 0x157, +0x158 => 0x159, +0x15A => 0x15B, +0x15C => 0x15D, +0x15E => 0x15F, +0x160 => 0x161, +0x162 => 0x163, +0x164 => 0x165, +0x166 => 0x167, +0x168 => 0x169, +0x16A => 0x16B, +0x16C => 0x16D, +0x16E => 0x16F, +0x170 => 0x171, +0x172 => 0x173, +0x174 => 0x175, +0x176 => 0x177, +0x178 => 0xFF, +0x179 => 0x17A, +0x17B => 0x17C, +0x17D => 0x17E, +0x17F => 0x73, +0x181 => 0x253, +0x182 => 0x183, +0x184 => 0x185, +0x186 => 0x254, +0x187 => 0x188, +0x189 => 0x256, +0x18A => 0x257, +0x18B => 0x18C, +0x18E => 0x1DD, +0x18F => 0x259, +0x190 => 0x25B, +0x191 => 0x192, +0x193 => 0x260, +0x194 => 0x263, +0x196 => 0x269, +0x197 => 0x268, +0x198 => 0x199, +0x19C => 0x26F, +0x19D => 0x272, +0x19F => 0x275, +0x1A0 => 0x1A1, +0x1A2 => 0x1A3, +0x1A4 => 0x1A5, +0x1A6 => 0x280, +0x1A7 => 0x1A8, +0x1A9 => 0x283, +0x1AC => 0x1AD, +0x1AE => 0x288, +0x1AF => 0x1B0, +0x1B1 => 0x28A, +0x1B2 => 0x28B, +0x1B3 => 0x1B4, +0x1B5 => 0x1B6, +0x1B7 => 0x292, +0x1B8 => 0x1B9, +0x1BC => 0x1BD, +0x1C4 => 0x1C6, +0x1C5 => 0x1C6, +0x1C7 => 0x1C9, +0x1C8 => 0x1C9, +0x1CA => 0x1CC, +0x1CB => 0x1CC, +0x1CD => 0x1CE, +0x1CF => 0x1D0, +0x1D1 => 0x1D2, +0x1D3 => 0x1D4, +0x1D5 => 0x1D6, +0x1D7 => 0x1D8, +0x1D9 => 0x1DA, +0x1DB => 0x1DC, +0x1DE => 0x1DF, +0x1E0 => 0x1E1, +0x1E2 => 0x1E3, +0x1E4 => 0x1E5, +0x1E6 => 0x1E7, +0x1E8 => 0x1E9, +0x1EA => 0x1EB, +0x1EC => 0x1ED, +0x1EE => 0x1EF, +0x1F1 => 0x1F3, +0x1F2 => 0x1F3, +0x1F4 => 0x1F5, +0x1F6 => 0x195, +0x1F7 => 0x1BF, +0x1F8 => 0x1F9, +0x1FA => 0x1FB, +0x1FC => 0x1FD, +0x1FE => 0x1FF, +0x200 => 0x201, +0x202 => 0x203, +0x204 => 0x205, +0x206 => 0x207, +0x208 => 0x209, +0x20A => 0x20B, +0x20C => 0x20D, +0x20E => 0x20F, +0x210 => 0x211, +0x212 => 0x213, +0x214 => 0x215, +0x216 => 0x217, +0x218 => 0x219, +0x21A => 0x21B, +0x21C => 0x21D, +0x21E => 0x21F, +0x220 => 0x19E, +0x222 => 0x223, +0x224 => 0x225, +0x226 => 0x227, +0x228 => 0x229, +0x22A => 0x22B, +0x22C => 0x22D, +0x22E => 0x22F, +0x230 => 0x231, +0x232 => 0x233, +0x23A => 0x2C65, +0x23B => 0x23C, +0x23D => 0x19A, +0x23E => 0x2C66, +0x241 => 0x242, +0x243 => 0x180, +0x244 => 0x289, +0x245 => 0x28C, +0x246 => 0x247, +0x248 => 0x249, +0x24A => 0x24B, +0x24C => 0x24D, +0x24E => 0x24F, +0x345 => 0x3B9, +0x370 => 0x371, +0x372 => 0x373, +0x376 => 0x377, +0x37F => 0x3F3, +0x386 => 0x3AC, +0x388 => 0x3AD, +0x389 => 0x3AE, +0x38A => 0x3AF, +0x38C => 0x3CC, +0x38E => 0x3CD, +0x38F => 0x3CE, +0x391 => 0x3B1, +0x392 => 0x3B2, +0x393 => 0x3B3, +0x394 => 0x3B4, +0x395 => 0x3B5, +0x396 => 0x3B6, +0x397 => 0x3B7, +0x398 => 0x3B8, +0x399 => 0x3B9, +0x39A => 0x3BA, +0x39B => 0x3BB, +0x39C => 0x3BC, +0x39D => 0x3BD, +0x39E => 0x3BE, +0x39F => 0x3BF, +0x3A0 => 0x3C0, +0x3A1 => 0x3C1, +0x3A3 => 0x3C3, +0x3A4 => 0x3C4, +0x3A5 => 0x3C5, +0x3A6 => 0x3C6, +0x3A7 => 0x3C7, +0x3A8 => 0x3C8, +0x3A9 => 0x3C9, +0x3AA => 0x3CA, +0x3AB => 0x3CB, +0x3C2 => 0x3C3, +0x3CF => 0x3D7, +0x3D0 => 0x3B2, +0x3D1 => 0x3B8, +0x3D5 => 0x3C6, +0x3D6 => 0x3C0, +0x3D8 => 0x3D9, +0x3DA => 0x3DB, +0x3DC => 0x3DD, +0x3DE => 0x3DF, +0x3E0 => 0x3E1, +0x3E2 => 0x3E3, +0x3E4 => 0x3E5, +0x3E6 => 0x3E7, +0x3E8 => 0x3E9, +0x3EA => 0x3EB, +0x3EC => 0x3ED, +0x3EE => 0x3EF, +0x3F0 => 0x3BA, +0x3F1 => 0x3C1, +0x3F4 => 0x3B8, +0x3F5 => 0x3B5, +0x3F7 => 0x3F8, +0x3F9 => 0x3F2, +0x3FA => 0x3FB, +0x3FD => 0x37B, +0x3FE => 0x37C, +0x3FF => 0x37D, +0x400 => 0x450, +0x401 => 0x451, +0x402 => 0x452, +0x403 => 0x453, +0x404 => 0x454, +0x405 => 0x455, +0x406 => 0x456, +0x407 => 0x457, +0x408 => 0x458, +0x409 => 0x459, +0x40A => 0x45A, +0x40B => 0x45B, +0x40C => 0x45C, +0x40D => 0x45D, +0x40E => 0x45E, +0x40F => 0x45F, +0x410 => 0x430, +0x411 => 0x431, +0x412 => 0x432, +0x413 => 0x433, +0x414 => 0x434, +0x415 => 0x435, +0x416 => 0x436, +0x417 => 0x437, +0x418 => 0x438, +0x419 => 0x439, +0x41A => 0x43A, +0x41B => 0x43B, +0x41C => 0x43C, +0x41D => 0x43D, +0x41E => 0x43E, +0x41F => 0x43F, +0x420 => 0x440, +0x421 => 0x441, +0x422 => 0x442, +0x423 => 0x443, +0x424 => 0x444, +0x425 => 0x445, +0x426 => 0x446, +0x427 => 0x447, +0x428 => 0x448, +0x429 => 0x449, +0x42A => 0x44A, +0x42B => 0x44B, +0x42C => 0x44C, +0x42D => 0x44D, +0x42E => 0x44E, +0x42F => 0x44F, +0x460 => 0x461, +0x462 => 0x463, +0x464 => 0x465, +0x466 => 0x467, +0x468 => 0x469, +0x46A => 0x46B, +0x46C => 0x46D, +0x46E => 0x46F, +0x470 => 0x471, +0x472 => 0x473, +0x474 => 0x475, +0x476 => 0x477, +0x478 => 0x479, +0x47A => 0x47B, +0x47C => 0x47D, +0x47E => 0x47F, +0x480 => 0x481, +0x48A => 0x48B, +0x48C => 0x48D, +0x48E => 0x48F, +0x490 => 0x491, +0x492 => 0x493, +0x494 => 0x495, +0x496 => 0x497, +0x498 => 0x499, +0x49A => 0x49B, +0x49C => 0x49D, +0x49E => 0x49F, +0x4A0 => 0x4A1, +0x4A2 => 0x4A3, +0x4A4 => 0x4A5, +0x4A6 => 0x4A7, +0x4A8 => 0x4A9, +0x4AA => 0x4AB, +0x4AC => 0x4AD, +0x4AE => 0x4AF, +0x4B0 => 0x4B1, +0x4B2 => 0x4B3, +0x4B4 => 0x4B5, +0x4B6 => 0x4B7, +0x4B8 => 0x4B9, +0x4BA => 0x4BB, +0x4BC => 0x4BD, +0x4BE => 0x4BF, +0x4C0 => 0x4CF, +0x4C1 => 0x4C2, +0x4C3 => 0x4C4, +0x4C5 => 0x4C6, +0x4C7 => 0x4C8, +0x4C9 => 0x4CA, +0x4CB => 0x4CC, +0x4CD => 0x4CE, +0x4D0 => 0x4D1, +0x4D2 => 0x4D3, +0x4D4 => 0x4D5, +0x4D6 => 0x4D7, +0x4D8 => 0x4D9, +0x4DA => 0x4DB, +0x4DC => 0x4DD, +0x4DE => 0x4DF, +0x4E0 => 0x4E1, +0x4E2 => 0x4E3, +0x4E4 => 0x4E5, +0x4E6 => 0x4E7, +0x4E8 => 0x4E9, +0x4EA => 0x4EB, +0x4EC => 0x4ED, +0x4EE => 0x4EF, +0x4F0 => 0x4F1, +0x4F2 => 0x4F3, +0x4F4 => 0x4F5, +0x4F6 => 0x4F7, +0x4F8 => 0x4F9, +0x4FA => 0x4FB, +0x4FC => 0x4FD, +0x4FE => 0x4FF, +0x500 => 0x501, +0x502 => 0x503, +0x504 => 0x505, +0x506 => 0x507, +0x508 => 0x509, +0x50A => 0x50B, +0x50C => 0x50D, +0x50E => 0x50F, +0x510 => 0x511, +0x512 => 0x513, +0x514 => 0x515, +0x516 => 0x517, +0x518 => 0x519, +0x51A => 0x51B, +0x51C => 0x51D, +0x51E => 0x51F, +0x520 => 0x521, +0x522 => 0x523, +0x524 => 0x525, +0x526 => 0x527, +0x528 => 0x529, +0x52A => 0x52B, +0x52C => 0x52D, +0x52E => 0x52F, +0x531 => 0x561, +0x532 => 0x562, +0x533 => 0x563, +0x534 => 0x564, +0x535 => 0x565, +0x536 => 0x566, +0x537 => 0x567, +0x538 => 0x568, +0x539 => 0x569, +0x53A => 0x56A, +0x53B => 0x56B, +0x53C => 0x56C, +0x53D => 0x56D, +0x53E => 0x56E, +0x53F => 0x56F, +0x540 => 0x570, +0x541 => 0x571, +0x542 => 0x572, +0x543 => 0x573, +0x544 => 0x574, +0x545 => 0x575, +0x546 => 0x576, +0x547 => 0x577, +0x548 => 0x578, +0x549 => 0x579, +0x54A => 0x57A, +0x54B => 0x57B, +0x54C => 0x57C, +0x54D => 0x57D, +0x54E => 0x57E, +0x54F => 0x57F, +0x550 => 0x580, +0x551 => 0x581, +0x552 => 0x582, +0x553 => 0x583, +0x554 => 0x584, +0x555 => 0x585, +0x556 => 0x586, +0x10A0 => 0x2D00, +0x10A1 => 0x2D01, +0x10A2 => 0x2D02, +0x10A3 => 0x2D03, +0x10A4 => 0x2D04, +0x10A5 => 0x2D05, +0x10A6 => 0x2D06, +0x10A7 => 0x2D07, +0x10A8 => 0x2D08, +0x10A9 => 0x2D09, +0x10AA => 0x2D0A, +0x10AB => 0x2D0B, +0x10AC => 0x2D0C, +0x10AD => 0x2D0D, +0x10AE => 0x2D0E, +0x10AF => 0x2D0F, +0x10B0 => 0x2D10, +0x10B1 => 0x2D11, +0x10B2 => 0x2D12, +0x10B3 => 0x2D13, +0x10B4 => 0x2D14, +0x10B5 => 0x2D15, +0x10B6 => 0x2D16, +0x10B7 => 0x2D17, +0x10B8 => 0x2D18, +0x10B9 => 0x2D19, +0x10BA => 0x2D1A, +0x10BB => 0x2D1B, +0x10BC => 0x2D1C, +0x10BD => 0x2D1D, +0x10BE => 0x2D1E, +0x10BF => 0x2D1F, +0x10C0 => 0x2D20, +0x10C1 => 0x2D21, +0x10C2 => 0x2D22, +0x10C3 => 0x2D23, +0x10C4 => 0x2D24, +0x10C5 => 0x2D25, +0x10C7 => 0x2D27, +0x10CD => 0x2D2D, +0x13F8 => 0x13F0, +0x13F9 => 0x13F1, +0x13FA => 0x13F2, +0x13FB => 0x13F3, +0x13FC => 0x13F4, +0x13FD => 0x13F5, +0x1C80 => 0x432, +0x1C81 => 0x434, +0x1C82 => 0x43E, +0x1C83 => 0x441, +0x1C84 => 0x442, +0x1C85 => 0x442, +0x1C86 => 0x44A, +0x1C87 => 0x463, +0x1C88 => 0xA64B, +0x1C90 => 0x10D0, +0x1C91 => 0x10D1, +0x1C92 => 0x10D2, +0x1C93 => 0x10D3, +0x1C94 => 0x10D4, +0x1C95 => 0x10D5, +0x1C96 => 0x10D6, +0x1C97 => 0x10D7, +0x1C98 => 0x10D8, +0x1C99 => 0x10D9, +0x1C9A => 0x10DA, +0x1C9B => 0x10DB, +0x1C9C => 0x10DC, +0x1C9D => 0x10DD, +0x1C9E => 0x10DE, +0x1C9F => 0x10DF, +0x1CA0 => 0x10E0, +0x1CA1 => 0x10E1, +0x1CA2 => 0x10E2, +0x1CA3 => 0x10E3, +0x1CA4 => 0x10E4, +0x1CA5 => 0x10E5, +0x1CA6 => 0x10E6, +0x1CA7 => 0x10E7, +0x1CA8 => 0x10E8, +0x1CA9 => 0x10E9, +0x1CAA => 0x10EA, +0x1CAB => 0x10EB, +0x1CAC => 0x10EC, +0x1CAD => 0x10ED, +0x1CAE => 0x10EE, +0x1CAF => 0x10EF, +0x1CB0 => 0x10F0, +0x1CB1 => 0x10F1, +0x1CB2 => 0x10F2, +0x1CB3 => 0x10F3, +0x1CB4 => 0x10F4, +0x1CB5 => 0x10F5, +0x1CB6 => 0x10F6, +0x1CB7 => 0x10F7, +0x1CB8 => 0x10F8, +0x1CB9 => 0x10F9, +0x1CBA => 0x10FA, +0x1CBD => 0x10FD, +0x1CBE => 0x10FE, +0x1CBF => 0x10FF, +0x1E00 => 0x1E01, +0x1E02 => 0x1E03, +0x1E04 => 0x1E05, +0x1E06 => 0x1E07, +0x1E08 => 0x1E09, +0x1E0A => 0x1E0B, +0x1E0C => 0x1E0D, +0x1E0E => 0x1E0F, +0x1E10 => 0x1E11, +0x1E12 => 0x1E13, +0x1E14 => 0x1E15, +0x1E16 => 0x1E17, +0x1E18 => 0x1E19, +0x1E1A => 0x1E1B, +0x1E1C => 0x1E1D, +0x1E1E => 0x1E1F, +0x1E20 => 0x1E21, +0x1E22 => 0x1E23, +0x1E24 => 0x1E25, +0x1E26 => 0x1E27, +0x1E28 => 0x1E29, +0x1E2A => 0x1E2B, +0x1E2C => 0x1E2D, +0x1E2E => 0x1E2F, +0x1E30 => 0x1E31, +0x1E32 => 0x1E33, +0x1E34 => 0x1E35, +0x1E36 => 0x1E37, +0x1E38 => 0x1E39, +0x1E3A => 0x1E3B, +0x1E3C => 0x1E3D, +0x1E3E => 0x1E3F, +0x1E40 => 0x1E41, +0x1E42 => 0x1E43, +0x1E44 => 0x1E45, +0x1E46 => 0x1E47, +0x1E48 => 0x1E49, +0x1E4A => 0x1E4B, +0x1E4C => 0x1E4D, +0x1E4E => 0x1E4F, +0x1E50 => 0x1E51, +0x1E52 => 0x1E53, +0x1E54 => 0x1E55, +0x1E56 => 0x1E57, +0x1E58 => 0x1E59, +0x1E5A => 0x1E5B, +0x1E5C => 0x1E5D, +0x1E5E => 0x1E5F, +0x1E60 => 0x1E61, +0x1E62 => 0x1E63, +0x1E64 => 0x1E65, +0x1E66 => 0x1E67, +0x1E68 => 0x1E69, +0x1E6A => 0x1E6B, +0x1E6C => 0x1E6D, +0x1E6E => 0x1E6F, +0x1E70 => 0x1E71, +0x1E72 => 0x1E73, +0x1E74 => 0x1E75, +0x1E76 => 0x1E77, +0x1E78 => 0x1E79, +0x1E7A => 0x1E7B, +0x1E7C => 0x1E7D, +0x1E7E => 0x1E7F, +0x1E80 => 0x1E81, +0x1E82 => 0x1E83, +0x1E84 => 0x1E85, +0x1E86 => 0x1E87, +0x1E88 => 0x1E89, +0x1E8A => 0x1E8B, +0x1E8C => 0x1E8D, +0x1E8E => 0x1E8F, +0x1E90 => 0x1E91, +0x1E92 => 0x1E93, +0x1E94 => 0x1E95, +0x1E9B => 0x1E61, +0x1E9E => 0xDF, +0x1EA0 => 0x1EA1, +0x1EA2 => 0x1EA3, +0x1EA4 => 0x1EA5, +0x1EA6 => 0x1EA7, +0x1EA8 => 0x1EA9, +0x1EAA => 0x1EAB, +0x1EAC => 0x1EAD, +0x1EAE => 0x1EAF, +0x1EB0 => 0x1EB1, +0x1EB2 => 0x1EB3, +0x1EB4 => 0x1EB5, +0x1EB6 => 0x1EB7, +0x1EB8 => 0x1EB9, +0x1EBA => 0x1EBB, +0x1EBC => 0x1EBD, +0x1EBE => 0x1EBF, +0x1EC0 => 0x1EC1, +0x1EC2 => 0x1EC3, +0x1EC4 => 0x1EC5, +0x1EC6 => 0x1EC7, +0x1EC8 => 0x1EC9, +0x1ECA => 0x1ECB, +0x1ECC => 0x1ECD, +0x1ECE => 0x1ECF, +0x1ED0 => 0x1ED1, +0x1ED2 => 0x1ED3, +0x1ED4 => 0x1ED5, +0x1ED6 => 0x1ED7, +0x1ED8 => 0x1ED9, +0x1EDA => 0x1EDB, +0x1EDC => 0x1EDD, +0x1EDE => 0x1EDF, +0x1EE0 => 0x1EE1, +0x1EE2 => 0x1EE3, +0x1EE4 => 0x1EE5, +0x1EE6 => 0x1EE7, +0x1EE8 => 0x1EE9, +0x1EEA => 0x1EEB, +0x1EEC => 0x1EED, +0x1EEE => 0x1EEF, +0x1EF0 => 0x1EF1, +0x1EF2 => 0x1EF3, +0x1EF4 => 0x1EF5, +0x1EF6 => 0x1EF7, +0x1EF8 => 0x1EF9, +0x1EFA => 0x1EFB, +0x1EFC => 0x1EFD, +0x1EFE => 0x1EFF, +0x1F08 => 0x1F00, +0x1F09 => 0x1F01, +0x1F0A => 0x1F02, +0x1F0B => 0x1F03, +0x1F0C => 0x1F04, +0x1F0D => 0x1F05, +0x1F0E => 0x1F06, +0x1F0F => 0x1F07, +0x1F18 => 0x1F10, +0x1F19 => 0x1F11, +0x1F1A => 0x1F12, +0x1F1B => 0x1F13, +0x1F1C => 0x1F14, +0x1F1D => 0x1F15, +0x1F28 => 0x1F20, +0x1F29 => 0x1F21, +0x1F2A => 0x1F22, +0x1F2B => 0x1F23, +0x1F2C => 0x1F24, +0x1F2D => 0x1F25, +0x1F2E => 0x1F26, +0x1F2F => 0x1F27, +0x1F38 => 0x1F30, +0x1F39 => 0x1F31, +0x1F3A => 0x1F32, +0x1F3B => 0x1F33, +0x1F3C => 0x1F34, +0x1F3D => 0x1F35, +0x1F3E => 0x1F36, +0x1F3F => 0x1F37, +0x1F48 => 0x1F40, +0x1F49 => 0x1F41, +0x1F4A => 0x1F42, +0x1F4B => 0x1F43, +0x1F4C => 0x1F44, +0x1F4D => 0x1F45, +0x1F59 => 0x1F51, +0x1F5B => 0x1F53, +0x1F5D => 0x1F55, +0x1F5F => 0x1F57, +0x1F68 => 0x1F60, +0x1F69 => 0x1F61, +0x1F6A => 0x1F62, +0x1F6B => 0x1F63, +0x1F6C => 0x1F64, +0x1F6D => 0x1F65, +0x1F6E => 0x1F66, +0x1F6F => 0x1F67, +0x1F88 => 0x1F80, +0x1F89 => 0x1F81, +0x1F8A => 0x1F82, +0x1F8B => 0x1F83, +0x1F8C => 0x1F84, +0x1F8D => 0x1F85, +0x1F8E => 0x1F86, +0x1F8F => 0x1F87, +0x1F98 => 0x1F90, +0x1F99 => 0x1F91, +0x1F9A => 0x1F92, +0x1F9B => 0x1F93, +0x1F9C => 0x1F94, +0x1F9D => 0x1F95, +0x1F9E => 0x1F96, +0x1F9F => 0x1F97, +0x1FA8 => 0x1FA0, +0x1FA9 => 0x1FA1, +0x1FAA => 0x1FA2, +0x1FAB => 0x1FA3, +0x1FAC => 0x1FA4, +0x1FAD => 0x1FA5, +0x1FAE => 0x1FA6, +0x1FAF => 0x1FA7, +0x1FB8 => 0x1FB0, +0x1FB9 => 0x1FB1, +0x1FBA => 0x1F70, +0x1FBB => 0x1F71, +0x1FBC => 0x1FB3, +0x1FBE => 0x3B9, +0x1FC8 => 0x1F72, +0x1FC9 => 0x1F73, +0x1FCA => 0x1F74, +0x1FCB => 0x1F75, +0x1FCC => 0x1FC3, +0x1FD8 => 0x1FD0, +0x1FD9 => 0x1FD1, +0x1FDA => 0x1F76, +0x1FDB => 0x1F77, +0x1FE8 => 0x1FE0, +0x1FE9 => 0x1FE1, +0x1FEA => 0x1F7A, +0x1FEB => 0x1F7B, +0x1FEC => 0x1FE5, +0x1FF8 => 0x1F78, +0x1FF9 => 0x1F79, +0x1FFA => 0x1F7C, +0x1FFB => 0x1F7D, +0x1FFC => 0x1FF3, +0x2126 => 0x3C9, +0x212A => 0x6B, +0x212B => 0xE5, +0x2132 => 0x214E, +0x2160 => 0x2170, +0x2161 => 0x2171, +0x2162 => 0x2172, +0x2163 => 0x2173, +0x2164 => 0x2174, +0x2165 => 0x2175, +0x2166 => 0x2176, +0x2167 => 0x2177, +0x2168 => 0x2178, +0x2169 => 0x2179, +0x216A => 0x217A, +0x216B => 0x217B, +0x216C => 0x217C, +0x216D => 0x217D, +0x216E => 0x217E, +0x216F => 0x217F, +0x2183 => 0x2184, +0x24B6 => 0x24D0, +0x24B7 => 0x24D1, +0x24B8 => 0x24D2, +0x24B9 => 0x24D3, +0x24BA => 0x24D4, +0x24BB => 0x24D5, +0x24BC => 0x24D6, +0x24BD => 0x24D7, +0x24BE => 0x24D8, +0x24BF => 0x24D9, +0x24C0 => 0x24DA, +0x24C1 => 0x24DB, +0x24C2 => 0x24DC, +0x24C3 => 0x24DD, +0x24C4 => 0x24DE, +0x24C5 => 0x24DF, +0x24C6 => 0x24E0, +0x24C7 => 0x24E1, +0x24C8 => 0x24E2, +0x24C9 => 0x24E3, +0x24CA => 0x24E4, +0x24CB => 0x24E5, +0x24CC => 0x24E6, +0x24CD => 0x24E7, +0x24CE => 0x24E8, +0x24CF => 0x24E9, +0x2C00 => 0x2C30, +0x2C01 => 0x2C31, +0x2C02 => 0x2C32, +0x2C03 => 0x2C33, +0x2C04 => 0x2C34, +0x2C05 => 0x2C35, +0x2C06 => 0x2C36, +0x2C07 => 0x2C37, +0x2C08 => 0x2C38, +0x2C09 => 0x2C39, +0x2C0A => 0x2C3A, +0x2C0B => 0x2C3B, +0x2C0C => 0x2C3C, +0x2C0D => 0x2C3D, +0x2C0E => 0x2C3E, +0x2C0F => 0x2C3F, +0x2C10 => 0x2C40, +0x2C11 => 0x2C41, +0x2C12 => 0x2C42, +0x2C13 => 0x2C43, +0x2C14 => 0x2C44, +0x2C15 => 0x2C45, +0x2C16 => 0x2C46, +0x2C17 => 0x2C47, +0x2C18 => 0x2C48, +0x2C19 => 0x2C49, +0x2C1A => 0x2C4A, +0x2C1B => 0x2C4B, +0x2C1C => 0x2C4C, +0x2C1D => 0x2C4D, +0x2C1E => 0x2C4E, +0x2C1F => 0x2C4F, +0x2C20 => 0x2C50, +0x2C21 => 0x2C51, +0x2C22 => 0x2C52, +0x2C23 => 0x2C53, +0x2C24 => 0x2C54, +0x2C25 => 0x2C55, +0x2C26 => 0x2C56, +0x2C27 => 0x2C57, +0x2C28 => 0x2C58, +0x2C29 => 0x2C59, +0x2C2A => 0x2C5A, +0x2C2B => 0x2C5B, +0x2C2C => 0x2C5C, +0x2C2D => 0x2C5D, +0x2C2E => 0x2C5E, +0x2C60 => 0x2C61, +0x2C62 => 0x26B, +0x2C63 => 0x1D7D, +0x2C64 => 0x27D, +0x2C67 => 0x2C68, +0x2C69 => 0x2C6A, +0x2C6B => 0x2C6C, +0x2C6D => 0x251, +0x2C6E => 0x271, +0x2C6F => 0x250, +0x2C70 => 0x252, +0x2C72 => 0x2C73, +0x2C75 => 0x2C76, +0x2C7E => 0x23F, +0x2C7F => 0x240, +0x2C80 => 0x2C81, +0x2C82 => 0x2C83, +0x2C84 => 0x2C85, +0x2C86 => 0x2C87, +0x2C88 => 0x2C89, +0x2C8A => 0x2C8B, +0x2C8C => 0x2C8D, +0x2C8E => 0x2C8F, +0x2C90 => 0x2C91, +0x2C92 => 0x2C93, +0x2C94 => 0x2C95, +0x2C96 => 0x2C97, +0x2C98 => 0x2C99, +0x2C9A => 0x2C9B, +0x2C9C => 0x2C9D, +0x2C9E => 0x2C9F, +0x2CA0 => 0x2CA1, +0x2CA2 => 0x2CA3, +0x2CA4 => 0x2CA5, +0x2CA6 => 0x2CA7, +0x2CA8 => 0x2CA9, +0x2CAA => 0x2CAB, +0x2CAC => 0x2CAD, +0x2CAE => 0x2CAF, +0x2CB0 => 0x2CB1, +0x2CB2 => 0x2CB3, +0x2CB4 => 0x2CB5, +0x2CB6 => 0x2CB7, +0x2CB8 => 0x2CB9, +0x2CBA => 0x2CBB, +0x2CBC => 0x2CBD, +0x2CBE => 0x2CBF, +0x2CC0 => 0x2CC1, +0x2CC2 => 0x2CC3, +0x2CC4 => 0x2CC5, +0x2CC6 => 0x2CC7, +0x2CC8 => 0x2CC9, +0x2CCA => 0x2CCB, +0x2CCC => 0x2CCD, +0x2CCE => 0x2CCF, +0x2CD0 => 0x2CD1, +0x2CD2 => 0x2CD3, +0x2CD4 => 0x2CD5, +0x2CD6 => 0x2CD7, +0x2CD8 => 0x2CD9, +0x2CDA => 0x2CDB, +0x2CDC => 0x2CDD, +0x2CDE => 0x2CDF, +0x2CE0 => 0x2CE1, +0x2CE2 => 0x2CE3, +0x2CEB => 0x2CEC, +0x2CED => 0x2CEE, +0x2CF2 => 0x2CF3, +0xA640 => 0xA641, +0xA642 => 0xA643, +0xA644 => 0xA645, +0xA646 => 0xA647, +0xA648 => 0xA649, +0xA64A => 0xA64B, +0xA64C => 0xA64D, +0xA64E => 0xA64F, +0xA650 => 0xA651, +0xA652 => 0xA653, +0xA654 => 0xA655, +0xA656 => 0xA657, +0xA658 => 0xA659, +0xA65A => 0xA65B, +0xA65C => 0xA65D, +0xA65E => 0xA65F, +0xA660 => 0xA661, +0xA662 => 0xA663, +0xA664 => 0xA665, +0xA666 => 0xA667, +0xA668 => 0xA669, +0xA66A => 0xA66B, +0xA66C => 0xA66D, +0xA680 => 0xA681, +0xA682 => 0xA683, +0xA684 => 0xA685, +0xA686 => 0xA687, +0xA688 => 0xA689, +0xA68A => 0xA68B, +0xA68C => 0xA68D, +0xA68E => 0xA68F, +0xA690 => 0xA691, +0xA692 => 0xA693, +0xA694 => 0xA695, +0xA696 => 0xA697, +0xA698 => 0xA699, +0xA69A => 0xA69B, +0xA722 => 0xA723, +0xA724 => 0xA725, +0xA726 => 0xA727, +0xA728 => 0xA729, +0xA72A => 0xA72B, +0xA72C => 0xA72D, +0xA72E => 0xA72F, +0xA732 => 0xA733, +0xA734 => 0xA735, +0xA736 => 0xA737, +0xA738 => 0xA739, +0xA73A => 0xA73B, +0xA73C => 0xA73D, +0xA73E => 0xA73F, +0xA740 => 0xA741, +0xA742 => 0xA743, +0xA744 => 0xA745, +0xA746 => 0xA747, +0xA748 => 0xA749, +0xA74A => 0xA74B, +0xA74C => 0xA74D, +0xA74E => 0xA74F, +0xA750 => 0xA751, +0xA752 => 0xA753, +0xA754 => 0xA755, +0xA756 => 0xA757, +0xA758 => 0xA759, +0xA75A => 0xA75B, +0xA75C => 0xA75D, +0xA75E => 0xA75F, +0xA760 => 0xA761, +0xA762 => 0xA763, +0xA764 => 0xA765, +0xA766 => 0xA767, +0xA768 => 0xA769, +0xA76A => 0xA76B, +0xA76C => 0xA76D, +0xA76E => 0xA76F, +0xA779 => 0xA77A, +0xA77B => 0xA77C, +0xA77D => 0x1D79, +0xA77E => 0xA77F, +0xA780 => 0xA781, +0xA782 => 0xA783, +0xA784 => 0xA785, +0xA786 => 0xA787, +0xA78B => 0xA78C, +0xA78D => 0x265, +0xA790 => 0xA791, +0xA792 => 0xA793, +0xA796 => 0xA797, +0xA798 => 0xA799, +0xA79A => 0xA79B, +0xA79C => 0xA79D, +0xA79E => 0xA79F, +0xA7A0 => 0xA7A1, +0xA7A2 => 0xA7A3, +0xA7A4 => 0xA7A5, +0xA7A6 => 0xA7A7, +0xA7A8 => 0xA7A9, +0xA7AA => 0x266, +0xA7AB => 0x25C, +0xA7AC => 0x261, +0xA7AD => 0x26C, +0xA7AE => 0x26A, +0xA7B0 => 0x29E, +0xA7B1 => 0x287, +0xA7B2 => 0x29D, +0xA7B3 => 0xAB53, +0xA7B4 => 0xA7B5, +0xA7B6 => 0xA7B7, +0xA7B8 => 0xA7B9, +0xA7BA => 0xA7BB, +0xA7BC => 0xA7BD, +0xA7BE => 0xA7BF, +0xA7C2 => 0xA7C3, +0xA7C4 => 0xA794, +0xA7C5 => 0x282, +0xA7C6 => 0x1D8E, +0xA7C7 => 0xA7C8, +0xA7C9 => 0xA7CA, +0xA7F5 => 0xA7F6, +0xAB70 => 0x13A0, +0xAB71 => 0x13A1, +0xAB72 => 0x13A2, +0xAB73 => 0x13A3, +0xAB74 => 0x13A4, +0xAB75 => 0x13A5, +0xAB76 => 0x13A6, +0xAB77 => 0x13A7, +0xAB78 => 0x13A8, +0xAB79 => 0x13A9, +0xAB7A => 0x13AA, +0xAB7B => 0x13AB, +0xAB7C => 0x13AC, +0xAB7D => 0x13AD, +0xAB7E => 0x13AE, +0xAB7F => 0x13AF, +0xAB80 => 0x13B0, +0xAB81 => 0x13B1, +0xAB82 => 0x13B2, +0xAB83 => 0x13B3, +0xAB84 => 0x13B4, +0xAB85 => 0x13B5, +0xAB86 => 0x13B6, +0xAB87 => 0x13B7, +0xAB88 => 0x13B8, +0xAB89 => 0x13B9, +0xAB8A => 0x13BA, +0xAB8B => 0x13BB, +0xAB8C => 0x13BC, +0xAB8D => 0x13BD, +0xAB8E => 0x13BE, +0xAB8F => 0x13BF, +0xAB90 => 0x13C0, +0xAB91 => 0x13C1, +0xAB92 => 0x13C2, +0xAB93 => 0x13C3, +0xAB94 => 0x13C4, +0xAB95 => 0x13C5, +0xAB96 => 0x13C6, +0xAB97 => 0x13C7, +0xAB98 => 0x13C8, +0xAB99 => 0x13C9, +0xAB9A => 0x13CA, +0xAB9B => 0x13CB, +0xAB9C => 0x13CC, +0xAB9D => 0x13CD, +0xAB9E => 0x13CE, +0xAB9F => 0x13CF, +0xABA0 => 0x13D0, +0xABA1 => 0x13D1, +0xABA2 => 0x13D2, +0xABA3 => 0x13D3, +0xABA4 => 0x13D4, +0xABA5 => 0x13D5, +0xABA6 => 0x13D6, +0xABA7 => 0x13D7, +0xABA8 => 0x13D8, +0xABA9 => 0x13D9, +0xABAA => 0x13DA, +0xABAB => 0x13DB, +0xABAC => 0x13DC, +0xABAD => 0x13DD, +0xABAE => 0x13DE, +0xABAF => 0x13DF, +0xABB0 => 0x13E0, +0xABB1 => 0x13E1, +0xABB2 => 0x13E2, +0xABB3 => 0x13E3, +0xABB4 => 0x13E4, +0xABB5 => 0x13E5, +0xABB6 => 0x13E6, +0xABB7 => 0x13E7, +0xABB8 => 0x13E8, +0xABB9 => 0x13E9, +0xABBA => 0x13EA, +0xABBB => 0x13EB, +0xABBC => 0x13EC, +0xABBD => 0x13ED, +0xABBE => 0x13EE, +0xABBF => 0x13EF, +0xFF21 => 0xFF41, +0xFF22 => 0xFF42, +0xFF23 => 0xFF43, +0xFF24 => 0xFF44, +0xFF25 => 0xFF45, +0xFF26 => 0xFF46, +0xFF27 => 0xFF47, +0xFF28 => 0xFF48, +0xFF29 => 0xFF49, +0xFF2A => 0xFF4A, +0xFF2B => 0xFF4B, +0xFF2C => 0xFF4C, +0xFF2D => 0xFF4D, +0xFF2E => 0xFF4E, +0xFF2F => 0xFF4F, +0xFF30 => 0xFF50, +0xFF31 => 0xFF51, +0xFF32 => 0xFF52, +0xFF33 => 0xFF53, +0xFF34 => 0xFF54, +0xFF35 => 0xFF55, +0xFF36 => 0xFF56, +0xFF37 => 0xFF57, +0xFF38 => 0xFF58, +0xFF39 => 0xFF59, +0xFF3A => 0xFF5A, +0x10400 => 0x10428, +0x10401 => 0x10429, +0x10402 => 0x1042A, +0x10403 => 0x1042B, +0x10404 => 0x1042C, +0x10405 => 0x1042D, +0x10406 => 0x1042E, +0x10407 => 0x1042F, +0x10408 => 0x10430, +0x10409 => 0x10431, +0x1040A => 0x10432, +0x1040B => 0x10433, +0x1040C => 0x10434, +0x1040D => 0x10435, +0x1040E => 0x10436, +0x1040F => 0x10437, +0x10410 => 0x10438, +0x10411 => 0x10439, +0x10412 => 0x1043A, +0x10413 => 0x1043B, +0x10414 => 0x1043C, +0x10415 => 0x1043D, +0x10416 => 0x1043E, +0x10417 => 0x1043F, +0x10418 => 0x10440, +0x10419 => 0x10441, +0x1041A => 0x10442, +0x1041B => 0x10443, +0x1041C => 0x10444, +0x1041D => 0x10445, +0x1041E => 0x10446, +0x1041F => 0x10447, +0x10420 => 0x10448, +0x10421 => 0x10449, +0x10422 => 0x1044A, +0x10423 => 0x1044B, +0x10424 => 0x1044C, +0x10425 => 0x1044D, +0x10426 => 0x1044E, +0x10427 => 0x1044F, +0x104B0 => 0x104D8, +0x104B1 => 0x104D9, +0x104B2 => 0x104DA, +0x104B3 => 0x104DB, +0x104B4 => 0x104DC, +0x104B5 => 0x104DD, +0x104B6 => 0x104DE, +0x104B7 => 0x104DF, +0x104B8 => 0x104E0, +0x104B9 => 0x104E1, +0x104BA => 0x104E2, +0x104BB => 0x104E3, +0x104BC => 0x104E4, +0x104BD => 0x104E5, +0x104BE => 0x104E6, +0x104BF => 0x104E7, +0x104C0 => 0x104E8, +0x104C1 => 0x104E9, +0x104C2 => 0x104EA, +0x104C3 => 0x104EB, +0x104C4 => 0x104EC, +0x104C5 => 0x104ED, +0x104C6 => 0x104EE, +0x104C7 => 0x104EF, +0x104C8 => 0x104F0, +0x104C9 => 0x104F1, +0x104CA => 0x104F2, +0x104CB => 0x104F3, +0x104CC => 0x104F4, +0x104CD => 0x104F5, +0x104CE => 0x104F6, +0x104CF => 0x104F7, +0x104D0 => 0x104F8, +0x104D1 => 0x104F9, +0x104D2 => 0x104FA, +0x104D3 => 0x104FB, +0x10C80 => 0x10CC0, +0x10C81 => 0x10CC1, +0x10C82 => 0x10CC2, +0x10C83 => 0x10CC3, +0x10C84 => 0x10CC4, +0x10C85 => 0x10CC5, +0x10C86 => 0x10CC6, +0x10C87 => 0x10CC7, +0x10C88 => 0x10CC8, +0x10C89 => 0x10CC9, +0x10C8A => 0x10CCA, +0x10C8B => 0x10CCB, +0x10C8C => 0x10CCC, +0x10C8D => 0x10CCD, +0x10C8E => 0x10CCE, +0x10C8F => 0x10CCF, +0x10C90 => 0x10CD0, +0x10C91 => 0x10CD1, +0x10C92 => 0x10CD2, +0x10C93 => 0x10CD3, +0x10C94 => 0x10CD4, +0x10C95 => 0x10CD5, +0x10C96 => 0x10CD6, +0x10C97 => 0x10CD7, +0x10C98 => 0x10CD8, +0x10C99 => 0x10CD9, +0x10C9A => 0x10CDA, +0x10C9B => 0x10CDB, +0x10C9C => 0x10CDC, +0x10C9D => 0x10CDD, +0x10C9E => 0x10CDE, +0x10C9F => 0x10CDF, +0x10CA0 => 0x10CE0, +0x10CA1 => 0x10CE1, +0x10CA2 => 0x10CE2, +0x10CA3 => 0x10CE3, +0x10CA4 => 0x10CE4, +0x10CA5 => 0x10CE5, +0x10CA6 => 0x10CE6, +0x10CA7 => 0x10CE7, +0x10CA8 => 0x10CE8, +0x10CA9 => 0x10CE9, +0x10CAA => 0x10CEA, +0x10CAB => 0x10CEB, +0x10CAC => 0x10CEC, +0x10CAD => 0x10CED, +0x10CAE => 0x10CEE, +0x10CAF => 0x10CEF, +0x10CB0 => 0x10CF0, +0x10CB1 => 0x10CF1, +0x10CB2 => 0x10CF2, +0x118A0 => 0x118C0, +0x118A1 => 0x118C1, +0x118A2 => 0x118C2, +0x118A3 => 0x118C3, +0x118A4 => 0x118C4, +0x118A5 => 0x118C5, +0x118A6 => 0x118C6, +0x118A7 => 0x118C7, +0x118A8 => 0x118C8, +0x118A9 => 0x118C9, +0x118AA => 0x118CA, +0x118AB => 0x118CB, +0x118AC => 0x118CC, +0x118AD => 0x118CD, +0x118AE => 0x118CE, +0x118AF => 0x118CF, +0x118B0 => 0x118D0, +0x118B1 => 0x118D1, +0x118B2 => 0x118D2, +0x118B3 => 0x118D3, +0x118B4 => 0x118D4, +0x118B5 => 0x118D5, +0x118B6 => 0x118D6, +0x118B7 => 0x118D7, +0x118B8 => 0x118D8, +0x118B9 => 0x118D9, +0x118BA => 0x118DA, +0x118BB => 0x118DB, +0x118BC => 0x118DC, +0x118BD => 0x118DD, +0x118BE => 0x118DE, +0x118BF => 0x118DF, +0x16E40 => 0x16E60, +0x16E41 => 0x16E61, +0x16E42 => 0x16E62, +0x16E43 => 0x16E63, +0x16E44 => 0x16E64, +0x16E45 => 0x16E65, +0x16E46 => 0x16E66, +0x16E47 => 0x16E67, +0x16E48 => 0x16E68, +0x16E49 => 0x16E69, +0x16E4A => 0x16E6A, +0x16E4B => 0x16E6B, +0x16E4C => 0x16E6C, +0x16E4D => 0x16E6D, +0x16E4E => 0x16E6E, +0x16E4F => 0x16E6F, +0x16E50 => 0x16E70, +0x16E51 => 0x16E71, +0x16E52 => 0x16E72, +0x16E53 => 0x16E73, +0x16E54 => 0x16E74, +0x16E55 => 0x16E75, +0x16E56 => 0x16E76, +0x16E57 => 0x16E77, +0x16E58 => 0x16E78, +0x16E59 => 0x16E79, +0x16E5A => 0x16E7A, +0x16E5B => 0x16E7B, +0x16E5C => 0x16E7C, +0x16E5D => 0x16E7D, +0x16E5E => 0x16E7E, +0x16E5F => 0x16E7F, +0x1E900 => 0x1E922, +0x1E901 => 0x1E923, +0x1E902 => 0x1E924, +0x1E903 => 0x1E925, +0x1E904 => 0x1E926, +0x1E905 => 0x1E927, +0x1E906 => 0x1E928, +0x1E907 => 0x1E929, +0x1E908 => 0x1E92A, +0x1E909 => 0x1E92B, +0x1E90A => 0x1E92C, +0x1E90B => 0x1E92D, +0x1E90C => 0x1E92E, +0x1E90D => 0x1E92F, +0x1E90E => 0x1E930, +0x1E90F => 0x1E931, +0x1E910 => 0x1E932, +0x1E911 => 0x1E933, +0x1E912 => 0x1E934, +0x1E913 => 0x1E935, +0x1E914 => 0x1E936, +0x1E915 => 0x1E937, +0x1E916 => 0x1E938, +0x1E917 => 0x1E939, +0x1E918 => 0x1E93A, +0x1E919 => 0x1E93B, +0x1E91A => 0x1E93C, +0x1E91B => 0x1E93D, +0x1E91C => 0x1E93E, +0x1E91D => 0x1E93F, +0x1E91E => 0x1E940, +0x1E91F => 0x1E941, +0x1E920 => 0x1E942, +0x1E921 => 0x1E943, +]; diff --git a/admin/vendor/opis/string/res/lower.php b/admin/vendor/opis/string/res/lower.php new file mode 100644 index 0000000..b5697d1 --- /dev/null +++ b/admin/vendor/opis/string/res/lower.php @@ -0,0 +1,1396 @@ + 0x61, +0x42 => 0x62, +0x43 => 0x63, +0x44 => 0x64, +0x45 => 0x65, +0x46 => 0x66, +0x47 => 0x67, +0x48 => 0x68, +0x49 => 0x69, +0x4A => 0x6A, +0x4B => 0x6B, +0x4C => 0x6C, +0x4D => 0x6D, +0x4E => 0x6E, +0x4F => 0x6F, +0x50 => 0x70, +0x51 => 0x71, +0x52 => 0x72, +0x53 => 0x73, +0x54 => 0x74, +0x55 => 0x75, +0x56 => 0x76, +0x57 => 0x77, +0x58 => 0x78, +0x59 => 0x79, +0x5A => 0x7A, +0xC0 => 0xE0, +0xC1 => 0xE1, +0xC2 => 0xE2, +0xC3 => 0xE3, +0xC4 => 0xE4, +0xC5 => 0xE5, +0xC6 => 0xE6, +0xC7 => 0xE7, +0xC8 => 0xE8, +0xC9 => 0xE9, +0xCA => 0xEA, +0xCB => 0xEB, +0xCC => 0xEC, +0xCD => 0xED, +0xCE => 0xEE, +0xCF => 0xEF, +0xD0 => 0xF0, +0xD1 => 0xF1, +0xD2 => 0xF2, +0xD3 => 0xF3, +0xD4 => 0xF4, +0xD5 => 0xF5, +0xD6 => 0xF6, +0xD8 => 0xF8, +0xD9 => 0xF9, +0xDA => 0xFA, +0xDB => 0xFB, +0xDC => 0xFC, +0xDD => 0xFD, +0xDE => 0xFE, +0x100 => 0x101, +0x102 => 0x103, +0x104 => 0x105, +0x106 => 0x107, +0x108 => 0x109, +0x10A => 0x10B, +0x10C => 0x10D, +0x10E => 0x10F, +0x110 => 0x111, +0x112 => 0x113, +0x114 => 0x115, +0x116 => 0x117, +0x118 => 0x119, +0x11A => 0x11B, +0x11C => 0x11D, +0x11E => 0x11F, +0x120 => 0x121, +0x122 => 0x123, +0x124 => 0x125, +0x126 => 0x127, +0x128 => 0x129, +0x12A => 0x12B, +0x12C => 0x12D, +0x12E => 0x12F, +0x130 => 0x69, +0x132 => 0x133, +0x134 => 0x135, +0x136 => 0x137, +0x139 => 0x13A, +0x13B => 0x13C, +0x13D => 0x13E, +0x13F => 0x140, +0x141 => 0x142, +0x143 => 0x144, +0x145 => 0x146, +0x147 => 0x148, +0x14A => 0x14B, +0x14C => 0x14D, +0x14E => 0x14F, +0x150 => 0x151, +0x152 => 0x153, +0x154 => 0x155, +0x156 => 0x157, +0x158 => 0x159, +0x15A => 0x15B, +0x15C => 0x15D, +0x15E => 0x15F, +0x160 => 0x161, +0x162 => 0x163, +0x164 => 0x165, +0x166 => 0x167, +0x168 => 0x169, +0x16A => 0x16B, +0x16C => 0x16D, +0x16E => 0x16F, +0x170 => 0x171, +0x172 => 0x173, +0x174 => 0x175, +0x176 => 0x177, +0x178 => 0xFF, +0x179 => 0x17A, +0x17B => 0x17C, +0x17D => 0x17E, +0x181 => 0x253, +0x182 => 0x183, +0x184 => 0x185, +0x186 => 0x254, +0x187 => 0x188, +0x189 => 0x256, +0x18A => 0x257, +0x18B => 0x18C, +0x18E => 0x1DD, +0x18F => 0x259, +0x190 => 0x25B, +0x191 => 0x192, +0x193 => 0x260, +0x194 => 0x263, +0x196 => 0x269, +0x197 => 0x268, +0x198 => 0x199, +0x19C => 0x26F, +0x19D => 0x272, +0x19F => 0x275, +0x1A0 => 0x1A1, +0x1A2 => 0x1A3, +0x1A4 => 0x1A5, +0x1A6 => 0x280, +0x1A7 => 0x1A8, +0x1A9 => 0x283, +0x1AC => 0x1AD, +0x1AE => 0x288, +0x1AF => 0x1B0, +0x1B1 => 0x28A, +0x1B2 => 0x28B, +0x1B3 => 0x1B4, +0x1B5 => 0x1B6, +0x1B7 => 0x292, +0x1B8 => 0x1B9, +0x1BC => 0x1BD, +0x1C4 => 0x1C6, +0x1C5 => 0x1C6, +0x1C7 => 0x1C9, +0x1C8 => 0x1C9, +0x1CA => 0x1CC, +0x1CB => 0x1CC, +0x1CD => 0x1CE, +0x1CF => 0x1D0, +0x1D1 => 0x1D2, +0x1D3 => 0x1D4, +0x1D5 => 0x1D6, +0x1D7 => 0x1D8, +0x1D9 => 0x1DA, +0x1DB => 0x1DC, +0x1DE => 0x1DF, +0x1E0 => 0x1E1, +0x1E2 => 0x1E3, +0x1E4 => 0x1E5, +0x1E6 => 0x1E7, +0x1E8 => 0x1E9, +0x1EA => 0x1EB, +0x1EC => 0x1ED, +0x1EE => 0x1EF, +0x1F1 => 0x1F3, +0x1F2 => 0x1F3, +0x1F4 => 0x1F5, +0x1F6 => 0x195, +0x1F7 => 0x1BF, +0x1F8 => 0x1F9, +0x1FA => 0x1FB, +0x1FC => 0x1FD, +0x1FE => 0x1FF, +0x200 => 0x201, +0x202 => 0x203, +0x204 => 0x205, +0x206 => 0x207, +0x208 => 0x209, +0x20A => 0x20B, +0x20C => 0x20D, +0x20E => 0x20F, +0x210 => 0x211, +0x212 => 0x213, +0x214 => 0x215, +0x216 => 0x217, +0x218 => 0x219, +0x21A => 0x21B, +0x21C => 0x21D, +0x21E => 0x21F, +0x220 => 0x19E, +0x222 => 0x223, +0x224 => 0x225, +0x226 => 0x227, +0x228 => 0x229, +0x22A => 0x22B, +0x22C => 0x22D, +0x22E => 0x22F, +0x230 => 0x231, +0x232 => 0x233, +0x23A => 0x2C65, +0x23B => 0x23C, +0x23D => 0x19A, +0x23E => 0x2C66, +0x241 => 0x242, +0x243 => 0x180, +0x244 => 0x289, +0x245 => 0x28C, +0x246 => 0x247, +0x248 => 0x249, +0x24A => 0x24B, +0x24C => 0x24D, +0x24E => 0x24F, +0x370 => 0x371, +0x372 => 0x373, +0x376 => 0x377, +0x37F => 0x3F3, +0x386 => 0x3AC, +0x388 => 0x3AD, +0x389 => 0x3AE, +0x38A => 0x3AF, +0x38C => 0x3CC, +0x38E => 0x3CD, +0x38F => 0x3CE, +0x391 => 0x3B1, +0x392 => 0x3B2, +0x393 => 0x3B3, +0x394 => 0x3B4, +0x395 => 0x3B5, +0x396 => 0x3B6, +0x397 => 0x3B7, +0x398 => 0x3B8, +0x399 => 0x3B9, +0x39A => 0x3BA, +0x39B => 0x3BB, +0x39C => 0x3BC, +0x39D => 0x3BD, +0x39E => 0x3BE, +0x39F => 0x3BF, +0x3A0 => 0x3C0, +0x3A1 => 0x3C1, +0x3A3 => 0x3C3, +0x3A4 => 0x3C4, +0x3A5 => 0x3C5, +0x3A6 => 0x3C6, +0x3A7 => 0x3C7, +0x3A8 => 0x3C8, +0x3A9 => 0x3C9, +0x3AA => 0x3CA, +0x3AB => 0x3CB, +0x3CF => 0x3D7, +0x3D8 => 0x3D9, +0x3DA => 0x3DB, +0x3DC => 0x3DD, +0x3DE => 0x3DF, +0x3E0 => 0x3E1, +0x3E2 => 0x3E3, +0x3E4 => 0x3E5, +0x3E6 => 0x3E7, +0x3E8 => 0x3E9, +0x3EA => 0x3EB, +0x3EC => 0x3ED, +0x3EE => 0x3EF, +0x3F4 => 0x3B8, +0x3F7 => 0x3F8, +0x3F9 => 0x3F2, +0x3FA => 0x3FB, +0x3FD => 0x37B, +0x3FE => 0x37C, +0x3FF => 0x37D, +0x400 => 0x450, +0x401 => 0x451, +0x402 => 0x452, +0x403 => 0x453, +0x404 => 0x454, +0x405 => 0x455, +0x406 => 0x456, +0x407 => 0x457, +0x408 => 0x458, +0x409 => 0x459, +0x40A => 0x45A, +0x40B => 0x45B, +0x40C => 0x45C, +0x40D => 0x45D, +0x40E => 0x45E, +0x40F => 0x45F, +0x410 => 0x430, +0x411 => 0x431, +0x412 => 0x432, +0x413 => 0x433, +0x414 => 0x434, +0x415 => 0x435, +0x416 => 0x436, +0x417 => 0x437, +0x418 => 0x438, +0x419 => 0x439, +0x41A => 0x43A, +0x41B => 0x43B, +0x41C => 0x43C, +0x41D => 0x43D, +0x41E => 0x43E, +0x41F => 0x43F, +0x420 => 0x440, +0x421 => 0x441, +0x422 => 0x442, +0x423 => 0x443, +0x424 => 0x444, +0x425 => 0x445, +0x426 => 0x446, +0x427 => 0x447, +0x428 => 0x448, +0x429 => 0x449, +0x42A => 0x44A, +0x42B => 0x44B, +0x42C => 0x44C, +0x42D => 0x44D, +0x42E => 0x44E, +0x42F => 0x44F, +0x460 => 0x461, +0x462 => 0x463, +0x464 => 0x465, +0x466 => 0x467, +0x468 => 0x469, +0x46A => 0x46B, +0x46C => 0x46D, +0x46E => 0x46F, +0x470 => 0x471, +0x472 => 0x473, +0x474 => 0x475, +0x476 => 0x477, +0x478 => 0x479, +0x47A => 0x47B, +0x47C => 0x47D, +0x47E => 0x47F, +0x480 => 0x481, +0x48A => 0x48B, +0x48C => 0x48D, +0x48E => 0x48F, +0x490 => 0x491, +0x492 => 0x493, +0x494 => 0x495, +0x496 => 0x497, +0x498 => 0x499, +0x49A => 0x49B, +0x49C => 0x49D, +0x49E => 0x49F, +0x4A0 => 0x4A1, +0x4A2 => 0x4A3, +0x4A4 => 0x4A5, +0x4A6 => 0x4A7, +0x4A8 => 0x4A9, +0x4AA => 0x4AB, +0x4AC => 0x4AD, +0x4AE => 0x4AF, +0x4B0 => 0x4B1, +0x4B2 => 0x4B3, +0x4B4 => 0x4B5, +0x4B6 => 0x4B7, +0x4B8 => 0x4B9, +0x4BA => 0x4BB, +0x4BC => 0x4BD, +0x4BE => 0x4BF, +0x4C0 => 0x4CF, +0x4C1 => 0x4C2, +0x4C3 => 0x4C4, +0x4C5 => 0x4C6, +0x4C7 => 0x4C8, +0x4C9 => 0x4CA, +0x4CB => 0x4CC, +0x4CD => 0x4CE, +0x4D0 => 0x4D1, +0x4D2 => 0x4D3, +0x4D4 => 0x4D5, +0x4D6 => 0x4D7, +0x4D8 => 0x4D9, +0x4DA => 0x4DB, +0x4DC => 0x4DD, +0x4DE => 0x4DF, +0x4E0 => 0x4E1, +0x4E2 => 0x4E3, +0x4E4 => 0x4E5, +0x4E6 => 0x4E7, +0x4E8 => 0x4E9, +0x4EA => 0x4EB, +0x4EC => 0x4ED, +0x4EE => 0x4EF, +0x4F0 => 0x4F1, +0x4F2 => 0x4F3, +0x4F4 => 0x4F5, +0x4F6 => 0x4F7, +0x4F8 => 0x4F9, +0x4FA => 0x4FB, +0x4FC => 0x4FD, +0x4FE => 0x4FF, +0x500 => 0x501, +0x502 => 0x503, +0x504 => 0x505, +0x506 => 0x507, +0x508 => 0x509, +0x50A => 0x50B, +0x50C => 0x50D, +0x50E => 0x50F, +0x510 => 0x511, +0x512 => 0x513, +0x514 => 0x515, +0x516 => 0x517, +0x518 => 0x519, +0x51A => 0x51B, +0x51C => 0x51D, +0x51E => 0x51F, +0x520 => 0x521, +0x522 => 0x523, +0x524 => 0x525, +0x526 => 0x527, +0x528 => 0x529, +0x52A => 0x52B, +0x52C => 0x52D, +0x52E => 0x52F, +0x531 => 0x561, +0x532 => 0x562, +0x533 => 0x563, +0x534 => 0x564, +0x535 => 0x565, +0x536 => 0x566, +0x537 => 0x567, +0x538 => 0x568, +0x539 => 0x569, +0x53A => 0x56A, +0x53B => 0x56B, +0x53C => 0x56C, +0x53D => 0x56D, +0x53E => 0x56E, +0x53F => 0x56F, +0x540 => 0x570, +0x541 => 0x571, +0x542 => 0x572, +0x543 => 0x573, +0x544 => 0x574, +0x545 => 0x575, +0x546 => 0x576, +0x547 => 0x577, +0x548 => 0x578, +0x549 => 0x579, +0x54A => 0x57A, +0x54B => 0x57B, +0x54C => 0x57C, +0x54D => 0x57D, +0x54E => 0x57E, +0x54F => 0x57F, +0x550 => 0x580, +0x551 => 0x581, +0x552 => 0x582, +0x553 => 0x583, +0x554 => 0x584, +0x555 => 0x585, +0x556 => 0x586, +0x10A0 => 0x2D00, +0x10A1 => 0x2D01, +0x10A2 => 0x2D02, +0x10A3 => 0x2D03, +0x10A4 => 0x2D04, +0x10A5 => 0x2D05, +0x10A6 => 0x2D06, +0x10A7 => 0x2D07, +0x10A8 => 0x2D08, +0x10A9 => 0x2D09, +0x10AA => 0x2D0A, +0x10AB => 0x2D0B, +0x10AC => 0x2D0C, +0x10AD => 0x2D0D, +0x10AE => 0x2D0E, +0x10AF => 0x2D0F, +0x10B0 => 0x2D10, +0x10B1 => 0x2D11, +0x10B2 => 0x2D12, +0x10B3 => 0x2D13, +0x10B4 => 0x2D14, +0x10B5 => 0x2D15, +0x10B6 => 0x2D16, +0x10B7 => 0x2D17, +0x10B8 => 0x2D18, +0x10B9 => 0x2D19, +0x10BA => 0x2D1A, +0x10BB => 0x2D1B, +0x10BC => 0x2D1C, +0x10BD => 0x2D1D, +0x10BE => 0x2D1E, +0x10BF => 0x2D1F, +0x10C0 => 0x2D20, +0x10C1 => 0x2D21, +0x10C2 => 0x2D22, +0x10C3 => 0x2D23, +0x10C4 => 0x2D24, +0x10C5 => 0x2D25, +0x10C7 => 0x2D27, +0x10CD => 0x2D2D, +0x13A0 => 0xAB70, +0x13A1 => 0xAB71, +0x13A2 => 0xAB72, +0x13A3 => 0xAB73, +0x13A4 => 0xAB74, +0x13A5 => 0xAB75, +0x13A6 => 0xAB76, +0x13A7 => 0xAB77, +0x13A8 => 0xAB78, +0x13A9 => 0xAB79, +0x13AA => 0xAB7A, +0x13AB => 0xAB7B, +0x13AC => 0xAB7C, +0x13AD => 0xAB7D, +0x13AE => 0xAB7E, +0x13AF => 0xAB7F, +0x13B0 => 0xAB80, +0x13B1 => 0xAB81, +0x13B2 => 0xAB82, +0x13B3 => 0xAB83, +0x13B4 => 0xAB84, +0x13B5 => 0xAB85, +0x13B6 => 0xAB86, +0x13B7 => 0xAB87, +0x13B8 => 0xAB88, +0x13B9 => 0xAB89, +0x13BA => 0xAB8A, +0x13BB => 0xAB8B, +0x13BC => 0xAB8C, +0x13BD => 0xAB8D, +0x13BE => 0xAB8E, +0x13BF => 0xAB8F, +0x13C0 => 0xAB90, +0x13C1 => 0xAB91, +0x13C2 => 0xAB92, +0x13C3 => 0xAB93, +0x13C4 => 0xAB94, +0x13C5 => 0xAB95, +0x13C6 => 0xAB96, +0x13C7 => 0xAB97, +0x13C8 => 0xAB98, +0x13C9 => 0xAB99, +0x13CA => 0xAB9A, +0x13CB => 0xAB9B, +0x13CC => 0xAB9C, +0x13CD => 0xAB9D, +0x13CE => 0xAB9E, +0x13CF => 0xAB9F, +0x13D0 => 0xABA0, +0x13D1 => 0xABA1, +0x13D2 => 0xABA2, +0x13D3 => 0xABA3, +0x13D4 => 0xABA4, +0x13D5 => 0xABA5, +0x13D6 => 0xABA6, +0x13D7 => 0xABA7, +0x13D8 => 0xABA8, +0x13D9 => 0xABA9, +0x13DA => 0xABAA, +0x13DB => 0xABAB, +0x13DC => 0xABAC, +0x13DD => 0xABAD, +0x13DE => 0xABAE, +0x13DF => 0xABAF, +0x13E0 => 0xABB0, +0x13E1 => 0xABB1, +0x13E2 => 0xABB2, +0x13E3 => 0xABB3, +0x13E4 => 0xABB4, +0x13E5 => 0xABB5, +0x13E6 => 0xABB6, +0x13E7 => 0xABB7, +0x13E8 => 0xABB8, +0x13E9 => 0xABB9, +0x13EA => 0xABBA, +0x13EB => 0xABBB, +0x13EC => 0xABBC, +0x13ED => 0xABBD, +0x13EE => 0xABBE, +0x13EF => 0xABBF, +0x13F0 => 0x13F8, +0x13F1 => 0x13F9, +0x13F2 => 0x13FA, +0x13F3 => 0x13FB, +0x13F4 => 0x13FC, +0x13F5 => 0x13FD, +0x1C90 => 0x10D0, +0x1C91 => 0x10D1, +0x1C92 => 0x10D2, +0x1C93 => 0x10D3, +0x1C94 => 0x10D4, +0x1C95 => 0x10D5, +0x1C96 => 0x10D6, +0x1C97 => 0x10D7, +0x1C98 => 0x10D8, +0x1C99 => 0x10D9, +0x1C9A => 0x10DA, +0x1C9B => 0x10DB, +0x1C9C => 0x10DC, +0x1C9D => 0x10DD, +0x1C9E => 0x10DE, +0x1C9F => 0x10DF, +0x1CA0 => 0x10E0, +0x1CA1 => 0x10E1, +0x1CA2 => 0x10E2, +0x1CA3 => 0x10E3, +0x1CA4 => 0x10E4, +0x1CA5 => 0x10E5, +0x1CA6 => 0x10E6, +0x1CA7 => 0x10E7, +0x1CA8 => 0x10E8, +0x1CA9 => 0x10E9, +0x1CAA => 0x10EA, +0x1CAB => 0x10EB, +0x1CAC => 0x10EC, +0x1CAD => 0x10ED, +0x1CAE => 0x10EE, +0x1CAF => 0x10EF, +0x1CB0 => 0x10F0, +0x1CB1 => 0x10F1, +0x1CB2 => 0x10F2, +0x1CB3 => 0x10F3, +0x1CB4 => 0x10F4, +0x1CB5 => 0x10F5, +0x1CB6 => 0x10F6, +0x1CB7 => 0x10F7, +0x1CB8 => 0x10F8, +0x1CB9 => 0x10F9, +0x1CBA => 0x10FA, +0x1CBD => 0x10FD, +0x1CBE => 0x10FE, +0x1CBF => 0x10FF, +0x1E00 => 0x1E01, +0x1E02 => 0x1E03, +0x1E04 => 0x1E05, +0x1E06 => 0x1E07, +0x1E08 => 0x1E09, +0x1E0A => 0x1E0B, +0x1E0C => 0x1E0D, +0x1E0E => 0x1E0F, +0x1E10 => 0x1E11, +0x1E12 => 0x1E13, +0x1E14 => 0x1E15, +0x1E16 => 0x1E17, +0x1E18 => 0x1E19, +0x1E1A => 0x1E1B, +0x1E1C => 0x1E1D, +0x1E1E => 0x1E1F, +0x1E20 => 0x1E21, +0x1E22 => 0x1E23, +0x1E24 => 0x1E25, +0x1E26 => 0x1E27, +0x1E28 => 0x1E29, +0x1E2A => 0x1E2B, +0x1E2C => 0x1E2D, +0x1E2E => 0x1E2F, +0x1E30 => 0x1E31, +0x1E32 => 0x1E33, +0x1E34 => 0x1E35, +0x1E36 => 0x1E37, +0x1E38 => 0x1E39, +0x1E3A => 0x1E3B, +0x1E3C => 0x1E3D, +0x1E3E => 0x1E3F, +0x1E40 => 0x1E41, +0x1E42 => 0x1E43, +0x1E44 => 0x1E45, +0x1E46 => 0x1E47, +0x1E48 => 0x1E49, +0x1E4A => 0x1E4B, +0x1E4C => 0x1E4D, +0x1E4E => 0x1E4F, +0x1E50 => 0x1E51, +0x1E52 => 0x1E53, +0x1E54 => 0x1E55, +0x1E56 => 0x1E57, +0x1E58 => 0x1E59, +0x1E5A => 0x1E5B, +0x1E5C => 0x1E5D, +0x1E5E => 0x1E5F, +0x1E60 => 0x1E61, +0x1E62 => 0x1E63, +0x1E64 => 0x1E65, +0x1E66 => 0x1E67, +0x1E68 => 0x1E69, +0x1E6A => 0x1E6B, +0x1E6C => 0x1E6D, +0x1E6E => 0x1E6F, +0x1E70 => 0x1E71, +0x1E72 => 0x1E73, +0x1E74 => 0x1E75, +0x1E76 => 0x1E77, +0x1E78 => 0x1E79, +0x1E7A => 0x1E7B, +0x1E7C => 0x1E7D, +0x1E7E => 0x1E7F, +0x1E80 => 0x1E81, +0x1E82 => 0x1E83, +0x1E84 => 0x1E85, +0x1E86 => 0x1E87, +0x1E88 => 0x1E89, +0x1E8A => 0x1E8B, +0x1E8C => 0x1E8D, +0x1E8E => 0x1E8F, +0x1E90 => 0x1E91, +0x1E92 => 0x1E93, +0x1E94 => 0x1E95, +0x1E9E => 0xDF, +0x1EA0 => 0x1EA1, +0x1EA2 => 0x1EA3, +0x1EA4 => 0x1EA5, +0x1EA6 => 0x1EA7, +0x1EA8 => 0x1EA9, +0x1EAA => 0x1EAB, +0x1EAC => 0x1EAD, +0x1EAE => 0x1EAF, +0x1EB0 => 0x1EB1, +0x1EB2 => 0x1EB3, +0x1EB4 => 0x1EB5, +0x1EB6 => 0x1EB7, +0x1EB8 => 0x1EB9, +0x1EBA => 0x1EBB, +0x1EBC => 0x1EBD, +0x1EBE => 0x1EBF, +0x1EC0 => 0x1EC1, +0x1EC2 => 0x1EC3, +0x1EC4 => 0x1EC5, +0x1EC6 => 0x1EC7, +0x1EC8 => 0x1EC9, +0x1ECA => 0x1ECB, +0x1ECC => 0x1ECD, +0x1ECE => 0x1ECF, +0x1ED0 => 0x1ED1, +0x1ED2 => 0x1ED3, +0x1ED4 => 0x1ED5, +0x1ED6 => 0x1ED7, +0x1ED8 => 0x1ED9, +0x1EDA => 0x1EDB, +0x1EDC => 0x1EDD, +0x1EDE => 0x1EDF, +0x1EE0 => 0x1EE1, +0x1EE2 => 0x1EE3, +0x1EE4 => 0x1EE5, +0x1EE6 => 0x1EE7, +0x1EE8 => 0x1EE9, +0x1EEA => 0x1EEB, +0x1EEC => 0x1EED, +0x1EEE => 0x1EEF, +0x1EF0 => 0x1EF1, +0x1EF2 => 0x1EF3, +0x1EF4 => 0x1EF5, +0x1EF6 => 0x1EF7, +0x1EF8 => 0x1EF9, +0x1EFA => 0x1EFB, +0x1EFC => 0x1EFD, +0x1EFE => 0x1EFF, +0x1F08 => 0x1F00, +0x1F09 => 0x1F01, +0x1F0A => 0x1F02, +0x1F0B => 0x1F03, +0x1F0C => 0x1F04, +0x1F0D => 0x1F05, +0x1F0E => 0x1F06, +0x1F0F => 0x1F07, +0x1F18 => 0x1F10, +0x1F19 => 0x1F11, +0x1F1A => 0x1F12, +0x1F1B => 0x1F13, +0x1F1C => 0x1F14, +0x1F1D => 0x1F15, +0x1F28 => 0x1F20, +0x1F29 => 0x1F21, +0x1F2A => 0x1F22, +0x1F2B => 0x1F23, +0x1F2C => 0x1F24, +0x1F2D => 0x1F25, +0x1F2E => 0x1F26, +0x1F2F => 0x1F27, +0x1F38 => 0x1F30, +0x1F39 => 0x1F31, +0x1F3A => 0x1F32, +0x1F3B => 0x1F33, +0x1F3C => 0x1F34, +0x1F3D => 0x1F35, +0x1F3E => 0x1F36, +0x1F3F => 0x1F37, +0x1F48 => 0x1F40, +0x1F49 => 0x1F41, +0x1F4A => 0x1F42, +0x1F4B => 0x1F43, +0x1F4C => 0x1F44, +0x1F4D => 0x1F45, +0x1F59 => 0x1F51, +0x1F5B => 0x1F53, +0x1F5D => 0x1F55, +0x1F5F => 0x1F57, +0x1F68 => 0x1F60, +0x1F69 => 0x1F61, +0x1F6A => 0x1F62, +0x1F6B => 0x1F63, +0x1F6C => 0x1F64, +0x1F6D => 0x1F65, +0x1F6E => 0x1F66, +0x1F6F => 0x1F67, +0x1F88 => 0x1F80, +0x1F89 => 0x1F81, +0x1F8A => 0x1F82, +0x1F8B => 0x1F83, +0x1F8C => 0x1F84, +0x1F8D => 0x1F85, +0x1F8E => 0x1F86, +0x1F8F => 0x1F87, +0x1F98 => 0x1F90, +0x1F99 => 0x1F91, +0x1F9A => 0x1F92, +0x1F9B => 0x1F93, +0x1F9C => 0x1F94, +0x1F9D => 0x1F95, +0x1F9E => 0x1F96, +0x1F9F => 0x1F97, +0x1FA8 => 0x1FA0, +0x1FA9 => 0x1FA1, +0x1FAA => 0x1FA2, +0x1FAB => 0x1FA3, +0x1FAC => 0x1FA4, +0x1FAD => 0x1FA5, +0x1FAE => 0x1FA6, +0x1FAF => 0x1FA7, +0x1FB8 => 0x1FB0, +0x1FB9 => 0x1FB1, +0x1FBA => 0x1F70, +0x1FBB => 0x1F71, +0x1FBC => 0x1FB3, +0x1FC8 => 0x1F72, +0x1FC9 => 0x1F73, +0x1FCA => 0x1F74, +0x1FCB => 0x1F75, +0x1FCC => 0x1FC3, +0x1FD8 => 0x1FD0, +0x1FD9 => 0x1FD1, +0x1FDA => 0x1F76, +0x1FDB => 0x1F77, +0x1FE8 => 0x1FE0, +0x1FE9 => 0x1FE1, +0x1FEA => 0x1F7A, +0x1FEB => 0x1F7B, +0x1FEC => 0x1FE5, +0x1FF8 => 0x1F78, +0x1FF9 => 0x1F79, +0x1FFA => 0x1F7C, +0x1FFB => 0x1F7D, +0x1FFC => 0x1FF3, +0x2126 => 0x3C9, +0x212A => 0x6B, +0x212B => 0xE5, +0x2132 => 0x214E, +0x2160 => 0x2170, +0x2161 => 0x2171, +0x2162 => 0x2172, +0x2163 => 0x2173, +0x2164 => 0x2174, +0x2165 => 0x2175, +0x2166 => 0x2176, +0x2167 => 0x2177, +0x2168 => 0x2178, +0x2169 => 0x2179, +0x216A => 0x217A, +0x216B => 0x217B, +0x216C => 0x217C, +0x216D => 0x217D, +0x216E => 0x217E, +0x216F => 0x217F, +0x2183 => 0x2184, +0x24B6 => 0x24D0, +0x24B7 => 0x24D1, +0x24B8 => 0x24D2, +0x24B9 => 0x24D3, +0x24BA => 0x24D4, +0x24BB => 0x24D5, +0x24BC => 0x24D6, +0x24BD => 0x24D7, +0x24BE => 0x24D8, +0x24BF => 0x24D9, +0x24C0 => 0x24DA, +0x24C1 => 0x24DB, +0x24C2 => 0x24DC, +0x24C3 => 0x24DD, +0x24C4 => 0x24DE, +0x24C5 => 0x24DF, +0x24C6 => 0x24E0, +0x24C7 => 0x24E1, +0x24C8 => 0x24E2, +0x24C9 => 0x24E3, +0x24CA => 0x24E4, +0x24CB => 0x24E5, +0x24CC => 0x24E6, +0x24CD => 0x24E7, +0x24CE => 0x24E8, +0x24CF => 0x24E9, +0x2C00 => 0x2C30, +0x2C01 => 0x2C31, +0x2C02 => 0x2C32, +0x2C03 => 0x2C33, +0x2C04 => 0x2C34, +0x2C05 => 0x2C35, +0x2C06 => 0x2C36, +0x2C07 => 0x2C37, +0x2C08 => 0x2C38, +0x2C09 => 0x2C39, +0x2C0A => 0x2C3A, +0x2C0B => 0x2C3B, +0x2C0C => 0x2C3C, +0x2C0D => 0x2C3D, +0x2C0E => 0x2C3E, +0x2C0F => 0x2C3F, +0x2C10 => 0x2C40, +0x2C11 => 0x2C41, +0x2C12 => 0x2C42, +0x2C13 => 0x2C43, +0x2C14 => 0x2C44, +0x2C15 => 0x2C45, +0x2C16 => 0x2C46, +0x2C17 => 0x2C47, +0x2C18 => 0x2C48, +0x2C19 => 0x2C49, +0x2C1A => 0x2C4A, +0x2C1B => 0x2C4B, +0x2C1C => 0x2C4C, +0x2C1D => 0x2C4D, +0x2C1E => 0x2C4E, +0x2C1F => 0x2C4F, +0x2C20 => 0x2C50, +0x2C21 => 0x2C51, +0x2C22 => 0x2C52, +0x2C23 => 0x2C53, +0x2C24 => 0x2C54, +0x2C25 => 0x2C55, +0x2C26 => 0x2C56, +0x2C27 => 0x2C57, +0x2C28 => 0x2C58, +0x2C29 => 0x2C59, +0x2C2A => 0x2C5A, +0x2C2B => 0x2C5B, +0x2C2C => 0x2C5C, +0x2C2D => 0x2C5D, +0x2C2E => 0x2C5E, +0x2C60 => 0x2C61, +0x2C62 => 0x26B, +0x2C63 => 0x1D7D, +0x2C64 => 0x27D, +0x2C67 => 0x2C68, +0x2C69 => 0x2C6A, +0x2C6B => 0x2C6C, +0x2C6D => 0x251, +0x2C6E => 0x271, +0x2C6F => 0x250, +0x2C70 => 0x252, +0x2C72 => 0x2C73, +0x2C75 => 0x2C76, +0x2C7E => 0x23F, +0x2C7F => 0x240, +0x2C80 => 0x2C81, +0x2C82 => 0x2C83, +0x2C84 => 0x2C85, +0x2C86 => 0x2C87, +0x2C88 => 0x2C89, +0x2C8A => 0x2C8B, +0x2C8C => 0x2C8D, +0x2C8E => 0x2C8F, +0x2C90 => 0x2C91, +0x2C92 => 0x2C93, +0x2C94 => 0x2C95, +0x2C96 => 0x2C97, +0x2C98 => 0x2C99, +0x2C9A => 0x2C9B, +0x2C9C => 0x2C9D, +0x2C9E => 0x2C9F, +0x2CA0 => 0x2CA1, +0x2CA2 => 0x2CA3, +0x2CA4 => 0x2CA5, +0x2CA6 => 0x2CA7, +0x2CA8 => 0x2CA9, +0x2CAA => 0x2CAB, +0x2CAC => 0x2CAD, +0x2CAE => 0x2CAF, +0x2CB0 => 0x2CB1, +0x2CB2 => 0x2CB3, +0x2CB4 => 0x2CB5, +0x2CB6 => 0x2CB7, +0x2CB8 => 0x2CB9, +0x2CBA => 0x2CBB, +0x2CBC => 0x2CBD, +0x2CBE => 0x2CBF, +0x2CC0 => 0x2CC1, +0x2CC2 => 0x2CC3, +0x2CC4 => 0x2CC5, +0x2CC6 => 0x2CC7, +0x2CC8 => 0x2CC9, +0x2CCA => 0x2CCB, +0x2CCC => 0x2CCD, +0x2CCE => 0x2CCF, +0x2CD0 => 0x2CD1, +0x2CD2 => 0x2CD3, +0x2CD4 => 0x2CD5, +0x2CD6 => 0x2CD7, +0x2CD8 => 0x2CD9, +0x2CDA => 0x2CDB, +0x2CDC => 0x2CDD, +0x2CDE => 0x2CDF, +0x2CE0 => 0x2CE1, +0x2CE2 => 0x2CE3, +0x2CEB => 0x2CEC, +0x2CED => 0x2CEE, +0x2CF2 => 0x2CF3, +0xA640 => 0xA641, +0xA642 => 0xA643, +0xA644 => 0xA645, +0xA646 => 0xA647, +0xA648 => 0xA649, +0xA64A => 0xA64B, +0xA64C => 0xA64D, +0xA64E => 0xA64F, +0xA650 => 0xA651, +0xA652 => 0xA653, +0xA654 => 0xA655, +0xA656 => 0xA657, +0xA658 => 0xA659, +0xA65A => 0xA65B, +0xA65C => 0xA65D, +0xA65E => 0xA65F, +0xA660 => 0xA661, +0xA662 => 0xA663, +0xA664 => 0xA665, +0xA666 => 0xA667, +0xA668 => 0xA669, +0xA66A => 0xA66B, +0xA66C => 0xA66D, +0xA680 => 0xA681, +0xA682 => 0xA683, +0xA684 => 0xA685, +0xA686 => 0xA687, +0xA688 => 0xA689, +0xA68A => 0xA68B, +0xA68C => 0xA68D, +0xA68E => 0xA68F, +0xA690 => 0xA691, +0xA692 => 0xA693, +0xA694 => 0xA695, +0xA696 => 0xA697, +0xA698 => 0xA699, +0xA69A => 0xA69B, +0xA722 => 0xA723, +0xA724 => 0xA725, +0xA726 => 0xA727, +0xA728 => 0xA729, +0xA72A => 0xA72B, +0xA72C => 0xA72D, +0xA72E => 0xA72F, +0xA732 => 0xA733, +0xA734 => 0xA735, +0xA736 => 0xA737, +0xA738 => 0xA739, +0xA73A => 0xA73B, +0xA73C => 0xA73D, +0xA73E => 0xA73F, +0xA740 => 0xA741, +0xA742 => 0xA743, +0xA744 => 0xA745, +0xA746 => 0xA747, +0xA748 => 0xA749, +0xA74A => 0xA74B, +0xA74C => 0xA74D, +0xA74E => 0xA74F, +0xA750 => 0xA751, +0xA752 => 0xA753, +0xA754 => 0xA755, +0xA756 => 0xA757, +0xA758 => 0xA759, +0xA75A => 0xA75B, +0xA75C => 0xA75D, +0xA75E => 0xA75F, +0xA760 => 0xA761, +0xA762 => 0xA763, +0xA764 => 0xA765, +0xA766 => 0xA767, +0xA768 => 0xA769, +0xA76A => 0xA76B, +0xA76C => 0xA76D, +0xA76E => 0xA76F, +0xA779 => 0xA77A, +0xA77B => 0xA77C, +0xA77D => 0x1D79, +0xA77E => 0xA77F, +0xA780 => 0xA781, +0xA782 => 0xA783, +0xA784 => 0xA785, +0xA786 => 0xA787, +0xA78B => 0xA78C, +0xA78D => 0x265, +0xA790 => 0xA791, +0xA792 => 0xA793, +0xA796 => 0xA797, +0xA798 => 0xA799, +0xA79A => 0xA79B, +0xA79C => 0xA79D, +0xA79E => 0xA79F, +0xA7A0 => 0xA7A1, +0xA7A2 => 0xA7A3, +0xA7A4 => 0xA7A5, +0xA7A6 => 0xA7A7, +0xA7A8 => 0xA7A9, +0xA7AA => 0x266, +0xA7AB => 0x25C, +0xA7AC => 0x261, +0xA7AD => 0x26C, +0xA7AE => 0x26A, +0xA7B0 => 0x29E, +0xA7B1 => 0x287, +0xA7B2 => 0x29D, +0xA7B3 => 0xAB53, +0xA7B4 => 0xA7B5, +0xA7B6 => 0xA7B7, +0xA7B8 => 0xA7B9, +0xA7BA => 0xA7BB, +0xA7BC => 0xA7BD, +0xA7BE => 0xA7BF, +0xA7C2 => 0xA7C3, +0xA7C4 => 0xA794, +0xA7C5 => 0x282, +0xA7C6 => 0x1D8E, +0xA7C7 => 0xA7C8, +0xA7C9 => 0xA7CA, +0xA7F5 => 0xA7F6, +0xFF21 => 0xFF41, +0xFF22 => 0xFF42, +0xFF23 => 0xFF43, +0xFF24 => 0xFF44, +0xFF25 => 0xFF45, +0xFF26 => 0xFF46, +0xFF27 => 0xFF47, +0xFF28 => 0xFF48, +0xFF29 => 0xFF49, +0xFF2A => 0xFF4A, +0xFF2B => 0xFF4B, +0xFF2C => 0xFF4C, +0xFF2D => 0xFF4D, +0xFF2E => 0xFF4E, +0xFF2F => 0xFF4F, +0xFF30 => 0xFF50, +0xFF31 => 0xFF51, +0xFF32 => 0xFF52, +0xFF33 => 0xFF53, +0xFF34 => 0xFF54, +0xFF35 => 0xFF55, +0xFF36 => 0xFF56, +0xFF37 => 0xFF57, +0xFF38 => 0xFF58, +0xFF39 => 0xFF59, +0xFF3A => 0xFF5A, +0x10400 => 0x10428, +0x10401 => 0x10429, +0x10402 => 0x1042A, +0x10403 => 0x1042B, +0x10404 => 0x1042C, +0x10405 => 0x1042D, +0x10406 => 0x1042E, +0x10407 => 0x1042F, +0x10408 => 0x10430, +0x10409 => 0x10431, +0x1040A => 0x10432, +0x1040B => 0x10433, +0x1040C => 0x10434, +0x1040D => 0x10435, +0x1040E => 0x10436, +0x1040F => 0x10437, +0x10410 => 0x10438, +0x10411 => 0x10439, +0x10412 => 0x1043A, +0x10413 => 0x1043B, +0x10414 => 0x1043C, +0x10415 => 0x1043D, +0x10416 => 0x1043E, +0x10417 => 0x1043F, +0x10418 => 0x10440, +0x10419 => 0x10441, +0x1041A => 0x10442, +0x1041B => 0x10443, +0x1041C => 0x10444, +0x1041D => 0x10445, +0x1041E => 0x10446, +0x1041F => 0x10447, +0x10420 => 0x10448, +0x10421 => 0x10449, +0x10422 => 0x1044A, +0x10423 => 0x1044B, +0x10424 => 0x1044C, +0x10425 => 0x1044D, +0x10426 => 0x1044E, +0x10427 => 0x1044F, +0x104B0 => 0x104D8, +0x104B1 => 0x104D9, +0x104B2 => 0x104DA, +0x104B3 => 0x104DB, +0x104B4 => 0x104DC, +0x104B5 => 0x104DD, +0x104B6 => 0x104DE, +0x104B7 => 0x104DF, +0x104B8 => 0x104E0, +0x104B9 => 0x104E1, +0x104BA => 0x104E2, +0x104BB => 0x104E3, +0x104BC => 0x104E4, +0x104BD => 0x104E5, +0x104BE => 0x104E6, +0x104BF => 0x104E7, +0x104C0 => 0x104E8, +0x104C1 => 0x104E9, +0x104C2 => 0x104EA, +0x104C3 => 0x104EB, +0x104C4 => 0x104EC, +0x104C5 => 0x104ED, +0x104C6 => 0x104EE, +0x104C7 => 0x104EF, +0x104C8 => 0x104F0, +0x104C9 => 0x104F1, +0x104CA => 0x104F2, +0x104CB => 0x104F3, +0x104CC => 0x104F4, +0x104CD => 0x104F5, +0x104CE => 0x104F6, +0x104CF => 0x104F7, +0x104D0 => 0x104F8, +0x104D1 => 0x104F9, +0x104D2 => 0x104FA, +0x104D3 => 0x104FB, +0x10C80 => 0x10CC0, +0x10C81 => 0x10CC1, +0x10C82 => 0x10CC2, +0x10C83 => 0x10CC3, +0x10C84 => 0x10CC4, +0x10C85 => 0x10CC5, +0x10C86 => 0x10CC6, +0x10C87 => 0x10CC7, +0x10C88 => 0x10CC8, +0x10C89 => 0x10CC9, +0x10C8A => 0x10CCA, +0x10C8B => 0x10CCB, +0x10C8C => 0x10CCC, +0x10C8D => 0x10CCD, +0x10C8E => 0x10CCE, +0x10C8F => 0x10CCF, +0x10C90 => 0x10CD0, +0x10C91 => 0x10CD1, +0x10C92 => 0x10CD2, +0x10C93 => 0x10CD3, +0x10C94 => 0x10CD4, +0x10C95 => 0x10CD5, +0x10C96 => 0x10CD6, +0x10C97 => 0x10CD7, +0x10C98 => 0x10CD8, +0x10C99 => 0x10CD9, +0x10C9A => 0x10CDA, +0x10C9B => 0x10CDB, +0x10C9C => 0x10CDC, +0x10C9D => 0x10CDD, +0x10C9E => 0x10CDE, +0x10C9F => 0x10CDF, +0x10CA0 => 0x10CE0, +0x10CA1 => 0x10CE1, +0x10CA2 => 0x10CE2, +0x10CA3 => 0x10CE3, +0x10CA4 => 0x10CE4, +0x10CA5 => 0x10CE5, +0x10CA6 => 0x10CE6, +0x10CA7 => 0x10CE7, +0x10CA8 => 0x10CE8, +0x10CA9 => 0x10CE9, +0x10CAA => 0x10CEA, +0x10CAB => 0x10CEB, +0x10CAC => 0x10CEC, +0x10CAD => 0x10CED, +0x10CAE => 0x10CEE, +0x10CAF => 0x10CEF, +0x10CB0 => 0x10CF0, +0x10CB1 => 0x10CF1, +0x10CB2 => 0x10CF2, +0x118A0 => 0x118C0, +0x118A1 => 0x118C1, +0x118A2 => 0x118C2, +0x118A3 => 0x118C3, +0x118A4 => 0x118C4, +0x118A5 => 0x118C5, +0x118A6 => 0x118C6, +0x118A7 => 0x118C7, +0x118A8 => 0x118C8, +0x118A9 => 0x118C9, +0x118AA => 0x118CA, +0x118AB => 0x118CB, +0x118AC => 0x118CC, +0x118AD => 0x118CD, +0x118AE => 0x118CE, +0x118AF => 0x118CF, +0x118B0 => 0x118D0, +0x118B1 => 0x118D1, +0x118B2 => 0x118D2, +0x118B3 => 0x118D3, +0x118B4 => 0x118D4, +0x118B5 => 0x118D5, +0x118B6 => 0x118D6, +0x118B7 => 0x118D7, +0x118B8 => 0x118D8, +0x118B9 => 0x118D9, +0x118BA => 0x118DA, +0x118BB => 0x118DB, +0x118BC => 0x118DC, +0x118BD => 0x118DD, +0x118BE => 0x118DE, +0x118BF => 0x118DF, +0x16E40 => 0x16E60, +0x16E41 => 0x16E61, +0x16E42 => 0x16E62, +0x16E43 => 0x16E63, +0x16E44 => 0x16E64, +0x16E45 => 0x16E65, +0x16E46 => 0x16E66, +0x16E47 => 0x16E67, +0x16E48 => 0x16E68, +0x16E49 => 0x16E69, +0x16E4A => 0x16E6A, +0x16E4B => 0x16E6B, +0x16E4C => 0x16E6C, +0x16E4D => 0x16E6D, +0x16E4E => 0x16E6E, +0x16E4F => 0x16E6F, +0x16E50 => 0x16E70, +0x16E51 => 0x16E71, +0x16E52 => 0x16E72, +0x16E53 => 0x16E73, +0x16E54 => 0x16E74, +0x16E55 => 0x16E75, +0x16E56 => 0x16E76, +0x16E57 => 0x16E77, +0x16E58 => 0x16E78, +0x16E59 => 0x16E79, +0x16E5A => 0x16E7A, +0x16E5B => 0x16E7B, +0x16E5C => 0x16E7C, +0x16E5D => 0x16E7D, +0x16E5E => 0x16E7E, +0x16E5F => 0x16E7F, +0x1E900 => 0x1E922, +0x1E901 => 0x1E923, +0x1E902 => 0x1E924, +0x1E903 => 0x1E925, +0x1E904 => 0x1E926, +0x1E905 => 0x1E927, +0x1E906 => 0x1E928, +0x1E907 => 0x1E929, +0x1E908 => 0x1E92A, +0x1E909 => 0x1E92B, +0x1E90A => 0x1E92C, +0x1E90B => 0x1E92D, +0x1E90C => 0x1E92E, +0x1E90D => 0x1E92F, +0x1E90E => 0x1E930, +0x1E90F => 0x1E931, +0x1E910 => 0x1E932, +0x1E911 => 0x1E933, +0x1E912 => 0x1E934, +0x1E913 => 0x1E935, +0x1E914 => 0x1E936, +0x1E915 => 0x1E937, +0x1E916 => 0x1E938, +0x1E917 => 0x1E939, +0x1E918 => 0x1E93A, +0x1E919 => 0x1E93B, +0x1E91A => 0x1E93C, +0x1E91B => 0x1E93D, +0x1E91C => 0x1E93E, +0x1E91D => 0x1E93F, +0x1E91E => 0x1E940, +0x1E91F => 0x1E941, +0x1E920 => 0x1E942, +0x1E921 => 0x1E943, +]; diff --git a/admin/vendor/opis/string/res/upper.php b/admin/vendor/opis/string/res/upper.php new file mode 100644 index 0000000..376ce6e --- /dev/null +++ b/admin/vendor/opis/string/res/upper.php @@ -0,0 +1,1413 @@ + 0x41, +0x62 => 0x42, +0x63 => 0x43, +0x64 => 0x44, +0x65 => 0x45, +0x66 => 0x46, +0x67 => 0x47, +0x68 => 0x48, +0x69 => 0x49, +0x6A => 0x4A, +0x6B => 0x4B, +0x6C => 0x4C, +0x6D => 0x4D, +0x6E => 0x4E, +0x6F => 0x4F, +0x70 => 0x50, +0x71 => 0x51, +0x72 => 0x52, +0x73 => 0x53, +0x74 => 0x54, +0x75 => 0x55, +0x76 => 0x56, +0x77 => 0x57, +0x78 => 0x58, +0x79 => 0x59, +0x7A => 0x5A, +0xB5 => 0x39C, +0xE0 => 0xC0, +0xE1 => 0xC1, +0xE2 => 0xC2, +0xE3 => 0xC3, +0xE4 => 0xC4, +0xE5 => 0xC5, +0xE6 => 0xC6, +0xE7 => 0xC7, +0xE8 => 0xC8, +0xE9 => 0xC9, +0xEA => 0xCA, +0xEB => 0xCB, +0xEC => 0xCC, +0xED => 0xCD, +0xEE => 0xCE, +0xEF => 0xCF, +0xF0 => 0xD0, +0xF1 => 0xD1, +0xF2 => 0xD2, +0xF3 => 0xD3, +0xF4 => 0xD4, +0xF5 => 0xD5, +0xF6 => 0xD6, +0xF8 => 0xD8, +0xF9 => 0xD9, +0xFA => 0xDA, +0xFB => 0xDB, +0xFC => 0xDC, +0xFD => 0xDD, +0xFE => 0xDE, +0xFF => 0x178, +0x101 => 0x100, +0x103 => 0x102, +0x105 => 0x104, +0x107 => 0x106, +0x109 => 0x108, +0x10B => 0x10A, +0x10D => 0x10C, +0x10F => 0x10E, +0x111 => 0x110, +0x113 => 0x112, +0x115 => 0x114, +0x117 => 0x116, +0x119 => 0x118, +0x11B => 0x11A, +0x11D => 0x11C, +0x11F => 0x11E, +0x121 => 0x120, +0x123 => 0x122, +0x125 => 0x124, +0x127 => 0x126, +0x129 => 0x128, +0x12B => 0x12A, +0x12D => 0x12C, +0x12F => 0x12E, +0x131 => 0x49, +0x133 => 0x132, +0x135 => 0x134, +0x137 => 0x136, +0x13A => 0x139, +0x13C => 0x13B, +0x13E => 0x13D, +0x140 => 0x13F, +0x142 => 0x141, +0x144 => 0x143, +0x146 => 0x145, +0x148 => 0x147, +0x14B => 0x14A, +0x14D => 0x14C, +0x14F => 0x14E, +0x151 => 0x150, +0x153 => 0x152, +0x155 => 0x154, +0x157 => 0x156, +0x159 => 0x158, +0x15B => 0x15A, +0x15D => 0x15C, +0x15F => 0x15E, +0x161 => 0x160, +0x163 => 0x162, +0x165 => 0x164, +0x167 => 0x166, +0x169 => 0x168, +0x16B => 0x16A, +0x16D => 0x16C, +0x16F => 0x16E, +0x171 => 0x170, +0x173 => 0x172, +0x175 => 0x174, +0x177 => 0x176, +0x17A => 0x179, +0x17C => 0x17B, +0x17E => 0x17D, +0x17F => 0x53, +0x180 => 0x243, +0x183 => 0x182, +0x185 => 0x184, +0x188 => 0x187, +0x18C => 0x18B, +0x192 => 0x191, +0x195 => 0x1F6, +0x199 => 0x198, +0x19A => 0x23D, +0x19E => 0x220, +0x1A1 => 0x1A0, +0x1A3 => 0x1A2, +0x1A5 => 0x1A4, +0x1A8 => 0x1A7, +0x1AD => 0x1AC, +0x1B0 => 0x1AF, +0x1B4 => 0x1B3, +0x1B6 => 0x1B5, +0x1B9 => 0x1B8, +0x1BD => 0x1BC, +0x1BF => 0x1F7, +0x1C5 => 0x1C4, +0x1C6 => 0x1C4, +0x1C8 => 0x1C7, +0x1C9 => 0x1C7, +0x1CB => 0x1CA, +0x1CC => 0x1CA, +0x1CE => 0x1CD, +0x1D0 => 0x1CF, +0x1D2 => 0x1D1, +0x1D4 => 0x1D3, +0x1D6 => 0x1D5, +0x1D8 => 0x1D7, +0x1DA => 0x1D9, +0x1DC => 0x1DB, +0x1DD => 0x18E, +0x1DF => 0x1DE, +0x1E1 => 0x1E0, +0x1E3 => 0x1E2, +0x1E5 => 0x1E4, +0x1E7 => 0x1E6, +0x1E9 => 0x1E8, +0x1EB => 0x1EA, +0x1ED => 0x1EC, +0x1EF => 0x1EE, +0x1F2 => 0x1F1, +0x1F3 => 0x1F1, +0x1F5 => 0x1F4, +0x1F9 => 0x1F8, +0x1FB => 0x1FA, +0x1FD => 0x1FC, +0x1FF => 0x1FE, +0x201 => 0x200, +0x203 => 0x202, +0x205 => 0x204, +0x207 => 0x206, +0x209 => 0x208, +0x20B => 0x20A, +0x20D => 0x20C, +0x20F => 0x20E, +0x211 => 0x210, +0x213 => 0x212, +0x215 => 0x214, +0x217 => 0x216, +0x219 => 0x218, +0x21B => 0x21A, +0x21D => 0x21C, +0x21F => 0x21E, +0x223 => 0x222, +0x225 => 0x224, +0x227 => 0x226, +0x229 => 0x228, +0x22B => 0x22A, +0x22D => 0x22C, +0x22F => 0x22E, +0x231 => 0x230, +0x233 => 0x232, +0x23C => 0x23B, +0x23F => 0x2C7E, +0x240 => 0x2C7F, +0x242 => 0x241, +0x247 => 0x246, +0x249 => 0x248, +0x24B => 0x24A, +0x24D => 0x24C, +0x24F => 0x24E, +0x250 => 0x2C6F, +0x251 => 0x2C6D, +0x252 => 0x2C70, +0x253 => 0x181, +0x254 => 0x186, +0x256 => 0x189, +0x257 => 0x18A, +0x259 => 0x18F, +0x25B => 0x190, +0x25C => 0xA7AB, +0x260 => 0x193, +0x261 => 0xA7AC, +0x263 => 0x194, +0x265 => 0xA78D, +0x266 => 0xA7AA, +0x268 => 0x197, +0x269 => 0x196, +0x26A => 0xA7AE, +0x26B => 0x2C62, +0x26C => 0xA7AD, +0x26F => 0x19C, +0x271 => 0x2C6E, +0x272 => 0x19D, +0x275 => 0x19F, +0x27D => 0x2C64, +0x280 => 0x1A6, +0x282 => 0xA7C5, +0x283 => 0x1A9, +0x287 => 0xA7B1, +0x288 => 0x1AE, +0x289 => 0x244, +0x28A => 0x1B1, +0x28B => 0x1B2, +0x28C => 0x245, +0x292 => 0x1B7, +0x29D => 0xA7B2, +0x29E => 0xA7B0, +0x345 => 0x399, +0x371 => 0x370, +0x373 => 0x372, +0x377 => 0x376, +0x37B => 0x3FD, +0x37C => 0x3FE, +0x37D => 0x3FF, +0x3AC => 0x386, +0x3AD => 0x388, +0x3AE => 0x389, +0x3AF => 0x38A, +0x3B1 => 0x391, +0x3B2 => 0x392, +0x3B3 => 0x393, +0x3B4 => 0x394, +0x3B5 => 0x395, +0x3B6 => 0x396, +0x3B7 => 0x397, +0x3B8 => 0x398, +0x3B9 => 0x399, +0x3BA => 0x39A, +0x3BB => 0x39B, +0x3BC => 0x39C, +0x3BD => 0x39D, +0x3BE => 0x39E, +0x3BF => 0x39F, +0x3C0 => 0x3A0, +0x3C1 => 0x3A1, +0x3C2 => 0x3A3, +0x3C3 => 0x3A3, +0x3C4 => 0x3A4, +0x3C5 => 0x3A5, +0x3C6 => 0x3A6, +0x3C7 => 0x3A7, +0x3C8 => 0x3A8, +0x3C9 => 0x3A9, +0x3CA => 0x3AA, +0x3CB => 0x3AB, +0x3CC => 0x38C, +0x3CD => 0x38E, +0x3CE => 0x38F, +0x3D0 => 0x392, +0x3D1 => 0x398, +0x3D5 => 0x3A6, +0x3D6 => 0x3A0, +0x3D7 => 0x3CF, +0x3D9 => 0x3D8, +0x3DB => 0x3DA, +0x3DD => 0x3DC, +0x3DF => 0x3DE, +0x3E1 => 0x3E0, +0x3E3 => 0x3E2, +0x3E5 => 0x3E4, +0x3E7 => 0x3E6, +0x3E9 => 0x3E8, +0x3EB => 0x3EA, +0x3ED => 0x3EC, +0x3EF => 0x3EE, +0x3F0 => 0x39A, +0x3F1 => 0x3A1, +0x3F2 => 0x3F9, +0x3F3 => 0x37F, +0x3F5 => 0x395, +0x3F8 => 0x3F7, +0x3FB => 0x3FA, +0x430 => 0x410, +0x431 => 0x411, +0x432 => 0x412, +0x433 => 0x413, +0x434 => 0x414, +0x435 => 0x415, +0x436 => 0x416, +0x437 => 0x417, +0x438 => 0x418, +0x439 => 0x419, +0x43A => 0x41A, +0x43B => 0x41B, +0x43C => 0x41C, +0x43D => 0x41D, +0x43E => 0x41E, +0x43F => 0x41F, +0x440 => 0x420, +0x441 => 0x421, +0x442 => 0x422, +0x443 => 0x423, +0x444 => 0x424, +0x445 => 0x425, +0x446 => 0x426, +0x447 => 0x427, +0x448 => 0x428, +0x449 => 0x429, +0x44A => 0x42A, +0x44B => 0x42B, +0x44C => 0x42C, +0x44D => 0x42D, +0x44E => 0x42E, +0x44F => 0x42F, +0x450 => 0x400, +0x451 => 0x401, +0x452 => 0x402, +0x453 => 0x403, +0x454 => 0x404, +0x455 => 0x405, +0x456 => 0x406, +0x457 => 0x407, +0x458 => 0x408, +0x459 => 0x409, +0x45A => 0x40A, +0x45B => 0x40B, +0x45C => 0x40C, +0x45D => 0x40D, +0x45E => 0x40E, +0x45F => 0x40F, +0x461 => 0x460, +0x463 => 0x462, +0x465 => 0x464, +0x467 => 0x466, +0x469 => 0x468, +0x46B => 0x46A, +0x46D => 0x46C, +0x46F => 0x46E, +0x471 => 0x470, +0x473 => 0x472, +0x475 => 0x474, +0x477 => 0x476, +0x479 => 0x478, +0x47B => 0x47A, +0x47D => 0x47C, +0x47F => 0x47E, +0x481 => 0x480, +0x48B => 0x48A, +0x48D => 0x48C, +0x48F => 0x48E, +0x491 => 0x490, +0x493 => 0x492, +0x495 => 0x494, +0x497 => 0x496, +0x499 => 0x498, +0x49B => 0x49A, +0x49D => 0x49C, +0x49F => 0x49E, +0x4A1 => 0x4A0, +0x4A3 => 0x4A2, +0x4A5 => 0x4A4, +0x4A7 => 0x4A6, +0x4A9 => 0x4A8, +0x4AB => 0x4AA, +0x4AD => 0x4AC, +0x4AF => 0x4AE, +0x4B1 => 0x4B0, +0x4B3 => 0x4B2, +0x4B5 => 0x4B4, +0x4B7 => 0x4B6, +0x4B9 => 0x4B8, +0x4BB => 0x4BA, +0x4BD => 0x4BC, +0x4BF => 0x4BE, +0x4C2 => 0x4C1, +0x4C4 => 0x4C3, +0x4C6 => 0x4C5, +0x4C8 => 0x4C7, +0x4CA => 0x4C9, +0x4CC => 0x4CB, +0x4CE => 0x4CD, +0x4CF => 0x4C0, +0x4D1 => 0x4D0, +0x4D3 => 0x4D2, +0x4D5 => 0x4D4, +0x4D7 => 0x4D6, +0x4D9 => 0x4D8, +0x4DB => 0x4DA, +0x4DD => 0x4DC, +0x4DF => 0x4DE, +0x4E1 => 0x4E0, +0x4E3 => 0x4E2, +0x4E5 => 0x4E4, +0x4E7 => 0x4E6, +0x4E9 => 0x4E8, +0x4EB => 0x4EA, +0x4ED => 0x4EC, +0x4EF => 0x4EE, +0x4F1 => 0x4F0, +0x4F3 => 0x4F2, +0x4F5 => 0x4F4, +0x4F7 => 0x4F6, +0x4F9 => 0x4F8, +0x4FB => 0x4FA, +0x4FD => 0x4FC, +0x4FF => 0x4FE, +0x501 => 0x500, +0x503 => 0x502, +0x505 => 0x504, +0x507 => 0x506, +0x509 => 0x508, +0x50B => 0x50A, +0x50D => 0x50C, +0x50F => 0x50E, +0x511 => 0x510, +0x513 => 0x512, +0x515 => 0x514, +0x517 => 0x516, +0x519 => 0x518, +0x51B => 0x51A, +0x51D => 0x51C, +0x51F => 0x51E, +0x521 => 0x520, +0x523 => 0x522, +0x525 => 0x524, +0x527 => 0x526, +0x529 => 0x528, +0x52B => 0x52A, +0x52D => 0x52C, +0x52F => 0x52E, +0x561 => 0x531, +0x562 => 0x532, +0x563 => 0x533, +0x564 => 0x534, +0x565 => 0x535, +0x566 => 0x536, +0x567 => 0x537, +0x568 => 0x538, +0x569 => 0x539, +0x56A => 0x53A, +0x56B => 0x53B, +0x56C => 0x53C, +0x56D => 0x53D, +0x56E => 0x53E, +0x56F => 0x53F, +0x570 => 0x540, +0x571 => 0x541, +0x572 => 0x542, +0x573 => 0x543, +0x574 => 0x544, +0x575 => 0x545, +0x576 => 0x546, +0x577 => 0x547, +0x578 => 0x548, +0x579 => 0x549, +0x57A => 0x54A, +0x57B => 0x54B, +0x57C => 0x54C, +0x57D => 0x54D, +0x57E => 0x54E, +0x57F => 0x54F, +0x580 => 0x550, +0x581 => 0x551, +0x582 => 0x552, +0x583 => 0x553, +0x584 => 0x554, +0x585 => 0x555, +0x586 => 0x556, +0x10D0 => 0x1C90, +0x10D1 => 0x1C91, +0x10D2 => 0x1C92, +0x10D3 => 0x1C93, +0x10D4 => 0x1C94, +0x10D5 => 0x1C95, +0x10D6 => 0x1C96, +0x10D7 => 0x1C97, +0x10D8 => 0x1C98, +0x10D9 => 0x1C99, +0x10DA => 0x1C9A, +0x10DB => 0x1C9B, +0x10DC => 0x1C9C, +0x10DD => 0x1C9D, +0x10DE => 0x1C9E, +0x10DF => 0x1C9F, +0x10E0 => 0x1CA0, +0x10E1 => 0x1CA1, +0x10E2 => 0x1CA2, +0x10E3 => 0x1CA3, +0x10E4 => 0x1CA4, +0x10E5 => 0x1CA5, +0x10E6 => 0x1CA6, +0x10E7 => 0x1CA7, +0x10E8 => 0x1CA8, +0x10E9 => 0x1CA9, +0x10EA => 0x1CAA, +0x10EB => 0x1CAB, +0x10EC => 0x1CAC, +0x10ED => 0x1CAD, +0x10EE => 0x1CAE, +0x10EF => 0x1CAF, +0x10F0 => 0x1CB0, +0x10F1 => 0x1CB1, +0x10F2 => 0x1CB2, +0x10F3 => 0x1CB3, +0x10F4 => 0x1CB4, +0x10F5 => 0x1CB5, +0x10F6 => 0x1CB6, +0x10F7 => 0x1CB7, +0x10F8 => 0x1CB8, +0x10F9 => 0x1CB9, +0x10FA => 0x1CBA, +0x10FD => 0x1CBD, +0x10FE => 0x1CBE, +0x10FF => 0x1CBF, +0x13F8 => 0x13F0, +0x13F9 => 0x13F1, +0x13FA => 0x13F2, +0x13FB => 0x13F3, +0x13FC => 0x13F4, +0x13FD => 0x13F5, +0x1C80 => 0x412, +0x1C81 => 0x414, +0x1C82 => 0x41E, +0x1C83 => 0x421, +0x1C84 => 0x422, +0x1C85 => 0x422, +0x1C86 => 0x42A, +0x1C87 => 0x462, +0x1C88 => 0xA64A, +0x1D79 => 0xA77D, +0x1D7D => 0x2C63, +0x1D8E => 0xA7C6, +0x1E01 => 0x1E00, +0x1E03 => 0x1E02, +0x1E05 => 0x1E04, +0x1E07 => 0x1E06, +0x1E09 => 0x1E08, +0x1E0B => 0x1E0A, +0x1E0D => 0x1E0C, +0x1E0F => 0x1E0E, +0x1E11 => 0x1E10, +0x1E13 => 0x1E12, +0x1E15 => 0x1E14, +0x1E17 => 0x1E16, +0x1E19 => 0x1E18, +0x1E1B => 0x1E1A, +0x1E1D => 0x1E1C, +0x1E1F => 0x1E1E, +0x1E21 => 0x1E20, +0x1E23 => 0x1E22, +0x1E25 => 0x1E24, +0x1E27 => 0x1E26, +0x1E29 => 0x1E28, +0x1E2B => 0x1E2A, +0x1E2D => 0x1E2C, +0x1E2F => 0x1E2E, +0x1E31 => 0x1E30, +0x1E33 => 0x1E32, +0x1E35 => 0x1E34, +0x1E37 => 0x1E36, +0x1E39 => 0x1E38, +0x1E3B => 0x1E3A, +0x1E3D => 0x1E3C, +0x1E3F => 0x1E3E, +0x1E41 => 0x1E40, +0x1E43 => 0x1E42, +0x1E45 => 0x1E44, +0x1E47 => 0x1E46, +0x1E49 => 0x1E48, +0x1E4B => 0x1E4A, +0x1E4D => 0x1E4C, +0x1E4F => 0x1E4E, +0x1E51 => 0x1E50, +0x1E53 => 0x1E52, +0x1E55 => 0x1E54, +0x1E57 => 0x1E56, +0x1E59 => 0x1E58, +0x1E5B => 0x1E5A, +0x1E5D => 0x1E5C, +0x1E5F => 0x1E5E, +0x1E61 => 0x1E60, +0x1E63 => 0x1E62, +0x1E65 => 0x1E64, +0x1E67 => 0x1E66, +0x1E69 => 0x1E68, +0x1E6B => 0x1E6A, +0x1E6D => 0x1E6C, +0x1E6F => 0x1E6E, +0x1E71 => 0x1E70, +0x1E73 => 0x1E72, +0x1E75 => 0x1E74, +0x1E77 => 0x1E76, +0x1E79 => 0x1E78, +0x1E7B => 0x1E7A, +0x1E7D => 0x1E7C, +0x1E7F => 0x1E7E, +0x1E81 => 0x1E80, +0x1E83 => 0x1E82, +0x1E85 => 0x1E84, +0x1E87 => 0x1E86, +0x1E89 => 0x1E88, +0x1E8B => 0x1E8A, +0x1E8D => 0x1E8C, +0x1E8F => 0x1E8E, +0x1E91 => 0x1E90, +0x1E93 => 0x1E92, +0x1E95 => 0x1E94, +0x1E9B => 0x1E60, +0x1EA1 => 0x1EA0, +0x1EA3 => 0x1EA2, +0x1EA5 => 0x1EA4, +0x1EA7 => 0x1EA6, +0x1EA9 => 0x1EA8, +0x1EAB => 0x1EAA, +0x1EAD => 0x1EAC, +0x1EAF => 0x1EAE, +0x1EB1 => 0x1EB0, +0x1EB3 => 0x1EB2, +0x1EB5 => 0x1EB4, +0x1EB7 => 0x1EB6, +0x1EB9 => 0x1EB8, +0x1EBB => 0x1EBA, +0x1EBD => 0x1EBC, +0x1EBF => 0x1EBE, +0x1EC1 => 0x1EC0, +0x1EC3 => 0x1EC2, +0x1EC5 => 0x1EC4, +0x1EC7 => 0x1EC6, +0x1EC9 => 0x1EC8, +0x1ECB => 0x1ECA, +0x1ECD => 0x1ECC, +0x1ECF => 0x1ECE, +0x1ED1 => 0x1ED0, +0x1ED3 => 0x1ED2, +0x1ED5 => 0x1ED4, +0x1ED7 => 0x1ED6, +0x1ED9 => 0x1ED8, +0x1EDB => 0x1EDA, +0x1EDD => 0x1EDC, +0x1EDF => 0x1EDE, +0x1EE1 => 0x1EE0, +0x1EE3 => 0x1EE2, +0x1EE5 => 0x1EE4, +0x1EE7 => 0x1EE6, +0x1EE9 => 0x1EE8, +0x1EEB => 0x1EEA, +0x1EED => 0x1EEC, +0x1EEF => 0x1EEE, +0x1EF1 => 0x1EF0, +0x1EF3 => 0x1EF2, +0x1EF5 => 0x1EF4, +0x1EF7 => 0x1EF6, +0x1EF9 => 0x1EF8, +0x1EFB => 0x1EFA, +0x1EFD => 0x1EFC, +0x1EFF => 0x1EFE, +0x1F00 => 0x1F08, +0x1F01 => 0x1F09, +0x1F02 => 0x1F0A, +0x1F03 => 0x1F0B, +0x1F04 => 0x1F0C, +0x1F05 => 0x1F0D, +0x1F06 => 0x1F0E, +0x1F07 => 0x1F0F, +0x1F10 => 0x1F18, +0x1F11 => 0x1F19, +0x1F12 => 0x1F1A, +0x1F13 => 0x1F1B, +0x1F14 => 0x1F1C, +0x1F15 => 0x1F1D, +0x1F20 => 0x1F28, +0x1F21 => 0x1F29, +0x1F22 => 0x1F2A, +0x1F23 => 0x1F2B, +0x1F24 => 0x1F2C, +0x1F25 => 0x1F2D, +0x1F26 => 0x1F2E, +0x1F27 => 0x1F2F, +0x1F30 => 0x1F38, +0x1F31 => 0x1F39, +0x1F32 => 0x1F3A, +0x1F33 => 0x1F3B, +0x1F34 => 0x1F3C, +0x1F35 => 0x1F3D, +0x1F36 => 0x1F3E, +0x1F37 => 0x1F3F, +0x1F40 => 0x1F48, +0x1F41 => 0x1F49, +0x1F42 => 0x1F4A, +0x1F43 => 0x1F4B, +0x1F44 => 0x1F4C, +0x1F45 => 0x1F4D, +0x1F51 => 0x1F59, +0x1F53 => 0x1F5B, +0x1F55 => 0x1F5D, +0x1F57 => 0x1F5F, +0x1F60 => 0x1F68, +0x1F61 => 0x1F69, +0x1F62 => 0x1F6A, +0x1F63 => 0x1F6B, +0x1F64 => 0x1F6C, +0x1F65 => 0x1F6D, +0x1F66 => 0x1F6E, +0x1F67 => 0x1F6F, +0x1F70 => 0x1FBA, +0x1F71 => 0x1FBB, +0x1F72 => 0x1FC8, +0x1F73 => 0x1FC9, +0x1F74 => 0x1FCA, +0x1F75 => 0x1FCB, +0x1F76 => 0x1FDA, +0x1F77 => 0x1FDB, +0x1F78 => 0x1FF8, +0x1F79 => 0x1FF9, +0x1F7A => 0x1FEA, +0x1F7B => 0x1FEB, +0x1F7C => 0x1FFA, +0x1F7D => 0x1FFB, +0x1F80 => 0x1F88, +0x1F81 => 0x1F89, +0x1F82 => 0x1F8A, +0x1F83 => 0x1F8B, +0x1F84 => 0x1F8C, +0x1F85 => 0x1F8D, +0x1F86 => 0x1F8E, +0x1F87 => 0x1F8F, +0x1F90 => 0x1F98, +0x1F91 => 0x1F99, +0x1F92 => 0x1F9A, +0x1F93 => 0x1F9B, +0x1F94 => 0x1F9C, +0x1F95 => 0x1F9D, +0x1F96 => 0x1F9E, +0x1F97 => 0x1F9F, +0x1FA0 => 0x1FA8, +0x1FA1 => 0x1FA9, +0x1FA2 => 0x1FAA, +0x1FA3 => 0x1FAB, +0x1FA4 => 0x1FAC, +0x1FA5 => 0x1FAD, +0x1FA6 => 0x1FAE, +0x1FA7 => 0x1FAF, +0x1FB0 => 0x1FB8, +0x1FB1 => 0x1FB9, +0x1FB3 => 0x1FBC, +0x1FBE => 0x399, +0x1FC3 => 0x1FCC, +0x1FD0 => 0x1FD8, +0x1FD1 => 0x1FD9, +0x1FE0 => 0x1FE8, +0x1FE1 => 0x1FE9, +0x1FE5 => 0x1FEC, +0x1FF3 => 0x1FFC, +0x214E => 0x2132, +0x2170 => 0x2160, +0x2171 => 0x2161, +0x2172 => 0x2162, +0x2173 => 0x2163, +0x2174 => 0x2164, +0x2175 => 0x2165, +0x2176 => 0x2166, +0x2177 => 0x2167, +0x2178 => 0x2168, +0x2179 => 0x2169, +0x217A => 0x216A, +0x217B => 0x216B, +0x217C => 0x216C, +0x217D => 0x216D, +0x217E => 0x216E, +0x217F => 0x216F, +0x2184 => 0x2183, +0x24D0 => 0x24B6, +0x24D1 => 0x24B7, +0x24D2 => 0x24B8, +0x24D3 => 0x24B9, +0x24D4 => 0x24BA, +0x24D5 => 0x24BB, +0x24D6 => 0x24BC, +0x24D7 => 0x24BD, +0x24D8 => 0x24BE, +0x24D9 => 0x24BF, +0x24DA => 0x24C0, +0x24DB => 0x24C1, +0x24DC => 0x24C2, +0x24DD => 0x24C3, +0x24DE => 0x24C4, +0x24DF => 0x24C5, +0x24E0 => 0x24C6, +0x24E1 => 0x24C7, +0x24E2 => 0x24C8, +0x24E3 => 0x24C9, +0x24E4 => 0x24CA, +0x24E5 => 0x24CB, +0x24E6 => 0x24CC, +0x24E7 => 0x24CD, +0x24E8 => 0x24CE, +0x24E9 => 0x24CF, +0x2C30 => 0x2C00, +0x2C31 => 0x2C01, +0x2C32 => 0x2C02, +0x2C33 => 0x2C03, +0x2C34 => 0x2C04, +0x2C35 => 0x2C05, +0x2C36 => 0x2C06, +0x2C37 => 0x2C07, +0x2C38 => 0x2C08, +0x2C39 => 0x2C09, +0x2C3A => 0x2C0A, +0x2C3B => 0x2C0B, +0x2C3C => 0x2C0C, +0x2C3D => 0x2C0D, +0x2C3E => 0x2C0E, +0x2C3F => 0x2C0F, +0x2C40 => 0x2C10, +0x2C41 => 0x2C11, +0x2C42 => 0x2C12, +0x2C43 => 0x2C13, +0x2C44 => 0x2C14, +0x2C45 => 0x2C15, +0x2C46 => 0x2C16, +0x2C47 => 0x2C17, +0x2C48 => 0x2C18, +0x2C49 => 0x2C19, +0x2C4A => 0x2C1A, +0x2C4B => 0x2C1B, +0x2C4C => 0x2C1C, +0x2C4D => 0x2C1D, +0x2C4E => 0x2C1E, +0x2C4F => 0x2C1F, +0x2C50 => 0x2C20, +0x2C51 => 0x2C21, +0x2C52 => 0x2C22, +0x2C53 => 0x2C23, +0x2C54 => 0x2C24, +0x2C55 => 0x2C25, +0x2C56 => 0x2C26, +0x2C57 => 0x2C27, +0x2C58 => 0x2C28, +0x2C59 => 0x2C29, +0x2C5A => 0x2C2A, +0x2C5B => 0x2C2B, +0x2C5C => 0x2C2C, +0x2C5D => 0x2C2D, +0x2C5E => 0x2C2E, +0x2C61 => 0x2C60, +0x2C65 => 0x23A, +0x2C66 => 0x23E, +0x2C68 => 0x2C67, +0x2C6A => 0x2C69, +0x2C6C => 0x2C6B, +0x2C73 => 0x2C72, +0x2C76 => 0x2C75, +0x2C81 => 0x2C80, +0x2C83 => 0x2C82, +0x2C85 => 0x2C84, +0x2C87 => 0x2C86, +0x2C89 => 0x2C88, +0x2C8B => 0x2C8A, +0x2C8D => 0x2C8C, +0x2C8F => 0x2C8E, +0x2C91 => 0x2C90, +0x2C93 => 0x2C92, +0x2C95 => 0x2C94, +0x2C97 => 0x2C96, +0x2C99 => 0x2C98, +0x2C9B => 0x2C9A, +0x2C9D => 0x2C9C, +0x2C9F => 0x2C9E, +0x2CA1 => 0x2CA0, +0x2CA3 => 0x2CA2, +0x2CA5 => 0x2CA4, +0x2CA7 => 0x2CA6, +0x2CA9 => 0x2CA8, +0x2CAB => 0x2CAA, +0x2CAD => 0x2CAC, +0x2CAF => 0x2CAE, +0x2CB1 => 0x2CB0, +0x2CB3 => 0x2CB2, +0x2CB5 => 0x2CB4, +0x2CB7 => 0x2CB6, +0x2CB9 => 0x2CB8, +0x2CBB => 0x2CBA, +0x2CBD => 0x2CBC, +0x2CBF => 0x2CBE, +0x2CC1 => 0x2CC0, +0x2CC3 => 0x2CC2, +0x2CC5 => 0x2CC4, +0x2CC7 => 0x2CC6, +0x2CC9 => 0x2CC8, +0x2CCB => 0x2CCA, +0x2CCD => 0x2CCC, +0x2CCF => 0x2CCE, +0x2CD1 => 0x2CD0, +0x2CD3 => 0x2CD2, +0x2CD5 => 0x2CD4, +0x2CD7 => 0x2CD6, +0x2CD9 => 0x2CD8, +0x2CDB => 0x2CDA, +0x2CDD => 0x2CDC, +0x2CDF => 0x2CDE, +0x2CE1 => 0x2CE0, +0x2CE3 => 0x2CE2, +0x2CEC => 0x2CEB, +0x2CEE => 0x2CED, +0x2CF3 => 0x2CF2, +0x2D00 => 0x10A0, +0x2D01 => 0x10A1, +0x2D02 => 0x10A2, +0x2D03 => 0x10A3, +0x2D04 => 0x10A4, +0x2D05 => 0x10A5, +0x2D06 => 0x10A6, +0x2D07 => 0x10A7, +0x2D08 => 0x10A8, +0x2D09 => 0x10A9, +0x2D0A => 0x10AA, +0x2D0B => 0x10AB, +0x2D0C => 0x10AC, +0x2D0D => 0x10AD, +0x2D0E => 0x10AE, +0x2D0F => 0x10AF, +0x2D10 => 0x10B0, +0x2D11 => 0x10B1, +0x2D12 => 0x10B2, +0x2D13 => 0x10B3, +0x2D14 => 0x10B4, +0x2D15 => 0x10B5, +0x2D16 => 0x10B6, +0x2D17 => 0x10B7, +0x2D18 => 0x10B8, +0x2D19 => 0x10B9, +0x2D1A => 0x10BA, +0x2D1B => 0x10BB, +0x2D1C => 0x10BC, +0x2D1D => 0x10BD, +0x2D1E => 0x10BE, +0x2D1F => 0x10BF, +0x2D20 => 0x10C0, +0x2D21 => 0x10C1, +0x2D22 => 0x10C2, +0x2D23 => 0x10C3, +0x2D24 => 0x10C4, +0x2D25 => 0x10C5, +0x2D27 => 0x10C7, +0x2D2D => 0x10CD, +0xA641 => 0xA640, +0xA643 => 0xA642, +0xA645 => 0xA644, +0xA647 => 0xA646, +0xA649 => 0xA648, +0xA64B => 0xA64A, +0xA64D => 0xA64C, +0xA64F => 0xA64E, +0xA651 => 0xA650, +0xA653 => 0xA652, +0xA655 => 0xA654, +0xA657 => 0xA656, +0xA659 => 0xA658, +0xA65B => 0xA65A, +0xA65D => 0xA65C, +0xA65F => 0xA65E, +0xA661 => 0xA660, +0xA663 => 0xA662, +0xA665 => 0xA664, +0xA667 => 0xA666, +0xA669 => 0xA668, +0xA66B => 0xA66A, +0xA66D => 0xA66C, +0xA681 => 0xA680, +0xA683 => 0xA682, +0xA685 => 0xA684, +0xA687 => 0xA686, +0xA689 => 0xA688, +0xA68B => 0xA68A, +0xA68D => 0xA68C, +0xA68F => 0xA68E, +0xA691 => 0xA690, +0xA693 => 0xA692, +0xA695 => 0xA694, +0xA697 => 0xA696, +0xA699 => 0xA698, +0xA69B => 0xA69A, +0xA723 => 0xA722, +0xA725 => 0xA724, +0xA727 => 0xA726, +0xA729 => 0xA728, +0xA72B => 0xA72A, +0xA72D => 0xA72C, +0xA72F => 0xA72E, +0xA733 => 0xA732, +0xA735 => 0xA734, +0xA737 => 0xA736, +0xA739 => 0xA738, +0xA73B => 0xA73A, +0xA73D => 0xA73C, +0xA73F => 0xA73E, +0xA741 => 0xA740, +0xA743 => 0xA742, +0xA745 => 0xA744, +0xA747 => 0xA746, +0xA749 => 0xA748, +0xA74B => 0xA74A, +0xA74D => 0xA74C, +0xA74F => 0xA74E, +0xA751 => 0xA750, +0xA753 => 0xA752, +0xA755 => 0xA754, +0xA757 => 0xA756, +0xA759 => 0xA758, +0xA75B => 0xA75A, +0xA75D => 0xA75C, +0xA75F => 0xA75E, +0xA761 => 0xA760, +0xA763 => 0xA762, +0xA765 => 0xA764, +0xA767 => 0xA766, +0xA769 => 0xA768, +0xA76B => 0xA76A, +0xA76D => 0xA76C, +0xA76F => 0xA76E, +0xA77A => 0xA779, +0xA77C => 0xA77B, +0xA77F => 0xA77E, +0xA781 => 0xA780, +0xA783 => 0xA782, +0xA785 => 0xA784, +0xA787 => 0xA786, +0xA78C => 0xA78B, +0xA791 => 0xA790, +0xA793 => 0xA792, +0xA794 => 0xA7C4, +0xA797 => 0xA796, +0xA799 => 0xA798, +0xA79B => 0xA79A, +0xA79D => 0xA79C, +0xA79F => 0xA79E, +0xA7A1 => 0xA7A0, +0xA7A3 => 0xA7A2, +0xA7A5 => 0xA7A4, +0xA7A7 => 0xA7A6, +0xA7A9 => 0xA7A8, +0xA7B5 => 0xA7B4, +0xA7B7 => 0xA7B6, +0xA7B9 => 0xA7B8, +0xA7BB => 0xA7BA, +0xA7BD => 0xA7BC, +0xA7BF => 0xA7BE, +0xA7C3 => 0xA7C2, +0xA7C8 => 0xA7C7, +0xA7CA => 0xA7C9, +0xA7F6 => 0xA7F5, +0xAB53 => 0xA7B3, +0xAB70 => 0x13A0, +0xAB71 => 0x13A1, +0xAB72 => 0x13A2, +0xAB73 => 0x13A3, +0xAB74 => 0x13A4, +0xAB75 => 0x13A5, +0xAB76 => 0x13A6, +0xAB77 => 0x13A7, +0xAB78 => 0x13A8, +0xAB79 => 0x13A9, +0xAB7A => 0x13AA, +0xAB7B => 0x13AB, +0xAB7C => 0x13AC, +0xAB7D => 0x13AD, +0xAB7E => 0x13AE, +0xAB7F => 0x13AF, +0xAB80 => 0x13B0, +0xAB81 => 0x13B1, +0xAB82 => 0x13B2, +0xAB83 => 0x13B3, +0xAB84 => 0x13B4, +0xAB85 => 0x13B5, +0xAB86 => 0x13B6, +0xAB87 => 0x13B7, +0xAB88 => 0x13B8, +0xAB89 => 0x13B9, +0xAB8A => 0x13BA, +0xAB8B => 0x13BB, +0xAB8C => 0x13BC, +0xAB8D => 0x13BD, +0xAB8E => 0x13BE, +0xAB8F => 0x13BF, +0xAB90 => 0x13C0, +0xAB91 => 0x13C1, +0xAB92 => 0x13C2, +0xAB93 => 0x13C3, +0xAB94 => 0x13C4, +0xAB95 => 0x13C5, +0xAB96 => 0x13C6, +0xAB97 => 0x13C7, +0xAB98 => 0x13C8, +0xAB99 => 0x13C9, +0xAB9A => 0x13CA, +0xAB9B => 0x13CB, +0xAB9C => 0x13CC, +0xAB9D => 0x13CD, +0xAB9E => 0x13CE, +0xAB9F => 0x13CF, +0xABA0 => 0x13D0, +0xABA1 => 0x13D1, +0xABA2 => 0x13D2, +0xABA3 => 0x13D3, +0xABA4 => 0x13D4, +0xABA5 => 0x13D5, +0xABA6 => 0x13D6, +0xABA7 => 0x13D7, +0xABA8 => 0x13D8, +0xABA9 => 0x13D9, +0xABAA => 0x13DA, +0xABAB => 0x13DB, +0xABAC => 0x13DC, +0xABAD => 0x13DD, +0xABAE => 0x13DE, +0xABAF => 0x13DF, +0xABB0 => 0x13E0, +0xABB1 => 0x13E1, +0xABB2 => 0x13E2, +0xABB3 => 0x13E3, +0xABB4 => 0x13E4, +0xABB5 => 0x13E5, +0xABB6 => 0x13E6, +0xABB7 => 0x13E7, +0xABB8 => 0x13E8, +0xABB9 => 0x13E9, +0xABBA => 0x13EA, +0xABBB => 0x13EB, +0xABBC => 0x13EC, +0xABBD => 0x13ED, +0xABBE => 0x13EE, +0xABBF => 0x13EF, +0xFF41 => 0xFF21, +0xFF42 => 0xFF22, +0xFF43 => 0xFF23, +0xFF44 => 0xFF24, +0xFF45 => 0xFF25, +0xFF46 => 0xFF26, +0xFF47 => 0xFF27, +0xFF48 => 0xFF28, +0xFF49 => 0xFF29, +0xFF4A => 0xFF2A, +0xFF4B => 0xFF2B, +0xFF4C => 0xFF2C, +0xFF4D => 0xFF2D, +0xFF4E => 0xFF2E, +0xFF4F => 0xFF2F, +0xFF50 => 0xFF30, +0xFF51 => 0xFF31, +0xFF52 => 0xFF32, +0xFF53 => 0xFF33, +0xFF54 => 0xFF34, +0xFF55 => 0xFF35, +0xFF56 => 0xFF36, +0xFF57 => 0xFF37, +0xFF58 => 0xFF38, +0xFF59 => 0xFF39, +0xFF5A => 0xFF3A, +0x10428 => 0x10400, +0x10429 => 0x10401, +0x1042A => 0x10402, +0x1042B => 0x10403, +0x1042C => 0x10404, +0x1042D => 0x10405, +0x1042E => 0x10406, +0x1042F => 0x10407, +0x10430 => 0x10408, +0x10431 => 0x10409, +0x10432 => 0x1040A, +0x10433 => 0x1040B, +0x10434 => 0x1040C, +0x10435 => 0x1040D, +0x10436 => 0x1040E, +0x10437 => 0x1040F, +0x10438 => 0x10410, +0x10439 => 0x10411, +0x1043A => 0x10412, +0x1043B => 0x10413, +0x1043C => 0x10414, +0x1043D => 0x10415, +0x1043E => 0x10416, +0x1043F => 0x10417, +0x10440 => 0x10418, +0x10441 => 0x10419, +0x10442 => 0x1041A, +0x10443 => 0x1041B, +0x10444 => 0x1041C, +0x10445 => 0x1041D, +0x10446 => 0x1041E, +0x10447 => 0x1041F, +0x10448 => 0x10420, +0x10449 => 0x10421, +0x1044A => 0x10422, +0x1044B => 0x10423, +0x1044C => 0x10424, +0x1044D => 0x10425, +0x1044E => 0x10426, +0x1044F => 0x10427, +0x104D8 => 0x104B0, +0x104D9 => 0x104B1, +0x104DA => 0x104B2, +0x104DB => 0x104B3, +0x104DC => 0x104B4, +0x104DD => 0x104B5, +0x104DE => 0x104B6, +0x104DF => 0x104B7, +0x104E0 => 0x104B8, +0x104E1 => 0x104B9, +0x104E2 => 0x104BA, +0x104E3 => 0x104BB, +0x104E4 => 0x104BC, +0x104E5 => 0x104BD, +0x104E6 => 0x104BE, +0x104E7 => 0x104BF, +0x104E8 => 0x104C0, +0x104E9 => 0x104C1, +0x104EA => 0x104C2, +0x104EB => 0x104C3, +0x104EC => 0x104C4, +0x104ED => 0x104C5, +0x104EE => 0x104C6, +0x104EF => 0x104C7, +0x104F0 => 0x104C8, +0x104F1 => 0x104C9, +0x104F2 => 0x104CA, +0x104F3 => 0x104CB, +0x104F4 => 0x104CC, +0x104F5 => 0x104CD, +0x104F6 => 0x104CE, +0x104F7 => 0x104CF, +0x104F8 => 0x104D0, +0x104F9 => 0x104D1, +0x104FA => 0x104D2, +0x104FB => 0x104D3, +0x10CC0 => 0x10C80, +0x10CC1 => 0x10C81, +0x10CC2 => 0x10C82, +0x10CC3 => 0x10C83, +0x10CC4 => 0x10C84, +0x10CC5 => 0x10C85, +0x10CC6 => 0x10C86, +0x10CC7 => 0x10C87, +0x10CC8 => 0x10C88, +0x10CC9 => 0x10C89, +0x10CCA => 0x10C8A, +0x10CCB => 0x10C8B, +0x10CCC => 0x10C8C, +0x10CCD => 0x10C8D, +0x10CCE => 0x10C8E, +0x10CCF => 0x10C8F, +0x10CD0 => 0x10C90, +0x10CD1 => 0x10C91, +0x10CD2 => 0x10C92, +0x10CD3 => 0x10C93, +0x10CD4 => 0x10C94, +0x10CD5 => 0x10C95, +0x10CD6 => 0x10C96, +0x10CD7 => 0x10C97, +0x10CD8 => 0x10C98, +0x10CD9 => 0x10C99, +0x10CDA => 0x10C9A, +0x10CDB => 0x10C9B, +0x10CDC => 0x10C9C, +0x10CDD => 0x10C9D, +0x10CDE => 0x10C9E, +0x10CDF => 0x10C9F, +0x10CE0 => 0x10CA0, +0x10CE1 => 0x10CA1, +0x10CE2 => 0x10CA2, +0x10CE3 => 0x10CA3, +0x10CE4 => 0x10CA4, +0x10CE5 => 0x10CA5, +0x10CE6 => 0x10CA6, +0x10CE7 => 0x10CA7, +0x10CE8 => 0x10CA8, +0x10CE9 => 0x10CA9, +0x10CEA => 0x10CAA, +0x10CEB => 0x10CAB, +0x10CEC => 0x10CAC, +0x10CED => 0x10CAD, +0x10CEE => 0x10CAE, +0x10CEF => 0x10CAF, +0x10CF0 => 0x10CB0, +0x10CF1 => 0x10CB1, +0x10CF2 => 0x10CB2, +0x118C0 => 0x118A0, +0x118C1 => 0x118A1, +0x118C2 => 0x118A2, +0x118C3 => 0x118A3, +0x118C4 => 0x118A4, +0x118C5 => 0x118A5, +0x118C6 => 0x118A6, +0x118C7 => 0x118A7, +0x118C8 => 0x118A8, +0x118C9 => 0x118A9, +0x118CA => 0x118AA, +0x118CB => 0x118AB, +0x118CC => 0x118AC, +0x118CD => 0x118AD, +0x118CE => 0x118AE, +0x118CF => 0x118AF, +0x118D0 => 0x118B0, +0x118D1 => 0x118B1, +0x118D2 => 0x118B2, +0x118D3 => 0x118B3, +0x118D4 => 0x118B4, +0x118D5 => 0x118B5, +0x118D6 => 0x118B6, +0x118D7 => 0x118B7, +0x118D8 => 0x118B8, +0x118D9 => 0x118B9, +0x118DA => 0x118BA, +0x118DB => 0x118BB, +0x118DC => 0x118BC, +0x118DD => 0x118BD, +0x118DE => 0x118BE, +0x118DF => 0x118BF, +0x16E60 => 0x16E40, +0x16E61 => 0x16E41, +0x16E62 => 0x16E42, +0x16E63 => 0x16E43, +0x16E64 => 0x16E44, +0x16E65 => 0x16E45, +0x16E66 => 0x16E46, +0x16E67 => 0x16E47, +0x16E68 => 0x16E48, +0x16E69 => 0x16E49, +0x16E6A => 0x16E4A, +0x16E6B => 0x16E4B, +0x16E6C => 0x16E4C, +0x16E6D => 0x16E4D, +0x16E6E => 0x16E4E, +0x16E6F => 0x16E4F, +0x16E70 => 0x16E50, +0x16E71 => 0x16E51, +0x16E72 => 0x16E52, +0x16E73 => 0x16E53, +0x16E74 => 0x16E54, +0x16E75 => 0x16E55, +0x16E76 => 0x16E56, +0x16E77 => 0x16E57, +0x16E78 => 0x16E58, +0x16E79 => 0x16E59, +0x16E7A => 0x16E5A, +0x16E7B => 0x16E5B, +0x16E7C => 0x16E5C, +0x16E7D => 0x16E5D, +0x16E7E => 0x16E5E, +0x16E7F => 0x16E5F, +0x1E922 => 0x1E900, +0x1E923 => 0x1E901, +0x1E924 => 0x1E902, +0x1E925 => 0x1E903, +0x1E926 => 0x1E904, +0x1E927 => 0x1E905, +0x1E928 => 0x1E906, +0x1E929 => 0x1E907, +0x1E92A => 0x1E908, +0x1E92B => 0x1E909, +0x1E92C => 0x1E90A, +0x1E92D => 0x1E90B, +0x1E92E => 0x1E90C, +0x1E92F => 0x1E90D, +0x1E930 => 0x1E90E, +0x1E931 => 0x1E90F, +0x1E932 => 0x1E910, +0x1E933 => 0x1E911, +0x1E934 => 0x1E912, +0x1E935 => 0x1E913, +0x1E936 => 0x1E914, +0x1E937 => 0x1E915, +0x1E938 => 0x1E916, +0x1E939 => 0x1E917, +0x1E93A => 0x1E918, +0x1E93B => 0x1E919, +0x1E93C => 0x1E91A, +0x1E93D => 0x1E91B, +0x1E93E => 0x1E91C, +0x1E93F => 0x1E91D, +0x1E940 => 0x1E91E, +0x1E941 => 0x1E91F, +0x1E942 => 0x1E920, +0x1E943 => 0x1E921, +]; diff --git a/admin/vendor/opis/string/src/Exception/InvalidCodePointException.php b/admin/vendor/opis/string/src/Exception/InvalidCodePointException.php new file mode 100644 index 0000000..9494adc --- /dev/null +++ b/admin/vendor/opis/string/src/Exception/InvalidCodePointException.php @@ -0,0 +1,46 @@ +codePoint = $codePoint; + } + + /** + * @return mixed + */ + public function codePoint() + { + return$this->codePoint; + } +} diff --git a/admin/vendor/opis/string/src/Exception/InvalidStringException.php b/admin/vendor/opis/string/src/Exception/InvalidStringException.php new file mode 100644 index 0000000..50b22a1 --- /dev/null +++ b/admin/vendor/opis/string/src/Exception/InvalidStringException.php @@ -0,0 +1,61 @@ +string = $string; + $this->offset = $offset; + } + + /** + * @return string + */ + public function string(): string + { + return $this->string; + } + + /** + * @return int + */ + public function offset(): int + { + return $this->offset; + } +} diff --git a/admin/vendor/opis/string/src/Exception/UnicodeException.php b/admin/vendor/opis/string/src/Exception/UnicodeException.php new file mode 100644 index 0000000..22e6d2f --- /dev/null +++ b/admin/vendor/opis/string/src/Exception/UnicodeException.php @@ -0,0 +1,25 @@ +codes = $codes; + $this->length = count($codes); + } + + /** + * @return int[] + */ + public function codePoints(): array + { + return $this->codes; + } + + /** + * @return string[] + */ + public function chars(): array + { + if ($this->chars === null) { + $this->chars = self::getCharsFromCodePoints($this->codes); + } + return $this->chars; + } + + /** + * @return int + */ + public function length(): int + { + return $this->length; + } + + /** + * @return bool + */ + public function isEmpty(): bool + { + return $this->length === 0; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function equals($text, bool $ignoreCase = false): bool + { + return $this->compareTo($text, $ignoreCase) === 0; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return int + */ + public function compareTo($text, bool $ignoreCase = false): int + { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints($text, $mode); + + $length = count($text); + + if ($length !== $this->length) { + return $this->length <=> $length; + } + + return $this->getMappedCodes($mode) <=> $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function contains($text, bool $ignoreCase = false): bool + { + return $this->indexOf($text, 0, $ignoreCase) !== -1; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function startsWith($text, bool $ignoreCase = false): bool + { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints($text, $mode); + + $len = count($text); + + if ($len === 0 || $len > $this->length) { + return false; + } + + return array_slice($this->getMappedCodes($mode), 0, $len) === $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function endsWith($text, bool $ignoreCase = false): bool + { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints($text, $mode); + + if (empty($text)) { + return false; + } + + $codes = $this->getMappedCodes($mode); + + $offset = $this->length - count($text); + + if ($offset < 0) { + return false; + } + + return array_slice($codes, $offset) === $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param bool $ignoreCase + * @return int + */ + public function indexOf($text, int $offset = 0, bool $ignoreCase = false): int + { + if ($offset < 0) { + $offset += $this->length; + } + if ($offset < 0 || $offset >= $this->length) { + return -1; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints($text, $mode); + + $len = count($text); + + if ($len === 0 || $offset + $len > $this->length) { + return -1; + } + + return $this->doIndexOf($this->getMappedCodes($mode), $text, $offset); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param bool $ignoreCase + * @return int + */ + public function lastIndexOf($text, int $offset = 0, bool $ignoreCase = false): int + { + if ($offset < 0) { + $start = $this->length + $offset; + if ($start < 0) { + return -1; + } + $last = 0; + } else { + if ($offset >= $this->length) { + return -1; + } + $start = $this->length - 1; + $last = $offset; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints($text, $mode); + + $len = count($text); + + if ($len === 0) { + return -1; + } + + if ($offset < 0) { + if ($len > $this->length) { + return -1; + } + $start = min($start, $this->length - $len); + } elseif ($offset + $len > $this->length) { + return -1; + } + + $codes = $this->getMappedCodes($mode); + + for ($i = $start; $i >= $last; $i--) { + $match = true; + + for ($j = 0; $j < $len; $j++) { + if ($codes[$i + $j] !== $text[$j]) { + $match = false; + break; + } + } + + if ($match) { + return $i; + } + } + + return -1; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @param bool $allowPrefixOnly If true the result can contain only the prefix + * @return $this + */ + public function ensurePrefix($text, bool $ignoreCase = false, bool $allowPrefixOnly = true): self + { + $text = self::resolveCodePoints($text); + + $len = count($text); + + if ($len === 0) { + return clone $this; + } + + if ($this->length === 0) { + return new static($text); + } + + if ($ignoreCase) { + $prefix = self::getMappedCodePoints($text, self::FOLD_CASE); + } else { + $prefix = &$text; + } + + if ($this->length === $len) { + $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); + if ($allowPrefixOnly && $part === $prefix) { + return clone $this; + } + // Remove last element to avoid double check + array_pop($part); + } elseif ($this->length < $len) { + $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); + // Checks if this can be a suffix + if ($allowPrefixOnly && (array_slice($prefix, 0, $this->length) === $part)) { + $text = array_slice($text, $this->length); + return new static(array_merge($this->codes, $text)); + } + } else { + $part = array_slice($this->codes, 0, $len); + if ($ignoreCase) { + $part = self::getMappedCodePoints($part, self::FOLD_CASE); + } + if ($part === $prefix) { + return clone $this; + } + // Remove last element to avoid double check + array_pop($part); + } + + $copy = $len; + + $part_len = count($part); + + while ($part_len) { + if ($part === array_slice($prefix, -$part_len)) { + $copy = $len - $part_len; + break; + } + array_pop($part); + $part_len--; + } + + if ($copy === 0) { + return clone $this; + } + + if ($copy < $len) { + $text = array_slice($text, 0, $copy); + } + + return new static(array_merge($text, $this->codes)); + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @param bool $allowSuffixOnly If true the result can contain only the suffix + * @return static + */ + public function ensureSuffix($text, bool $ignoreCase = false, bool $allowSuffixOnly = true): self + { + $text = self::resolveCodePoints($text); + + $len = count($text); + + if ($len === 0) { + return clone $this; + } + + if ($this->length === 0) { + return new static($text); + } + + if ($ignoreCase) { + $suffix = self::getMappedCodePoints($text, self::FOLD_CASE); + } else { + $suffix = &$text; + } + + if ($this->length === $len) { + $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); + if ($allowSuffixOnly && $part === $suffix) { + return clone $this; + } + // Remove first element to avoid double check + array_shift($part); + } elseif ($this->length < $len) { + $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); + // Checks if this can be a prefix + if ($allowSuffixOnly && (array_slice($suffix, -$this->length) === $part)) { + $text = array_slice($text, 0, $len - $this->length); + return new static(array_merge($text, $this->codes)); + } + } else { + $part = array_slice($this->codes, -$len); + if ($ignoreCase) { + $part = self::getMappedCodePoints($part, self::FOLD_CASE); + } + if ($part === $suffix) { + return clone $this; + } + // Remove first element to avoid double check + array_shift($part); + } + + $skip = 0; + + $part_len = count($part); + + while ($part_len) { + if ($part === array_slice($suffix, 0, $part_len)) { + $skip = $part_len; + break; + } + array_shift($part); + $part_len--; + } + + if ($skip === $len) { + return clone $this; + } + + if ($skip) { + array_splice($text, 0, $skip); + } + + return new static(array_merge($this->codes, $text)); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return static + */ + public function append($text, int $mode = self::KEEP_CASE): self + { + return new static(array_merge($this->codes, self::resolveCodePoints($text, $mode))); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return static + */ + public function prepend($text, int $mode = self::KEEP_CASE): self + { + return new static(array_merge(self::resolveCodePoints($text, $mode), $this->codes)); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param int $mode + * @return static + */ + public function insert($text, int $offset, int $mode = self::KEEP_CASE): self + { + $codes = $this->codes; + + array_splice($codes, $offset, 0, self::resolveCodePoints($text, $mode)); + + return new static($codes); + } + + /** + * @param int $offset + * @param int|null $length + * @return static + */ + public function remove(int $offset, ?int $length = null): self + { + $codes = $this->codes; + + if ($length === null) { + array_splice($codes, $offset); + } else { + array_splice($codes, $offset, $length); + } + + return new static($codes); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trim($mask = " \t\n\r\0\x0B"): self + { + return $this->doTrim($mask, true, true); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trimLeft($mask = " \t\n\r\0\x0B"): self + { + return $this->doTrim($mask, true, false); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trimRight($mask = " \t\n\r\0\x0B"): self + { + return $this->doTrim($mask, false, true); + } + + /** + * @return static + */ + public function reverse(): self + { + return new static(array_reverse($this->codes)); + } + + /** + * @param int $times + * @return static + */ + public function repeat(int $times = 1): self + { + if ($times <= 1) { + return clone $this; + } + + $codes = []; + + while ($times--) { + $codes = array_merge($codes, $this->codes); + } + + return new static($codes); + } + + /** + * @param string|self|int[]|string[] $subject + * @param string|self|int[]|string[] $replace + * @param int $offset + * @param bool $ignoreCase + * @return static + */ + public function replace($subject, $replace, int $offset = 0, bool $ignoreCase = false): self + { + if ($offset < 0) { + $offset += $this->length; + } + if ($offset < 0 || $offset >= $this->length) { + return clone $this; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $subject = self::resolveCodePoints($subject, $mode); + + $len = count($subject); + + if ($len === 0 || $offset + $len > $this->length) { + return clone $this; + } + + $offset = $this->doIndexOf($this->getMappedCodes($mode), $subject, $offset); + + if ($offset === -1) { + return clone $this; + } + + $codes = $this->codes; + + array_splice($codes, $offset, count($subject), self::resolveCodePoints($replace)); + + return new static($codes); + } + + /** + * @param string|self|int[]|string[] $subject + * @param string|self|int[]|string[] $replace + * @param bool $ignoreCase + * @param int $offset + * @return static + */ + public function replaceAll($subject, $replace, int $offset = 0, bool $ignoreCase = false): self + { + if ($offset < 0) { + $offset += $this->length; + } + if ($offset < 0 || $offset >= $this->length) { + return clone $this; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $subject = self::resolveCodePoints($subject, $mode); + + $len = count($subject); + + if ($len === 0 || $offset + $len > $this->length) { + return clone $this; + } + + $replace = self::resolveCodePoints($replace); + + $codes = $this->getMappedCodes($mode); + + $copy = $this->codes; + + $fix = count($replace) - $len; + + $t = 0; + + while (($pos = $this->doIndexOf($codes, $subject, $offset)) >= 0) { + array_splice($copy, $pos + $t * $fix, $len, $replace); + $offset = $pos + $len; + $t++; + } + + return new static($copy); + } + + /** + * @param string|self|int[]|string[] $delimiter + * @param bool $ignoreCase + * @return array + */ + public function split($delimiter = '', bool $ignoreCase = false): array + { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + $delimiter = self::resolveCodePoints($delimiter, $mode); + $len = count($delimiter); + + $ret = []; + + if ($len === 0) { + foreach ($this->codes as $code) { + $ret[] = new static([$code]); + } + } else { + $codes = $this->getMappedCodes($mode); + + $offset = 0; + + while (($pos = $this->doIndexOf($codes, $delimiter, $offset)) >= 0) { + $ret[] = new static(array_slice($this->codes, $offset, $pos - $offset)); + $offset = $pos + $len; + } + + $ret[] = new static(array_slice($this->codes, $offset)); + } + + return $ret; + } + + /** + * @param int $start + * @param int|null $length + * @return static + */ + public function substring(int $start, ?int $length = null): self + { + return new static(array_slice($this->codes, $start, $length)); + } + + /** + * @param int $size If negative then pad left otherwise pad right + * @param self|string|int $char A char or a code point + * @return static + */ + public function pad(int $size, $char = 0x20): self + { + return new static(array_pad($this->codes, $size, self::resolveFirstCodePoint($char, 0x20))); + } + + /** + * @param int $size + * @param self|string|int $char + * @return static + */ + public function padLeft(int $size, $char = 0x20): self + { + if ($size > 0) { + $size = -$size; + } + + return $this->pad($size, $char); + } + + /** + * @param int $size + * @param self|string|int $char + * @return static + */ + public function padRight(int $size, $char = 0x20): self + { + if ($size < 0) { + $size = -$size; + } + + return $this->pad($size, $char); + } + + /** + * @return bool + */ + public function isLowerCase(): bool + { + return $this->isCase(self::LOWER_CASE); + } + + /** + * @return bool + */ + public function isUpperCase(): bool + { + return $this->isCase(self::UPPER_CASE); + } + + /** + * @return bool + */ + public function isAscii(): bool + { + $key = 'i' . self::ASCII_CONV; + + if (!isset($this->cache[$key])) { + $ok = true; + + foreach ($this->codes as $code) { + if ($code >= 0x80) { + $ok = false; + break; + } + } + + $this->cache[$key] = $ok; + } + + return $this->cache[$key]; + } + + /** + * Convert all chars to lower case (where possible) + * @return static + */ + public function toLower(): self + { + if ($this->cache['i' . self::LOWER_CASE] ?? false) { + return clone $this; + } + return new static($this->getMappedCodes(self::LOWER_CASE)); + } + + /** + * Convert all chars to upper case (where possible) + * @return static + */ + public function toUpper(): self + { + if ($this->cache['i' . self::UPPER_CASE] ?? false) { + return clone $this; + } + return new static($this->getMappedCodes(self::UPPER_CASE)); + } + + /** + * Converts all chars to their ASCII equivalent (if any) + * @return static + */ + public function toAscii(): self + { + if ($this->cache['i' . self::ASCII_CONV] ?? false) { + return clone $this; + } + return new static($this->getMappedCodes(self::ASCII_CONV)); + } + + /** + * @param int $index + * @return string + */ + public function charAt(int $index): string + { + // Allow negative index + if ($index < 0 && $index + $this->length >= 0) { + $index += $this->length; + } + + if ($index < 0 || $index >= $this->length) { + return ''; + } + + return $this->chars()[$index]; + } + + /** + * @param int $index + * @return int + */ + public function codePointAt(int $index): int + { + // Allow negative index + if ($index < 0 && $index + $this->length >= 0) { + $index += $this->length; + } + + if ($index < 0 || $index >= $this->length) { + return -1; + } + + return $this->codes[$index]; + } + + /** + * @param int $offset + * @return int + */ + public function __invoke(int $offset): int + { + if ($offset < 0) { + if ($offset + $this->length < 0) { + throw new OutOfBoundsException("Undefined offset: {$offset}"); + } + $offset += $this->length; + } elseif ($offset >= $this->length) { + throw new OutOfBoundsException("Undefined offset: {$offset}"); + } + + return $this->codes[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetExists($offset): bool + { + // Allow negative index + if ($offset < 0) { + $offset += $this->length; + } + + return isset($this->codes[$offset]); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset): string + { + if ($offset < 0) { + if ($offset + $this->length < 0) { + throw new OutOfBoundsException("Undefined offset: {$offset}"); + } + $offset += $this->length; + } elseif ($offset >= $this->length) { + throw new OutOfBoundsException("Undefined offset: {$offset}"); + } + + return $this->chars()[$offset]; + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + // Allow negative index + if ($offset < 0) { + $offset += $this->length; + } + + if (!isset($this->codes[$offset])) { + return; + } + + + $value = self::resolveFirstCodePoint($value); + if ($value === -1) { + return; + } + + if ($value === $this->codes[$offset]) { + // Same value, nothing to do + return; + } + + $this->codes[$offset] = $value; + + // Clear cache + $this->str = null; + $this->cache = null; + if ($this->chars) { + $this->chars[$offset] = self::getCharFromCodePoint($value); + } + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + throw new RuntimeException("Invalid operation"); + } + + /** + * @inheritDoc + */ + public function count(): int + { + return $this->length; + } + + /** + * @return string + */ + public function __toString(): string + { + if ($this->str === null) { + $this->str = self::getStringFromCodePoints($this->codes); + } + + return $this->str; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): string + { + return $this->__toString(); + } + + public function __serialize(): array + { + return [ + 'value' => $this->__toString(), + ]; + } + + public function __unserialize(array $data): void + { + $this->str = $data['value']; + $this->codes = self::getCodePointsFromString($this->str); + $this->length = count($this->codes); + } + + /** + * Creates an unicode string instance from raw string + * @param string $string + * @param string|null $encoding Defaults to UTF-8 + * @param int $mode + * @return static + * @throws InvalidStringException + */ + public static function from(string $string, ?string $encoding = null, int $mode = self::KEEP_CASE): self + { + if ($encoding !== null && strcasecmp($encoding, 'UTF-8') !== 0) { + if (false === $string = @iconv($encoding, 'UTF-8', $string)) { + throw new UnicodeException("Could not convert string from '$encoding' encoding to UTF-8 encoding"); + } + } + + $instance = new static(self::getCodePointsFromString($string, $mode)); + if ($mode === self::KEEP_CASE) { + $instance->str = $string; + } + return $instance; + } + + /** + * Creates an unicode string instance from code points + * @param int[] $codes + * @param int $mode + * @return static + * @throws InvalidCodePointException + */ + public static function fromCodePoints(array $codes, int $mode = self::KEEP_CASE): self + { + $map = self::getMapByMode($mode); + + foreach ($codes as &$code) { + if (!is_int($codes) || !self::isValidCodePoint($code)) { + throw new InvalidCodePointException($code); + } else { + $code = $map[$code] ?? $code; + } + } + + return new static(array_values($codes)); + } + + /** + * Converts the code point to corresponding char + * @param int $code + * @return string The char or an empty string if code point is invalid + */ + public static function getCharFromCodePoint(int $code): string + { + if ($code < 0) { + return ''; + } + + if ($code < 0x80) { + return chr($code); + } + + if ($code < 0x800) { + return chr(($code >> 6) + 0xC0) . chr(($code & 0x3F) + 0x80); + } + + if ($code >= 0xD800 && $code <= 0xDFFF) { + /* + The definition of UTF-8 prohibits encoding character numbers between + U+D800 and U+DFFF, which are reserved for use with the UTF-16 + encoding form (as surrogate pairs) and do not directly represent characters. + */ + return ''; + } + + if ($code <= 0xFFFF) { + return + chr(($code >> 12) + 0xE0) . + chr((($code >> 6) & 0x3F) + 0x80) . + chr(($code & 0x3F) + 0x80); + } + + if ($code <= 0x10FFFF) { + return + chr(($code >> 18) + 0xF0) . + chr((($code >> 12) & 0x3F) + 0x80) . + chr((($code >> 6) & 0x3F) + 0x80) . + chr(($code & 0x3F) + 0x80); + } + + /* + Restricted the range of characters to 0000-10FFFF (the UTF-16 accessible range). + */ + + return ''; + } + + /** + * Convert a string to a code point array + * @param string $str + * @param int $mode + * @return array + * @throws InvalidStringException + */ + public static function getCodePointsFromString(string $str, int $mode = self::KEEP_CASE): array + { + // 0x00-0x7F + // 0xC2-0xDF 0x80-0xBF + // 0xE0-0xE0 0xA0-0xBF 0x80-0xBF + // 0xE1-0xEC 0x80-0xBF 0x80-0xBF + // 0xED-0xED 0x80-0x9F 0x80-0xBF + // 0xEE-0xEF 0x80-0xBF 0x80-0xBF + // 0xF0-0xF0 0x90-0xBF 0x80-0xBF 0x80-0xBF + // 0xF1-0xF3 0x80-0xBF 0x80-0xBF 0x80-0xBF + // 0xF4-0xF4 0x80-0x8F 0x80-0xBF 0x80-0xBF + + $codes = []; + $length = strlen($str); + $mode = self::getMapByMode($mode); + + $i = 0; + while ($i < $length) { + $ord0 = ord($str[$i++]); + + if ($ord0 < 0x80) { + $codes[] = $mode[$ord0] ?? $ord0; + continue; + } + + if ($i === $length || $ord0 < 0xC2 || $ord0 > 0xF4) { + throw new InvalidStringException($str, $i - 1); + } + + $ord1 = ord($str[$i++]); + + if ($ord0 < 0xE0) { + if ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + $ord1 = ($ord0 - 0xC0) * 64 + $ord1 - 0x80; + $codes[] = $mode[$ord1] ?? $ord1; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord2 = ord($str[$i++]); + + if ($ord0 < 0xF0) { + if ($ord0 === 0xE0) { + if ($ord1 < 0xA0 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord0 === 0xED) { + if ($ord1 < 0x80 || $ord1 >= 0xA0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + $ord2 = ($ord0 - 0xE0) * 0x1000 + ($ord1 - 0x80) * 64 + $ord2 - 0x80; + $codes[] = $mode[$ord2] ?? $ord2; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord3 = ord($str[$i++]); + + if ($ord0 < 0xF5) { + if ($ord0 === 0xF0) { + if ($ord1 < 0x90 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord0 === 0xF4) { + if ($ord1 < 0x80 || $ord1 >= 0x90) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord3 < 0x80 || $ord3 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + $ord3 = ($ord0 - 0xF0) * 0x40000 + ($ord1 - 0x80) * 0x1000 + ($ord2 - 0x80) * 64 + $ord3 - 0x80; + $codes[] = $mode[$ord3] ?? $ord3; + + continue; + } + + throw new InvalidStringException($str, $i - 1); + } + + return $codes; + } + + /** + * @param string $str + * @return iterable + * + * The key represents the current char index + * Value is a two element array + * - first element is an integer representing the code point + * - second element is an array of integers (length 1 to 4) representing bytes + */ + public static function walkString(string $str): iterable + { + $i = 0; + $length = strlen($str); + + while ($i < $length) { + $index = $i; + + $ord0 = ord($str[$i++]); + + if ($ord0 < 0x80) { + yield $index => [ + $ord0, + [$ord0] + ]; + continue; + } + + if ($i === $length || $ord0 < 0xC2 || $ord0 > 0xF4) { + throw new InvalidStringException($str, $i - 1); + } + + $ord1 = ord($str[$i++]); + + if ($ord0 < 0xE0) { + if ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + yield $index => [ + ($ord0 - 0xC0) * 64 + $ord1 - 0x80, + [$ord0, $ord1] + ]; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord2 = ord($str[$i++]); + + if ($ord0 < 0xF0) { + if ($ord0 === 0xE0) { + if ($ord1 < 0xA0 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord0 === 0xED) { + if ($ord1 < 0x80 || $ord1 >= 0xA0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + yield $index => [ + ($ord0 - 0xE0) * 0x1000 + ($ord1 - 0x80) * 64 + $ord2 - 0x80, + [$ord0, $ord1, $ord2] + ]; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord3 = ord($str[$i++]); + + if ($ord0 < 0xF5) { + if ($ord0 === 0xF0) { + if ($ord1 < 0x90 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord0 === 0xF4) { + if ($ord1 < 0x80 || $ord1 >= 0x90) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord3 < 0x80 || $ord3 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + yield $index => [ + ($ord0 - 0xF0) * 0x40000 + ($ord1 - 0x80) * 0x1000 + ($ord2 - 0x80) * 64 + $ord3 - 0x80, + [$ord0, $ord1, $ord2, $ord3] + ]; + + continue; + } + + throw new InvalidStringException($str, $i - 1); + } + } + + /** + * Compute string length + * @param string $str + * @return int + * @throws InvalidStringException + */ + public static function getStringLength(string $str): int + { + $count = 0; + $length = strlen($str); + + $i = 0; + while ($i < $length) { + $ord0 = ord($str[$i++]); + + if ($ord0 < 0x80) { + $count++; + continue; + } + + if ($i === $length || $ord0 < 0xC2 || $ord0 > 0xF4) { + throw new InvalidStringException($str, $i - 1); + } + + $ord1 = ord($str[$i++]); + + if ($ord0 < 0xE0) { + if ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + // $ord1 = ($ord0 - 0xC0) * 64 + $ord1 - 0x80; + $count++; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord2 = ord($str[$i++]); + + if ($ord0 < 0xF0) { + if ($ord0 === 0xE0) { + if ($ord1 < 0xA0 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord0 === 0xED) { + if ($ord1 < 0x80 || $ord1 >= 0xA0) { + throw new InvalidStringException($str, $i - 2); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + // $ord2 = ($ord0 - 0xE0) * 0x1000 + ($ord1 - 0x80) * 64 + $ord2 - 0x80; + $count++; + + continue; + } + + if ($i === $length) { + throw new InvalidStringException($str, $i - 1); + } + + $ord3 = ord($str[$i++]); + + if ($ord0 < 0xF5) { + if ($ord0 === 0xF0) { + if ($ord1 < 0x90 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord0 === 0xF4) { + if ($ord1 < 0x80 || $ord1 >= 0x90) { + throw new InvalidStringException($str, $i - 3); + } + } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { + throw new InvalidStringException($str, $i - 3); + } + + if ($ord2 < 0x80 || $ord2 >= 0xC0) { + throw new InvalidStringException($str, $i - 2); + } + + if ($ord3 < 0x80 || $ord3 >= 0xC0) { + throw new InvalidStringException($str, $i - 1); + } + + // $ord3 = ($ord0 - 0xF0) * 0x40000 + ($ord1 - 0x80) * 0x1000 + ($ord2 - 0x80) * 64 + $ord3 - 0x80; + $count++; + + continue; + } + + throw new InvalidStringException($str, $i - 1); + } + + return $count; + } + + /** + * Converts each code point to a char + * @param array $codes + * @param int $mode + * @return array + * @throws InvalidCodePointException + */ + public static function getCharsFromCodePoints(array $codes, int $mode = self::KEEP_CASE): array + { + $mode = self::getMapByMode($mode); + + foreach ($codes as &$code) { + $char = self::getCharFromCodePoint($mode[$code] ?? $code); + if ($char === '') { + throw new InvalidCodePointException($code); + } else { + $code = $char; + } + } + + return $codes; + } + + /** + * @param string $str + * @param int $mode + * @return string[] + */ + public static function getCharsFromString(string $str, int $mode = self::KEEP_CASE): array + { + return self::getCharsFromCodePoints(self::getCodePointsFromString($str), $mode); + } + + /** + * Converts all code points to chars and returns the string + * Invalid code points are ignored + * @param array $codes + * @param int $mode + * @return string + */ + public static function getStringFromCodePoints(array $codes, int $mode = self::KEEP_CASE): string + { + $str = ''; + + $mode = self::getMapByMode($mode); + + foreach ($codes as $code) { + if (isset($mode[$code])) { + $code = $mode[$code]; + } + + if ($code < 0x80) { + $str .= chr($code); + continue; + } + + if ($code < 0x800) { + $str .= chr(($code >> 6) + 0xC0) . chr(($code & 0x3F) + 0x80); + continue; + } + + if ($code >= 0xD800 && $code <= 0xDFFF) { + continue; + } + + if ($code <= 0xFFFF) { + $str .= + chr(($code >> 12) + 0xE0) . + chr((($code >> 6) & 0x3F) + 0x80) . + chr(($code & 0x3F) + 0x80); + continue; + } + + if ($code <= 0x10FFFF) { + $str .= + chr(($code >> 18) + 0xF0) . + chr((($code >> 12) & 0x3F) + 0x80) . + chr((($code >> 6) & 0x3F) + 0x80) . + chr(($code & 0x3F) + 0x80); + } + } + + return $str; + } + + /** + * @param array $codes + * @param int $mode + * @return array + */ + public static function getMappedCodePoints(array $codes, int $mode): array + { + if ($mode === self::KEEP_CASE) { + return $codes; + } + + $mode = self::getMapByMode($mode); + + if (empty($mode)) { + return $codes; + } + + foreach ($codes as &$code) { + $code = $mode[$code] ?? $code; + } + + return $codes; + } + + /** + * Checks if a code point is valid + * @param int $code + * @return bool + */ + public static function isValidCodePoint(int $code): bool + { + if ($code < 0 || $code > 0x10FFFF) { + return false; + } + + return $code < 0xD800 || $code > 0xDFFF; + } + + /** + * @param int $mode + * @return int[] + */ + private function getMappedCodes(int $mode): array + { + if ($mode === self::KEEP_CASE || ($this->cache['i' . $mode] ?? false)) { + return $this->codes; + } + + $key = 'm' . $mode; + + if (!isset($this->cache[$key])) { + $this->cache[$key] = self::getMappedCodePoints($this->codes, $mode); + } + + return $this->cache[$key]; + } + + /** + * @param int $mode + * @return bool + */ + private function isCase(int $mode): bool + { + $key = 'i' . $mode; + + if (!isset($this->cache[$key])) { + $list = self::getMapByMode($mode); + foreach ($this->codes as $code) { + if (isset($list[$code])) { + return $this->cache[$key] = false; + } + } + + return $this->cache[$key] = true; + } + + return $this->cache[$key]; + } + + /** + * @param int[] $codes + * @param int[] $text + * @param int $offset + * @return int + */ + private function doIndexOf(array $codes, array $text, int $offset = 0): int + { + $len = count($text); + + for ($i = $offset, $last = count($codes) - $len; $i <= $last; $i++) { + $match = true; + + for ($j = 0; $j < $len; $j++) { + if ($codes[$i + $j] !== $text[$j]) { + $match = false; + break; + } + } + + if ($match) { + return $i; + } + } + + return -1; + } + + /** + * @param string|self|int[]|string[] $mask + * @param bool $left + * @param bool $right + * @return static + */ + private function doTrim($mask, bool $left, bool $right): self + { + if ($this->length === 0) { + return clone $this; + } + + $mask = self::resolveCodePoints($mask); + + if (empty($mask)) { + return clone $this; + } + + $codes = $this->codes; + + if ($left) { + while (in_array($codes[0], $mask, true)) { + array_shift($codes); + if (empty($codes)) { + return new static(); + } + } + } + + if ($right) { + $last = count($codes) - 1; + while (in_array($codes[$last], $mask, true)) { + array_pop($codes); + if (--$last < 0) { + return new static(); + } + } + } + + return new static($codes); + } + + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return array + */ + private static function resolveCodePoints($text, int $mode = self::KEEP_CASE): array + { + if ($text instanceof self) { + return $text->getMappedCodes($mode); + } + + if (is_string($text)) { + return self::getCodePointsFromString($text, $mode); + } + + if ($text && is_array($text) && is_int($text[0])) { + // assume code point array + return self::getMappedCodePoints($text, $mode); + } + + return []; + } + + /** + * @param self|string|int|string[]|int[] $text + * @param int $invalid + * @return int + */ + private static function resolveFirstCodePoint($text, int $invalid = -1): int + { + if ($text instanceof self) { + return $text->length === 0 ? $invalid : $text->codes[0]; + } + + if (is_array($text)) { + if (empty($text)) { + return $invalid; + } + $text = reset($text); + } + + if (is_string($text)) { + if (isset($text[4])) { + $text = substr($text, 0, 4); + } + return self::getCodePointsFromString($text)[0] ?? $invalid; + } + + if (is_int($text)) { + return self::isValidCodePoint($text) ? $text : $invalid; + } + + return $invalid; + } + + /** + * @param int $mode + * @return int[] + */ + private static function getMapByMode(int $mode): array + { + if (isset(self::$maps[$mode])) { + return self::$maps[$mode]; + } + + switch ($mode) { + case self::LOWER_CASE: + $file = 'lower'; + break; + case self::UPPER_CASE: + $file = 'upper'; + break; + case self::ASCII_CONV: + $file = 'ascii'; + break; + case self::FOLD_CASE: + $file = 'fold'; + break; + default: + return []; + } + + /** @noinspection PhpIncludeInspection */ + return self::$maps[$mode] = include(__DIR__ . "/../res/{$file}.php"); + } +} diff --git a/admin/vendor/opis/uri/LICENSE b/admin/vendor/opis/uri/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/admin/vendor/opis/uri/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/admin/vendor/opis/uri/README.md b/admin/vendor/opis/uri/README.md new file mode 100644 index 0000000..9d82dca --- /dev/null +++ b/admin/vendor/opis/uri/README.md @@ -0,0 +1,40 @@ +Opis URI +========== +[![Tests](https://github.com/opis/uri/workflows/Tests/badge.svg)](https://github.com/opis/uri/actions) +[![Latest Stable Version](https://poser.pugx.org/opis/uri/version.png)](https://packagist.org/packages/opis/uri) +[![Latest Unstable Version](https://poser.pugx.org/opis/uri/v/unstable.png)](//packagist.org/packages/opis/uri) +[![License](https://poser.pugx.org/opis/uri/license.png)](https://packagist.org/packages/opis/uri) + + +**Opis URI** library allows you to build, parse and validate URIs and URI-templates. + +## License + +**Opis URI** is licensed under the [Apache License, Version 2.0][license]. + +## Requirements + +* PHP ^7.4 || ^8.0 + +## Installation + +**Opis URI** is available on [Packagist] and it can be installed from a +command line interface by using [Composer]. + +```bash +composer require opis/uri +``` + +Or you could directly reference it into your `composer.json` file as a dependency + +```json +{ + "require": { + "opis/uri": "^1.0" + } +} +``` + +[license]: https://www.apache.org/licenses/LICENSE-2.0 "Apache License" +[Packagist]: https://packagist.org/packages/opis/database "Packagist" +[Composer]: https://getcomposer.org "Composer" \ No newline at end of file diff --git a/admin/vendor/opis/uri/autoload.php b/admin/vendor/opis/uri/autoload.php new file mode 100644 index 0000000..8aa754c --- /dev/null +++ b/admin/vendor/opis/uri/autoload.php @@ -0,0 +1,42 @@ += $n && $input[$i] < $m) { + $m = $input[$i]; + } + } + + if (($m - $n) > intdiv(self::MAX_INT - $delta, $handled + 1)) { + throw new PunycodeException("Punycode overflow"); + } + + $delta += ($m - $n) * ($handled + 1); + + $n = $m; + + for ($i = 0; $i < $input_len; $i++) { + if ($input[$i] < $n && (++$delta === 0)) { + throw new PunycodeException("Punycode overflow"); + } + + if ($input[$i] === $n) { + $q = $delta; + for ($k = self::BASE; ; $k += self::BASE) { + $t = self::threshold($k, $bias); + if ($q < $t) { + break; + } + + $base_minus_t = self::BASE - $t; + + $q -= $t; + + $output[] = self::encodeDigit($t + ($q % $base_minus_t)); + + $q = intdiv($q, $base_minus_t); + } + + $output[] = self::encodeDigit($q); + + $bias = self::adapt($delta, $handled + 1, $handled === $basic_length); + $delta = 0; + $handled++; + } + } + + $delta++; $n++; + } + + return self::PREFIX . UnicodeString::getStringFromCodePoints($output); + } + + public static function decodePart(string $input): string + { + if (stripos($input, self::PREFIX) !== 0) { + return $input; + } + + $input = UnicodeString::getCodePointsFromString(substr($input, self::PREFIX_LEN), UnicodeString::LOWER_CASE); + $input_len = count($input); + + $pos = array_keys($input, self::DELIMITER, true); + if ($pos) { + $pos = end($pos); + } else { + $pos = -1; + } + + /** @var int $pos */ + + if ($pos === -1) { + $output = []; + $pos = $output_len = 0; + } else { + $output = array_slice($input, 0, ++$pos); + $output_len = $pos; + for ($i = 0; $i < $pos; $i++) { + if ($output[$i] >= 0x80) { + throw new PunycodeException("Non-basic code point is not allowed: {$output[$i]}"); + } + } + } + + $i = 0; + $n = self::INITIAL_N; + $bias = self::INITIAL_BIAS; + + while ($pos < $input_len) { + $old_i = $i; + + for ($w = 1, $k = self::BASE; ; $k += self::BASE) { + if ($pos >= $input_len) { + throw new PunycodeException("Punycode bad input"); + } + + $digit = self::decodeDigit($input[$pos++]); + + if ($digit >= self::BASE || $digit > intdiv(self::MAX_INT - $i, $w)) { + throw new PunycodeException("Punycode overflow"); + } + + $i += $digit * $w; + + $t = self::threshold($k, $bias); + if ($digit < $t) { + break; + } + + $t = self::BASE - $t; + + if ($w > intdiv(self::MAX_INT, $t)) { + throw new PunycodeException("Punycode overflow"); + } + + $w *= $t; + } + + $output_len++; + + if (intdiv($i, $output_len) > self::MAX_INT - $n) { + throw new PunycodeException("Punycode overflow"); + } + + $n += intdiv($i, $output_len); + + $bias = self::adapt($i - $old_i, $output_len, $old_i === 0); + + $i %= $output_len; + + array_splice($output, $i, 0, $n); + + $i++; + } + + return UnicodeString::getStringFromCodePoints($output); + } + + public static function normalizePart(string $input): string + { + $input = strtolower($input); + + if (strpos($input, self::DELIMITER) === 0) { + self::decodePart($input); // just validate + return $input; + } + + return self::encodePart($input); + } + + private static function encodeDigit(int $digit): int + { + return $digit + 0x16 + ($digit < 0x1A ? 0x4B: 0x00); + } + + private static function decodeDigit(int $code): int + { + if ($code < 0x3A) { + return $code - 0x16; + } + if ($code < 0x5B) { + return $code - 0x41; + } + if ($code < 0x7B) { + return $code - 0x61; + } + + return self::BASE; + } + + private static function threshold(int $k, int $bias): int + { + $d = $k - $bias; + + if ($d <= self::TMIN) { + return self::TMIN; + } + + if ($d >= self::TMAX) { + return self::TMAX; + } + + return $d; + } + + private static function adapt(int $delta, int $num_points, bool $first_time = false): int + { + $delta = intdiv($delta, $first_time ? self::DAMP : 2); + $delta += intdiv($delta, $num_points); + + $k = 0; + $base_tmin_diff = self::BASE - self::TMIN; + $lim = $base_tmin_diff * self::TMAX / 2; + + while ($delta > $lim) { + $delta = intdiv($delta, $base_tmin_diff); + $k += self::BASE; + } + + $k += intdiv(($base_tmin_diff + 1) * $delta, $delta + self::SKEW); + + return $k; + } +} \ No newline at end of file diff --git a/admin/vendor/opis/uri/src/PunycodeException.php b/admin/vendor/opis/uri/src/PunycodeException.php new file mode 100644 index 0000000..6eb1c12 --- /dev/null +++ b/admin/vendor/opis/uri/src/PunycodeException.php @@ -0,0 +1,25 @@ +[^:]+)(?::(?.*))?$`'; + + protected const HOST_LABEL_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-]+)*$`i'; + + protected const AUTHORITY_REGEX = '`^(?:(?[^@]+)\@)?(?(\[[a-f0-9:]+\]|[^:]+))(?::(?\d+))?$`i'; + + protected const PATH_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'()*+,;=:@/]+)*$`i'; + + protected const QUERY_OR_FRAGMENT_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'"()\[\]*+,;=:@?/%]+)*$`i'; + + protected array $components; + + protected ?string $str = null; + + /** + * @param array $components An array of normalized components + */ + public function __construct(array $components) + { + $this->components = $components + [ + 'scheme' => null, + 'user' => null, + 'pass' => null, + 'host' => null, + 'port' => null, + 'path' => null, + 'query' => null, + 'fragment' => null, + ]; + } + + /** + * @return string|null + */ + public function scheme(): ?string + { + return $this->components['scheme']; + } + + /** + * @return string|null + */ + public function user(): ?string + { + return $this->components['user']; + } + + /** + * @return string|null + */ + public function pass(): ?string + { + return $this->components['pass']; + } + + /** + * @return string|null + */ + public function userInfo(): ?string + { + if ($this->components['user'] === null) { + return null; + } + + if ($this->components['pass'] === null) { + return $this->components['user']; + } + + return $this->components['user'] . ':' . $this->components['pass']; + } + + /** + * @return string|null + */ + public function host(): ?string + { + return $this->components['host']; + } + + /** + * @return int|null + */ + public function port(): ?int + { + return $this->components['port']; + } + + /** + * @return string|null + */ + public function authority(): ?string + { + if ($this->components['host'] === null) { + return null; + } + + $authority = $this->userInfo(); + if ($authority !== null) { + $authority .= '@'; + } + + $authority .= $this->components['host']; + + if ($this->components['port'] !== null) { + $authority .= ':' . $this->components['port']; + } + + return $authority; + } + + /** + * @return string|null + */ + public function path(): ?string + { + return $this->components['path']; + } + + /** + * @return string|null + */ + public function query(): ?string + { + return $this->components['query']; + } + + /** + * @return string|null + */ + public function fragment(): ?string + { + return $this->components['fragment']; + } + + /** + * @return array|null[] + */ + public function components(): array + { + return $this->components; + } + + /** + * @return bool + */ + public function isAbsolute(): bool + { + return $this->components['scheme'] !== null; + } + + /** + * Use this URI as base to resolve the reference + * @param static|string|array $ref + * @param bool $normalize + * @return $this|null + */ + public function resolveRef($ref, bool $normalize = false): ?self + { + $ref = self::resolveComponents($ref); + if ($ref === null) { + return $this; + } + + return new static(self::mergeComponents($ref, $this->components, $normalize)); + } + + /** + * Resolve this URI reference using a base URI + * @param static|string|array $base + * @param bool $normalize + * @return static + */ + public function resolve($base, bool $normalize = false): self + { + if ($this->isAbsolute()) { + return $this; + } + + $base = self::resolveComponents($base); + + if ($base === null) { + return $this; + } + + return new static(self::mergeComponents($this->components, $base, $normalize)); + } + + /** + * @return string + */ + public function __toString(): string + { + if ($this->str !== null) { + return $this->str; + } + + $str = ''; + + if ($this->components['scheme'] !== null) { + $str .= $this->components['scheme'] . ':'; + } + + if ($this->components['host'] !== null) { + $str .= '//' . $this->authority(); + } + + $str .= $this->components['path']; + + if ($this->components['query'] !== null) { + $str .= '?' . $this->components['query']; + } + + if ($this->components['fragment'] !== null) { + $str .= '#' . $this->components['fragment']; + } + + return $this->str = $str; + } + + /** + * @param string $uri + * @param bool $normalize + * @return static|null + */ + public static function create(string $uri, bool $normalize = false): ?self + { + $comp = self::parseComponents($uri); + if (!$comp) { + return null; + } + + if ($normalize) { + $comp = self::normalizeComponents($comp); + } + + return new static($comp); + } + + /** + * Checks if the scheme contains valid chars + * @param string $scheme + * @return bool + */ + public static function isValidScheme(string $scheme): bool + { + return (bool)preg_match(self::SCHEME_REGEX, $scheme); + } + + /** + * Checks if user contains valid chars + * @param string $user + * @return bool + */ + public static function isValidUser(string $user): bool + { + return (bool)preg_match(self::USER_OR_PASS_REGEX, $user); + } + + /** + * Checks if pass contains valid chars + * @param string $pass + * @return bool + */ + public static function isValidPass(string $pass): bool + { + return (bool)preg_match(self::USER_OR_PASS_REGEX, $pass); + } + + /** + * @param string $userInfo + * @return bool + */ + public static function isValidUserInfo(string $userInfo): bool + { + /** @var array|string $userInfo */ + + if (!preg_match(self::USERINFO_REGEX, $userInfo, $userInfo)) { + return false; + } + + if (!self::isValidUser($userInfo['user'])) { + return false; + } + + if (isset($userInfo['pass'])) { + return self::isValidPass($userInfo['pass']); + } + + return true; + } + + /** + * Checks if host is valid + * @param string $host + * @return bool + */ + public static function isValidHost(string $host): bool + { + // min and max length + if ($host === '' || isset($host[253])) { + return false; + } + + // check ipv6 + if ($host[0] === '[') { + if ($host[-1] !== ']') { + return false; + } + + return filter_var( + substr($host, 1, -1), + \FILTER_VALIDATE_IP, + \FILTER_FLAG_IPV6 + ) !== false; + } + + // check ipv4 + if (preg_match('`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$`', $host)) { + return \filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) !== false; + } + + foreach (explode('.', $host) as $host) { + // empty or too long label + if ($host === '' || isset($host[63])) { + return false; + } + if ($host[0] === '-' || $host[-1] === '-') { + return false; + } + if (!preg_match(self::HOST_LABEL_REGEX, $host)) { + return false; + } + } + + return true; + } + + /** + * Checks if the port is valid + * @param int $port + * @return bool + */ + public static function isValidPort(int $port): bool + { + return $port >= 0 && $port <= 65535; + } + + /** + * Checks if authority contains valid chars + * @param string $authority + * @return bool + */ + public static function isValidAuthority(string $authority): bool + { + if ($authority === '') { + return true; + } + + /** @var array|string $authority */ + + if (!preg_match(self::AUTHORITY_REGEX, $authority, $authority)) { + return false; + } + + if (isset($authority['port']) && !self::isValidPort((int)$authority['port'])) { + return false; + } + + if (isset($authority['userinfo']) && !self::isValidUserInfo($authority['userinfo'])) { + return false; + } + + return self::isValidHost($authority['host']); + } + + /** + * Checks if the path contains valid chars + * @param string $path + * @return bool + */ + public static function isValidPath(string $path): bool + { + return $path === '' || (bool)preg_match(self::PATH_REGEX, $path); + } + + /** + * Checks if the query string contains valid chars + * @param string $query + * @return bool + */ + public static function isValidQuery(string $query): bool + { + return $query === '' || (bool)preg_match(self::QUERY_OR_FRAGMENT_REGEX, $query); + } + + /** + * Checks if the fragment contains valid chars + * @param string $fragment + * @return bool + */ + public static function isValidFragment(string $fragment): bool + { + return $fragment === '' || (bool)preg_match(self::QUERY_OR_FRAGMENT_REGEX, $fragment); + } + + /** + * @param string $uri + * @param bool $expand_authority + * @param bool $validate + * @return array|null + */ + public static function parseComponents(string $uri, bool $expand_authority = true, bool $validate = true): ?array + { + if (!preg_match(self::URI_REGEX, $uri, $uri)) { + return null; + } + + $comp = []; + + // scheme + if (isset($uri[2]) && $uri[2] !== '') { + if ($validate && !self::isValidScheme($uri[2])) { + return null; + } + $comp['scheme'] = $uri[2]; + } + + // authority + if (isset($uri[4]) && isset($uri[3][0])) { + if ($uri[4] === '') { + if ($expand_authority) { + $comp['host'] = ''; + } else { + $comp['authority'] = ''; + } + } elseif ($expand_authority) { + $au = self::parseAuthorityComponents($uri[4], $validate); + if ($au === null) { + return null; + } + $comp += $au; + unset($au); + } else { + if ($validate && !self::isValidAuthority($uri[4])) { + return null; + } + $comp['authority'] = $uri[4]; + } + } + + // path + if (isset($uri[5])) { + if ($validate && !self::isValidPath($uri[5])) { + return null; + } + $comp['path'] = $uri[5]; + // not a relative uri, remove dot segments + if (isset($comp['scheme']) || isset($comp['authority']) || isset($comp['host'])) { + $comp['path'] = self::removeDotSegmentsFromPath($comp['path']); + } + } + + // query + if (isset($uri[7]) && isset($uri[6][0])) { + if ($validate && !self::isValidQuery($uri[7])) { + return null; + } + $comp['query'] = $uri[7]; + } + + // fragment + if (isset($uri[9]) && isset($uri[8][0])) { + if ($validate && !self::isValidFragment($uri[9])) { + return null; + } + $comp['fragment'] = $uri[9]; + } + + return $comp; + } + + /** + * @param self|string|array $uri + * @return array|null + */ + public static function resolveComponents($uri): ?array + { + if ($uri instanceof self) { + return $uri->components; + } + + if (is_string($uri)) { + return self::parseComponents($uri); + } + + if (is_array($uri)) { + if (isset($uri['host'])) { + unset($uri['authority']); + } elseif (isset($uri['authority'])) { + $au = self::parseAuthorityComponents($uri['authority']); + unset($uri['authority']); + if ($au !== null) { + unset($uri['user'], $uri['pass'], $uri['host'], $uri['port']); + $uri += $au; + } + } + return $uri; + } + + return null; + } + + /** + * @param string $authority + * @param bool $validate + * @return array|null + */ + public static function parseAuthorityComponents(string $authority, bool $validate = true): ?array + { + /** @var array|string $authority */ + + if (!preg_match(self::AUTHORITY_REGEX, $authority, $authority)) { + return null; + } + + $comp = []; + + // userinfo + if (isset($authority['userinfo']) && $authority['userinfo'] !== '') { + if (!preg_match(self::USERINFO_REGEX, $authority['userinfo'], $ui)) { + return null; + } + + // user + if ($validate && !self::isValidUser($ui['user'])) { + return null; + } + $comp['user'] = $ui['user']; + + // pass + if (isset($ui['pass']) && $ui['pass'] !== '') { + if ($validate && !self::isValidPass($ui['pass'])) { + return null; + } + $comp['pass'] = $ui['pass']; + } + + unset($ui); + } + + // host + if ($validate && !self::isValidHost($authority['host'])) { + return null; + } + $comp['host'] = $authority['host']; + + + // port + if (isset($authority['port'])) { + $authority['port'] = (int)$authority['port']; + if (!self::isValidPort($authority['port'])) { + return null; + } + $comp['port'] = $authority['port']; + } + + return $comp; + } + + /** + * @param array $ref + * @param array $base + * @param bool $normalize + * @return array + */ + public static function mergeComponents(array $ref, array $base, bool $normalize = false): array + { + if (isset($ref['scheme'])) { + $dest = $ref; + } else { + $dest = []; + + $dest['scheme'] = $base['scheme'] ?? null; + + if (isset($ref['authority']) || isset($ref['host'])) { + $dest += $ref; + } else { + if (isset($base['authority'])) { + $dest['authority'] = $base['authority']; + } else { + $dest['user'] = $base['user'] ?? null; + $dest['pass'] = $base['pass'] ?? null; + $dest['host'] = $base['host'] ?? null; + $dest['port'] = $base['port'] ?? null; + } + + if (!isset($ref['path'])) { + $ref['path'] = ''; + } + if (!isset($base['path'])) { + $base['path'] = ''; + } + + if ($ref['path'] === '') { + $dest['path'] = $base['path']; + $dest['query'] = $ref['query'] ?? $base['query'] ?? null; + } else { + if ($ref['path'][0] === '/') { + $dest['path'] = $ref['path']; + } else { + if ((isset($base['authority']) || isset($base['host'])) && $base['path'] === '') { + $dest['path'] = '/' . $ref['path']; + } else { + $dest['path'] = $base['path']; + + if ($dest['path'] !== '') { + $pos = strrpos($dest['path'], '/'); + if ($pos === false) { + $dest['path'] = ''; + } else { + $dest['path'] = substr($dest['path'], 0, $pos); + } + + unset($pos); + } + $dest['path'] .= '/' . $ref['path']; + } + } + + $dest['query'] = $ref['query'] ?? null; + } + } + } + + $dest['fragment'] = $ref['fragment'] ?? null; + + if ($normalize) { + return self::normalizeComponents($dest); + } + + if (isset($dest['path'])) { + $dest['path'] = self::removeDotSegmentsFromPath($dest['path']); + } + + return $dest; + } + + public static function normalizeComponents(array $components): array + { + if (isset($components['scheme'])) { + $components['scheme'] = strtolower($components['scheme']); + // Remove default port + if (isset($components['port']) && self::getSchemePort($components['scheme']) === $components['port']) { + $components['port'] = null; + } + } + + if (isset($components['host'])) { + $components['host'] = strtolower($components['host']); + } + + if (isset($components['path'])) { + $components['path'] = self::removeDotSegmentsFromPath($components['path']); + } + + if (isset($components['query'])) { + $components['query'] = self::normalizeQueryString($components['query']); + } + + return $components; + } + + /** + * Removes dot segments from path + * @param string $path + * @return string + */ + public static function removeDotSegmentsFromPath(string $path): string + { + // Fast check common simple paths + if ($path === '' || $path === '/') { + return $path; + } + + $output = ''; + $last_slash = 0; + + $len = strlen($path); + $i = 0; + + while ($i < $len) { + if ($path[$i] === '.') { + $j = $i + 1; + // search for . + if ($j >= $len) { + break; + } + + // search for ./ + if ($path[$j] === '/') { + $i = $j + 1; + continue; + } + + // search for ../ + if ($path[$j] === '.') { + $k = $j + 1; + if ($k >= $len) { + break; + } + if ($path[$k] === '/') { + $i = $k + 1; + continue; + } + } + } elseif ($path[$i] === '/') { + $j = $i + 1; + if ($j >= $len) { + $output .= '/'; + break; + } + + // search for /. + if ($path[$j] === '.') { + $k = $j + 1; + if ($k >= $len) { + $output .= '/'; + break; + } + // search for /./ + if ($path[$k] === '/') { + $i = $k; + continue; + } + // search for /.. + if ($path[$k] === '.') { + $n = $k + 1; + if ($n >= $len) { + // keep the slash + $output = substr($output, 0, $last_slash + 1); + break; + } + // search for /../ + if ($path[$n] === '/') { + $output = substr($output, 0, $last_slash); + $last_slash = (int)strrpos($output, '/'); + $i = $n; + continue; + } + } + } + } + + $pos = strpos($path, '/', $i + 1); + + if ($pos === false) { + $output .= substr($path, $i); + break; + } + + $last_slash = strlen($output); + $output .= substr($path, $i, $pos - $i); + + $i = $pos; + } + + return $output; + } + + /** + * @param string|null $query + * @return array + */ + public static function parseQueryString(?string $query): array + { + if ($query === null) { + return []; + } + + $list = []; + + foreach (explode('&', $query) as $name) { + $value = null; + if (($pos = strpos($name, '=')) !== false) { + $value = self::decodeComponent(substr($name, $pos + 1)); + $name = self::decodeComponent(substr($name, 0, $pos)); + } else { + $name = self::decodeComponent($name); + } + $list[$name] = $value; + } + + return $list; + } + + /** + * @param array $qs + * @param string|null $prefix + * @param string $separator + * @param bool $sort + * @return string + */ + public static function buildQueryString(array $qs, ?string $prefix = null, + string $separator = '&', bool $sort = false): string + { + $isIndexed = static function (array $array): bool { + for ($i = 0, $max = count($array); $i < $max; $i++) { + if (!array_key_exists($i, $array)) { + return false; + } + } + return true; + }; + + $f = static function (array $arr, ?string $prefix = null) use (&$f, &$isIndexed): iterable { + $indexed = $prefix !== null && $isIndexed($arr); + + foreach ($arr as $key => $value) { + if ($prefix !== null) { + $key = $prefix . ($indexed ? "[]" : "[{$key}]"); + } + if (is_array($value)) { + yield from $f($value, $key); + } else { + yield $key => $value; + } + } + }; + + $data = []; + + foreach ($f($qs, $prefix) as $key => $value) { + $item = is_string($key) ? self::encodeComponent($key) : $key; + if ($value !== null) { + $item .= '='; + $item .= is_string($value) ? self::encodeComponent($value) : $value; + } + if ($item === '' || $item === '=') { + continue; + } + $data[] = $item; + } + + if (!$data) { + return ''; + } + + if ($sort) { + sort($data); + } + + return implode($separator, $data); + } + + /** + * @param string $query + * @return string + */ + public static function normalizeQueryString(string $query): string + { + return static::buildQueryString(self::parseQueryString($query), null, '&', true); + } + + public static function decodeComponent(string $component): string + { + return rawurldecode($component); + } + + public static function encodeComponent(string $component, ?array $skip = null): string + { + if (!$skip) { + return rawurlencode($component); + } + + $str = ''; + + foreach (UnicodeString::walkString($component) as [$cp, $chars]) { + if ($cp < 0x80) { + if ($cp === 0x2D || $cp === 0x2E || + $cp === 0x5F || $cp === 0x7E || + ($cp >= 0x41 && $cp <= 0x5A) || + ($cp >= 0x61 && $cp <= 0x7A) || + ($cp >= 0x30 && $cp <= 0x39) || + in_array($cp, $skip, true) + ) { + $str .= chr($cp); + } else { + $str .= '%' . strtoupper(dechex($cp)); + } + } else { + $i = 0; + while (isset($chars[$i])) { + $str .= '%' . strtoupper(dechex($chars[$i++])); + } + } + } + + return $str; + } + + public static function setSchemePort(string $scheme, ?int $port): void + { + $scheme = strtolower($scheme); + + if ($port === null) { + unset(self::$KNOWN_PORTS[$scheme]); + } else { + self::$KNOWN_PORTS[$scheme] = $port; + } + } + + public static function getSchemePort(string $scheme): ?int + { + return self::$KNOWN_PORTS[strtolower($scheme)] ?? null; + } + + protected static array $KNOWN_PORTS = [ + 'ftp' => 21, + 'ssh' => 22, + 'telnet' => 23, + 'smtp' => 25, + 'tftp' => 69, + 'http' => 80, + 'pop' => 110, + 'sftp' => 115, + 'imap' => 143, + 'irc' => 194, + 'ldap' => 389, + 'https' => 443, + 'ldaps' => 636, + 'telnets' => 992, + 'imaps' => 993, + 'ircs' => 994, + 'pops' => 995, + ]; +} \ No newline at end of file diff --git a/admin/vendor/opis/uri/src/UriTemplate.php b/admin/vendor/opis/uri/src/UriTemplate.php new file mode 100644 index 0000000..98c5b69 --- /dev/null +++ b/admin/vendor/opis/uri/src/UriTemplate.php @@ -0,0 +1,520 @@ +[a-zA-Z0-9\_\%\.]+)(?:(?\*)?|\:(?\d+))?$~'; + + /** @var string */ + protected const TEMPLATE_REGEX = <<<'REGEX' +~\{ +(?[+#./;&=,!@|\?])? +(? + (?:(?P>varspec),)* + (?(?: + [a-zA-Z0-9\_\%\.]+ + (?:\*|\:\d+)? + )) +) +\}~x +REGEX; + + /** @var array */ + protected const TEMPLATE_TABLE = [ + '' => [ + 'first' => '', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ], + '+' => [ + 'first' => '', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => true, + ], + '.' => [ + 'first' => '.', + 'sep' => '.', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ], + '/' => [ + 'first' => '/', + 'sep' => '/', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ], + ';' => [ + 'first' => ';', + 'sep' => ';', + 'named' => true, + 'ifemp' => '', + 'allow' => false, + ], + '?' => [ + 'first' => '?', + 'sep' => '&', + 'named' => true, + 'ifemp' => '=', + 'allow' => false, + ], + '&' => [ + 'first' => '&', + 'sep' => '&', + 'named' => true, + 'ifemp' => '=', + 'allow' => false, + ], + '#' => [ + 'first' => '#', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => true, + ], + ]; + + protected string $uri; + + /** @var bool|null|array */ + protected $parsed = false; + + /** + * UriTemplate constructor. + * @param string $uri_template + */ + public function __construct(string $uri_template) + { + $this->uri = $uri_template; + } + + /** + * @param array $vars + * @return string + */ + public function resolve(array $vars): string + { + if ($this->parsed === false) { + $this->parsed = $this->parse($this->uri); + } + if ($this->parsed === null || !$vars) { + return $this->uri; + } + + $data = ''; + $vars = $this->prepareVars($vars); + + foreach ($this->parsed as $item) { + if (!is_array($item)) { + $data .= $item; + continue; + } + + $data .= $this->parseTemplateExpression( + self::TEMPLATE_TABLE[$item['operator']], + $this->resolveVars($item['vars'], $vars) + ); + } + + return $data; + } + + /** + * @return bool + */ + public function hasPlaceholders(): bool + { + if ($this->parsed === false) { + $this->parse($this->uri); + } + + return $this->parsed !== null; + } + + /** + * @param string $uri + * @return array|null + */ + protected function parse(string $uri): ?array + { + $placeholders = null; + preg_match_all(self::TEMPLATE_REGEX, $uri, $placeholders, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); + + if (!$placeholders) { + return null; + } + + $dataIndex = -1; + $data = []; + + $hasVars = false; + $nextOffset = 0; + foreach ($placeholders as &$p) { + $offset = $p[0][1]; + if ($nextOffset < $offset) { + $data[] = substr($uri, $nextOffset, $offset - $nextOffset); + $dataIndex++; + } + $matched = $p[0][0]; + $nextOffset = $offset + strlen($matched); + + $operator = $p['operator'][0] ?? null; + if ($operator === null || !isset(self::TEMPLATE_TABLE[$operator])) { + if ($dataIndex >= 0 && is_string($data[$dataIndex])) { + $data[$dataIndex] .= $matched; + } else { + $data[] = $matched; + $dataIndex++; + } + continue; + } + + $varList = $p['varlist'][0] ?? ''; + $varList = $varList === '' ? [] : explode(',', $varList); + $p = null; + + $varData = []; + + foreach ($varList as $var) { + if (!preg_match(self::TEMPLATE_VARSPEC_REGEX, $var, $spec)) { + continue; + } + + $varData[] = [ + 'name' => $spec['varname'], + 'explode' => isset($spec['explode']) && $spec['explode'] === '*', + 'prefix' => isset($spec['prefix']) ? (int)$spec['prefix'] : 0, + ]; + + unset($var, $spec); + } + + if ($varData) { + $hasVars = true; + $data[] = [ + 'operator' => $operator, + 'vars' => $varData, + ]; + $dataIndex++; + } else { + if ($dataIndex >= 0 && is_string($data[$dataIndex])) { + $data[$dataIndex] .= $matched; + } else { + $data[] = $matched; + $dataIndex++; + } + } + + unset($varData, $varList, $operator); + } + + if (!$hasVars) { + return null; + } + + $matched = substr($uri, $nextOffset); + if ($matched !== false && $matched !== '') { + if ($dataIndex >= 0 && is_string($data[$dataIndex])) { + $data[$dataIndex] .= $matched; + } else { + $data[] = $matched; + } + } + + return $data; + } + + /** + * Convert assoc arrays to objects + * @param array $vars + * @return array + */ + protected function prepareVars(array $vars): array + { + foreach ($vars as &$value) { + if (is_scalar($value)) { + if (!is_string($value)) { + $value = (string)$value; + } + continue; + } + + if (!is_array($value)) { + continue; + } + + $len = count($value); + for ($i = 0; $i < $len; $i++) { + if (!array_key_exists($i, $value)) { + $value = (object)$value; + break; + } + } + } + + return $vars; + } + + /** + * @param array $vars + * @param array $data + * @return array + */ + protected function resolveVars(array $vars, array $data): array + { + $resolved = []; + + foreach ($vars as $info) { + $name = $info['name']; + + if (!isset($data[$name])) { + continue; + } + + $resolved[] = $info + ['value' => &$data[$name]]; + } + + return $resolved; + } + + /** + * @param array $table + * @param array $data + * @return string + */ + protected function parseTemplateExpression(array $table, array $data): string + { + $result = []; + foreach ($data as $var) { + $str = ""; + if (is_string($var['value'])) { + if ($table['named']) { + $str .= $var['name']; + if ($var['value'] === '') { + $str .= $table['ifemp']; + } else { + $str .= '='; + } + } + if ($var['prefix']) { + $str .= $this->encodeTemplateString(self::prefix($var['value'], $var['prefix']), $table['allow']); + } else { + $str .= $this->encodeTemplateString($var['value'], $table['allow']); + } + } elseif ($var['explode']) { + $list = []; + if ($table['named']) { + if (is_array($var['value'])) { + foreach ($var['value'] as $v) { + if (is_null($v) || !is_scalar($v)) { + continue; + } + $v = $this->encodeTemplateString((string)$v, $table['allow']); + if ($v === '') { + $list[] = $var['name'] . $table['ifemp']; + } else { + $list[] = $var['name'] . '=' . $v; + } + } + } elseif (is_object($var['value'])) { + foreach ($var['value'] as $prop => $v) { + if (is_null($v) || !is_scalar($v)) { + continue; + } + $v = $this->encodeTemplateString((string)$v, $table['allow']); + $prop = $this->encodeTemplateString((string)$prop, $table['allow']); + if ($v === '') { + $list[] = $prop . $table['ifemp']; + } else { + $list[] = $prop . '=' . $v; + } + } + } + } else { + if (is_array($var['value'])) { + foreach ($var['value'] as $v) { + if (is_null($v) || !is_scalar($v)) { + continue; + } + $list[] = $this->encodeTemplateString($v, $table['allow']); + } + } elseif (is_object($var['value'])) { + foreach ($var['value'] as $prop => $v) { + if (is_null($v) || !is_scalar($v)) { + continue; + } + $v = $this->encodeTemplateString((string)$v, $table['allow']); + $prop = $this->encodeTemplateString((string)$prop, $table['allow']); + $list[] = $prop . '=' . $v; + } + } + } + + if ($list) { + $str .= implode($table['sep'], $list); + } + unset($list); + } else { + if ($table['named']) { + $str .= $var['name']; + if ($var['value'] === '') { + $str .= $table['ifemp']; + } else { + $str .= '='; + } + } + $list = []; + if (is_array($var['value'])) { + foreach ($var['value'] as $v) { + $list[] = $this->encodeTemplateString($v, $table['allow']); + } + } elseif (is_object($var['value'])) { + foreach ($var['value'] as $prop => $v) { + $list[] = $this->encodeTemplateString((string)$prop, $table['allow']); + $list[] = $this->encodeTemplateString((string)$v, $table['allow']); + } + } + if ($list) { + $str .= implode(',', $list); + } + unset($list); + } + + if ($str !== '') { + $result[] = $str; + } + } + + if (!$result) { + return ''; + } + + $result = implode($table['sep'], $result); + + if ($result !== '') { + $result = $table['first'] . $result; + } + + return $result; + } + + /** + * @param string $data + * @param bool $reserved + * @return string + */ + protected function encodeTemplateString(string $data, bool $reserved): string + { + $skip = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; + + if ($reserved) { + $skip .= ':/?#[]@!$&\'()*+,;='; + } + + $result = ''; + $temp = ''; + for ($i = 0, $len = strlen($data); $i < $len; $i++) { + if (strpos($skip, $data[$i]) !== false) { + if ($temp !== '') { + $result .= Uri::encodeComponent($temp); + $temp = ''; + } + $result .= $data[$i]; + continue; + } + if ($reserved && $data[$i] === '%') { + if (isset($data[$i + 1]) && isset($data[$i + 2]) + && strpos('ABCDEF0123456789', $data[$i + 1]) !== false + && strpos('ABCDEF0123456789', $data[$i + 2]) !== false) { + if ($temp !== '') { + $result .= Uri::encodeComponent($temp); + } + $result .= '%' . $data[$i + 1] . $data[$i + 2]; + $i += 3; + continue; + } + } + $temp .= $data[$i]; + } + + if ($temp !== '') { + $result .= Uri::encodeComponent($temp); + } + + return $result; + } + + /** + * @return string + */ + public function value(): string + { + return $this->uri; + } + + public function __toString(): string + { + return $this->uri; + } + + /** + * @param string $uri + * @return bool + */ + public static function isTemplate(string $uri): bool + { + $open = substr_count($uri, '{'); + if ($open === 0) { + return false; + } + $close = substr_count($uri, '}'); + if ($open !== $close) { + return false; + } + + return (bool)preg_match(self::TEMPLATE_REGEX, $uri); + } + + /** + * @param string $str + * @param int $len + * @return string + */ + protected static function prefix(string $str, int $len): string + { + if ($len === 0) { + return ''; + } + + if ($len >= strlen($str)) { + // Prefix is longer than string length + return $str; + } + + return (string)UnicodeString::from($str)->substring(0, $len); + } +} \ No newline at end of file diff --git a/admin/vendor/php-http/curl-client/.github/workflows/Build-Test.yml b/admin/vendor/php-http/curl-client/.github/workflows/Build-Test.yml new file mode 100644 index 0000000..8995df3 --- /dev/null +++ b/admin/vendor/php-http/curl-client/.github/workflows/Build-Test.yml @@ -0,0 +1,50 @@ +name: Tests + +on: + push: + branches: + - '*.x' + pull_request: + +jobs: + tests: + if: "! contains(toJSON(github.event.commits.*.msg), 'skip') && ! contains(toJSON(github.event.commits.*.msg), 'ci')" #skip ci... + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest] + php-versions: ['8.1', '8.2', '8.3', '8.4', '8.5'] + include: + - php-versions: '8.1' + COMPOSER_FLAGS: '--prefer-stable --prefer-lowest' + PHPUNIT_FLAGS: '--coverage-clover build/coverage.xml' + + name: PHP ${{ matrix.php-versions }} - ${{ matrix.operating-system }} + + env: + extensions: curl json libxml dom + key: cache-v1 # can be any string, change to clear the extension cache. + + steps: + # Checks out a copy of your repository on the ubuntu machine + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP Action + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.extensions }} + coverage: xdebug + tools: 'composer:v2, pecl' + + - name: Install Composer dependencies + run: composer update ${{ matrix.COMPOSER_FLAGS }} --no-interaction + + - name: boot test server + run: vendor/bin/http_test_server > /dev/null 2>&1 & + + - name: Run tests + run: composer test diff --git a/admin/vendor/php-http/curl-client/.php_cs b/admin/vendor/php-http/curl-client/.php_cs new file mode 100644 index 0000000..febeee5 --- /dev/null +++ b/admin/vendor/php-http/curl-client/.php_cs @@ -0,0 +1,9 @@ +level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers([]) + ->finder( + Symfony\CS\Finder\DefaultFinder::create()->in(__DIR__ . '/src') + ) +; diff --git a/admin/vendor/php-http/curl-client/CHANGELOG.md b/admin/vendor/php-http/curl-client/CHANGELOG.md new file mode 100644 index 0000000..ccdb9b6 --- /dev/null +++ b/admin/vendor/php-http/curl-client/CHANGELOG.md @@ -0,0 +1,247 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 2.4.0 - 2025-12-09 + +- Added support for Symfony 8 +- Added support for PHP 8.5 +- Remove support for PHP < 8.1 + +## 2.3.3 - 2024-10-31 + +### Added + +- Added support for PHP 8.4 + +## 2.3.2 - 2024-03-03 + +### Fixed + +- Fixed running the tests with Guzzle PSR-7 and PSR-17 implementations. + +## 2.3.1 - 2023-11-03 + +### Added + +- Allow installation with Symfony 7. + +## 2.3.0 - 2023-04-28 + +### Added + +- Test with PHP 8.2 + +### Fixed + +- This client needs a PSR-17 factories implementation. Instead of requiring an implementation, + previous versions only required the interfaces which could lead to a non-functional installation. + Fixed by requiring `psr/http-factory-implementation`. + +## 2.2.1 - 2021-12-10 + +### Added + +- Symfony 6 support +- Tested with PHP 8.1 + +## 2.2.0 - 2020-12-14 + +### Added + +- PHP 8.0 support + +## 2.1.0 - 2019-12-27 + +### Added + +- Symfony 5 support + +## 2.0.0 - 2019-03-05 + +### Removed + +- HHVM support removed. + +### Changed + +- Minimal PHP version changed to 7.1. +- `Client::__construct` now expects PSR-17 factories instead of HTTPlug ones. + +### Added + +- #41: Support [PSR-17](https://www.php-fig.org/psr/psr-17/) and + [PSR-18](https://www.php-fig.org/psr/psr-18/). + + +## 1.7.1 - 2018-03-26 + +### Fixed + +- #36: Failure evaluating code: `is_resource($handle)` (string assertions are deprecated in PHP 7.2) + + +## 1.7 - 2017-02-09 + +### Changed + +- #30: Make sure we rewind streams + +## 1.6.2 - 2017-01-02 + +### Fixed + +- #29: Request not using CURLOPT_POSTFIELDS have content-length set to + +### Changed + +- Use binary mode to create response body stream. + + +## 1.6.1 - 2016-11-11 + +### Fixed + +- #27: ErrorPlugin and sendAsyncRequest() incompatibility + + +## 1.6 - 2016-09-12 + +### Changed + +- `Client::sendRequest` now throws `Http\Client\Exception\NetworkException` on network errors. +- `\UnexpectedValueException` replaced with `Http\Client\Exception\RequestException` in + `Client::sendRequest` and `Client::sendAsyncRequest` + + +## 1.5.1 - 2016-08-29 + +### Fixed + +- #26: Combining CurlClient with StopwatchPlugin causes Promise onRejected handler to never be + invoked. + + +## 1.5 - 2016-08-03 + +### Changed + +- Request body can be send with any method except GET, HEAD and TRACE. +- #25: Make discovery a hard dependency. + + +## 1.4.2 - 2016-06-14 + +### Added + +- #23: "php-http/async-client-implementation" added to "provide" section. + + +## 1.4.1 - 2016-05-30 + +### Fixed + +- #22: Cannot create the client using `HttpClientDiscovery`. + + +## 1.4 - 2016-03-30 + +### Changed + +- #20: Minimize memory usage when reading large response body. + + +## 1.3 - 2016-03-14 + +### Fixed + +- #18: Invalid "Expect" header. + +### Removed + +- #13: Remove HeaderParser. + + +## 1.2 - 2016-03-09 + +### Added + +- #16: Make sure discovery can find the curl client + +### Fixed + +- #15: "Out of memory" sending large files. + + +## 1.1.0 - 2016-01-29 + +### Changed + +- Switch to php-http/message 1.0. + + +## 1.0.0 - 2016-01-28 + +First stable release. + + +## 0.7.0 - 2016-01-26 + +### Changed + +- Migrate from `php-http/discovery` and `php-http/utils` to `php-http/message`. + +## 0.6.0 - 2016-01-12 + +### Changed + +- Root namespace changed from `Http\Curl` to `Http\Client\Curl`. +- Main client class name renamed from `CurlHttpClient` to `Client`. +- Minimum required [php-http/discovery](https://packagist.org/packages/php-http/discovery) + version changed to 0.5. + + +## 0.5.0 - 2015-12-18 + +### Changed + +- Compatibility with php-http/httplug 1.0 beta +- Switch to php-http/discovery 0.4 + + +## 0.4.0 - 2015-12-16 + +### Changed + +- Switch to php-http/message-factory 1.0 + + +## 0.3.1 - 2015-12-14 + +### Changed + +- Requirements fixed. + + +## 0.3.0 - 2015-11-24 + +### Changed + +- Use cURL constants as options keys. + + +## 0.2.0 - 2015-11-17 + +### Added + +- HttpAsyncClient support. + + +## 0.1.0 - 2015-11-11 + +### Added + +- Initial release diff --git a/admin/vendor/php-http/curl-client/LICENSE b/admin/vendor/php-http/curl-client/LICENSE new file mode 100644 index 0000000..8e2c4a0 --- /dev/null +++ b/admin/vendor/php-http/curl-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/php-http/curl-client/README.md b/admin/vendor/php-http/curl-client/README.md new file mode 100644 index 0000000..71ab2ea --- /dev/null +++ b/admin/vendor/php-http/curl-client/README.md @@ -0,0 +1,42 @@ +# Curl client for PHP HTTP + +[![Latest Version](https://img.shields.io/github/release/php-http/curl-client.svg?style=flat-square)](https://github.com/php-http/curl-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Tests](https://github.com/php-http/curl-client/actions/workflows/Build-Test.yml/badge.svg?branch=2.x)](https://github.com/php-http/curl-client/actions/workflows/Build-Test.yml) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/curl-client.svg?style=flat-square)](https://packagist.org/packages/php-http/curl-client) + +The cURL client use the cURL PHP extension which must be activated in your `php.ini`. + + +## Install + +Via Composer + +``` bash +$ composer require php-http/curl-client +``` + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/clients/curl-client.html). + +## Testing + +``` bash +$ composer test +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. + + +## Security + +If you discover any security related issues, please contact us at +[security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/admin/vendor/php-http/curl-client/composer.json b/admin/vendor/php-http/curl-client/composer.json new file mode 100644 index 0000000..8db713e --- /dev/null +++ b/admin/vendor/php-http/curl-client/composer.json @@ -0,0 +1,56 @@ +{ + "name": "php-http/curl-client", + "description": "PSR-18 and HTTPlug Async client with cURL", + "license": "MIT", + "keywords": [ + "curl", + "http", + "psr-18" + ], + "homepage": "http://php-http.org", + "authors": [{ + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + }], + "require": { + "php": "^8.1", + "ext-curl": "*", + "php-http/discovery": "^1.6", + "php-http/httplug": "^2.0", + "php-http/message": "^1.2", + "psr/http-client": "^1.0", + "psr/http-factory-implementation": "^1.0", + "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^2.0", + "php-http/client-integration-tests": "^4.0", + "phpunit/phpunit": "^9.6.17 || ^10.0 || ^11.0 || ^12.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.1" + }, + "autoload": { + "psr-4": { + "Http\\Client\\Curl\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Http\\Client\\Curl\\Tests\\": "tests/" + } + }, + "provide": { + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0", + "psr/http-client-implementation": "1.0" + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml" + }, + "config": { + "allow-plugins": { + "php-http/discovery": false + } + } +} diff --git a/admin/vendor/php-http/curl-client/puli.json b/admin/vendor/php-http/curl-client/puli.json new file mode 100644 index 0000000..b35768d --- /dev/null +++ b/admin/vendor/php-http/curl-client/puli.json @@ -0,0 +1,242 @@ +{ + "version": "1.0", + "name": "php-http/curl-client", + "bindings": { + "98239b8b-103b-4f47-94c7-4cba49a05a1f": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpAsyncClient" + }, + "a6a79968-2aa5-427c-bbe1-a581d9a48321": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpClient" + } + }, + "config": { + "bootstrap-file": "vendor/autoload.php" + }, + "packages": { + "clue/stream-filter": { + "install-path": "vendor/clue/stream-filter", + "installer": "composer", + "env": "dev" + }, + "doctrine/instantiator": { + "install-path": "vendor/doctrine/instantiator", + "installer": "composer", + "env": "dev" + }, + "guzzlehttp/psr7": { + "install-path": "vendor/guzzlehttp/psr7", + "installer": "composer", + "env": "dev" + }, + "justinrainbow/json-schema": { + "install-path": "vendor/justinrainbow/json-schema", + "installer": "composer", + "env": "dev" + }, + "paragonie/random_compat": { + "install-path": "vendor/paragonie/random_compat", + "installer": "composer", + "env": "dev" + }, + "php-http/adapter-integration-tests": { + "install-path": "vendor/php-http/adapter-integration-tests", + "installer": "composer", + "env": "dev" + }, + "php-http/discovery": { + "install-path": "vendor/php-http/discovery", + "installer": "composer", + "env": "dev" + }, + "php-http/httplug": { + "install-path": "vendor/php-http/httplug", + "installer": "composer" + }, + "php-http/message": { + "install-path": "vendor/php-http/message", + "installer": "composer", + "env": "dev" + }, + "php-http/message-factory": { + "install-path": "vendor/php-http/message-factory", + "installer": "composer" + }, + "php-http/promise": { + "install-path": "vendor/php-http/promise", + "installer": "composer" + }, + "phpdocumentor/reflection-docblock": { + "install-path": "vendor/phpdocumentor/reflection-docblock", + "installer": "composer", + "env": "dev" + }, + "phpspec/prophecy": { + "install-path": "vendor/phpspec/prophecy", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-code-coverage": { + "install-path": "vendor/phpunit/php-code-coverage", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-file-iterator": { + "install-path": "vendor/phpunit/php-file-iterator", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-text-template": { + "install-path": "vendor/phpunit/php-text-template", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-timer": { + "install-path": "vendor/phpunit/php-timer", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-token-stream": { + "install-path": "vendor/phpunit/php-token-stream", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit": { + "install-path": "vendor/phpunit/phpunit", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit-mock-objects": { + "install-path": "vendor/phpunit/phpunit-mock-objects", + "installer": "composer", + "env": "dev" + }, + "psr/http-message": { + "install-path": "vendor/psr/http-message", + "installer": "composer" + }, + "psr/log": { + "install-path": "vendor/psr/log", + "installer": "composer", + "env": "dev" + }, + "puli/composer-plugin": { + "install-path": "vendor/puli/composer-plugin", + "installer": "composer", + "env": "dev" + }, + "puli/discovery": { + "install-path": "vendor/puli/discovery", + "installer": "composer", + "env": "dev" + }, + "puli/repository": { + "install-path": "vendor/puli/repository", + "installer": "composer", + "env": "dev" + }, + "puli/url-generator": { + "install-path": "vendor/puli/url-generator", + "installer": "composer", + "env": "dev" + }, + "ramsey/uuid": { + "install-path": "vendor/ramsey/uuid", + "installer": "composer", + "env": "dev" + }, + "sebastian/comparator": { + "install-path": "vendor/sebastian/comparator", + "installer": "composer", + "env": "dev" + }, + "sebastian/diff": { + "install-path": "vendor/sebastian/diff", + "installer": "composer", + "env": "dev" + }, + "sebastian/environment": { + "install-path": "vendor/sebastian/environment", + "installer": "composer", + "env": "dev" + }, + "sebastian/exporter": { + "install-path": "vendor/sebastian/exporter", + "installer": "composer", + "env": "dev" + }, + "sebastian/global-state": { + "install-path": "vendor/sebastian/global-state", + "installer": "composer", + "env": "dev" + }, + "sebastian/recursion-context": { + "install-path": "vendor/sebastian/recursion-context", + "installer": "composer", + "env": "dev" + }, + "sebastian/version": { + "install-path": "vendor/sebastian/version", + "installer": "composer", + "env": "dev" + }, + "seld/jsonlint": { + "install-path": "vendor/seld/jsonlint", + "installer": "composer", + "env": "dev" + }, + "symfony/filesystem": { + "install-path": "vendor/symfony/filesystem", + "installer": "composer", + "env": "dev" + }, + "symfony/process": { + "install-path": "vendor/symfony/process", + "installer": "composer", + "env": "dev" + }, + "symfony/yaml": { + "install-path": "vendor/symfony/yaml", + "installer": "composer", + "env": "dev" + }, + "th3n3rd/cartesian-product": { + "install-path": "vendor/th3n3rd/cartesian-product", + "installer": "composer", + "env": "dev" + }, + "webmozart/assert": { + "install-path": "vendor/webmozart/assert", + "installer": "composer", + "env": "dev" + }, + "webmozart/expression": { + "install-path": "vendor/webmozart/expression", + "installer": "composer", + "env": "dev" + }, + "webmozart/glob": { + "install-path": "vendor/webmozart/glob", + "installer": "composer", + "env": "dev" + }, + "webmozart/json": { + "install-path": "vendor/webmozart/json", + "installer": "composer", + "env": "dev" + }, + "webmozart/path-util": { + "install-path": "vendor/webmozart/path-util", + "installer": "composer", + "env": "dev" + }, + "zendframework/zend-diactoros": { + "install-path": "vendor/zendframework/zend-diactoros", + "installer": "composer", + "env": "dev" + } + } +} diff --git a/admin/vendor/php-http/curl-client/src/Client.php b/admin/vendor/php-http/curl-client/src/Client.php new file mode 100644 index 0000000..d3fd6bd --- /dev/null +++ b/admin/vendor/php-http/curl-client/src/Client.php @@ -0,0 +1,398 @@ + + * @author Blake Williams + * + * @api + * + * @since 1.0 + */ +class Client implements HttpClient, HttpAsyncClient +{ + /** + * cURL options. + * + * @var array + */ + private $curlOptions; + + /** + * PSR-17 response factory. + * + * @var ResponseFactoryInterface + */ + private $responseFactory; + + /** + * PSR-17 stream factory. + * + * @var StreamFactoryInterface + */ + private $streamFactory; + + /** + * cURL synchronous requests handle. + * + * @var resource|\CurlHandle|null + */ + private $handle; + + /** + * Simultaneous requests runner. + * + * @var MultiRunner|null + */ + private $multiRunner; + + /** + * Create HTTP client. + * + * @param ResponseFactoryInterface|null $responseFactory PSR-17 HTTP response factory. + * @param StreamFactoryInterface|null $streamFactory PSR-17 HTTP stream factory. + * @param array $options cURL options + * {@link http://php.net/curl_setopt}. + * + * @throws NotFoundException If factory discovery failed. + * + * @since 2.0 Accepts PSR-17 factories instead of HTTPlug ones. + */ + public function __construct( + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + array $options = [] + ) { + $this->responseFactory = $responseFactory ?: Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); + $resolver = new OptionsResolver(); + $resolver->setDefaults( + [ + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_FOLLOWLOCATION => false + ] + ); + + // Our parsing will fail if this is set to true. + $resolver->setAllowedValues( + (string)CURLOPT_HEADER, + [false] + ); + + // Our parsing will fail if this is set to true. + $resolver->setAllowedValues( + (string)CURLOPT_RETURNTRANSFER, + [false] + ); + + // We do not know what everything curl supports and might support in the future. + // Make sure that we accept everything that is in the options. + $resolver->setDefined(array_keys($options)); + + $this->curlOptions = $resolver->resolve($options); + } + + /** + * Release resources if still active. + */ + public function __destruct() + { + if (is_resource($this->handle)) { + curl_close($this->handle); + } + } + + /** + * Sends a PSR-7 request and returns a PSR-7 response. + * + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If creating the body stream fails. + * @throws Exception\NetworkException In case of network problems. + * @throws Exception\RequestException On invalid request. + * + * @since 1.6 \UnexpectedValueException replaced with RequestException + * @since 1.6 Throw NetworkException on network errors + * @since 1.0 + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $responseBuilder = $this->createResponseBuilder(); + $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); + + if (is_resource($this->handle)) { + curl_reset($this->handle); + } else { + $this->handle = curl_init(); + } + + curl_setopt_array($this->handle, $requestOptions); + curl_exec($this->handle); + + $errno = curl_errno($this->handle); + switch ($errno) { + case CURLE_OK: + // All OK, no actions needed. + break; + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + case CURLE_OPERATION_TIMEOUTED: + case CURLE_SSL_CONNECT_ERROR: + throw new Exception\NetworkException(curl_error($this->handle), $request); + default: + throw new Exception\RequestException(curl_error($this->handle), $request); + } + + $response = $responseBuilder->getResponse(); + $response->getBody()->seek(0); + + return $response; + } + + /** + * Create builder to use for building response object. + * + * @return ResponseBuilder + */ + private function createResponseBuilder(): ResponseBuilder + { + $body = $this->streamFactory->createStreamFromFile('php://temp', 'w+b'); + + $response = $this->responseFactory + ->createResponse(200) + ->withBody($body); + + return new ResponseBuilder($response); + } + + /** + * Update cURL options for given request and hook in the response builder. + * + * @param RequestInterface $request Request on which to create options. + * @param ResponseBuilder $responseBuilder Builder to use for building response. + * + * @return array cURL options based on request. + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If can not read body. + * @throws Exception\RequestException On invalid request. + */ + private function prepareRequestOptions( + RequestInterface $request, + ResponseBuilder $responseBuilder + ): array { + $curlOptions = $this->curlOptions; + + try { + $curlOptions[CURLOPT_HTTP_VERSION] + = $this->getProtocolVersion($request->getProtocolVersion()); + } catch (\UnexpectedValueException $e) { + throw new Exception\RequestException($e->getMessage(), $request); + } + $curlOptions[CURLOPT_URL] = (string)$request->getUri(); + + $curlOptions = $this->addRequestBodyOptions($request, $curlOptions); + + $curlOptions[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $curlOptions); + + if ($request->getUri()->getUserInfo()) { + $curlOptions[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); + } + + $curlOptions[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) { + $str = trim($data); + if ('' !== $str) { + if (stripos($str, 'http/') === 0) { + $responseBuilder->setStatus($str)->getResponse(); + } else { + $responseBuilder->addHeader($str); + } + } + + return strlen($data); + }; + + $curlOptions[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) { + return $responseBuilder->getResponse()->getBody()->write($data); + }; + + return $curlOptions; + } + + /** + * Return cURL constant for specified HTTP version. + * + * @param string $requestVersion HTTP version ("1.0", "1.1" or "2.0"). + * + * @return int Respective CURL_HTTP_VERSION_x_x constant. + * + * @throws \UnexpectedValueException If unsupported version requested. + */ + private function getProtocolVersion(string $requestVersion): int + { + switch ($requestVersion) { + case '1.0': + return CURL_HTTP_VERSION_1_0; + case '1.1': + return CURL_HTTP_VERSION_1_1; + case '2.0': + if (defined('CURL_HTTP_VERSION_2_0')) { + return CURL_HTTP_VERSION_2_0; + } + throw new \UnexpectedValueException('libcurl 7.33 needed for HTTP 2.0 support'); + } + + return CURL_HTTP_VERSION_NONE; + } + + /** + * Add request body related cURL options. + * + * @param RequestInterface $request Request on which to create options. + * @param array $curlOptions Options created by prepareRequestOptions(). + * + * @return array cURL options based on request. + */ + private function addRequestBodyOptions(RequestInterface $request, array $curlOptions): array + { + /* + * Some HTTP methods cannot have payload: + * + * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or + * CURLOPT_POSTFIELDS. + * - HEAD — cURL treats HEAD as GET request with a same restrictions. + * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request. + */ + if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) { + $body = $request->getBody(); + $bodySize = $body->getSize(); + if ($bodySize !== 0) { + if ($body->isSeekable()) { + $body->rewind(); + } + + // Message has non empty body. + if (null === $bodySize || $bodySize > 1024 * 1024) { + // Avoid full loading large or unknown size body into memory + $curlOptions[CURLOPT_UPLOAD] = true; + if (null !== $bodySize) { + $curlOptions[CURLOPT_INFILESIZE] = $bodySize; + } + $curlOptions[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } else { + // Small body can be loaded into memory + $curlOptions[CURLOPT_POSTFIELDS] = (string)$body; + } + } + } + + if ($request->getMethod() === 'HEAD') { + // This will set HTTP method to "HEAD". + $curlOptions[CURLOPT_NOBODY] = true; + } elseif ($request->getMethod() !== 'GET') { + // GET is a default method. Other methods should be specified explicitly. + $curlOptions[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); + } + + return $curlOptions; + } + + /** + * Create headers array for CURLOPT_HTTPHEADER. + * + * @param RequestInterface $request Request on which to create headers. + * @param array $curlOptions Options created by prepareRequestOptions(). + * + * @return string[] + */ + private function createHeaders(RequestInterface $request, array $curlOptions): array + { + $curlHeaders = []; + $headers = $request->getHeaders(); + foreach ($headers as $name => $values) { + $header = strtolower($name); + if ('expect' === $header) { + // curl-client does not support "Expect-Continue", so dropping "expect" headers + continue; + } + if ('content-length' === $header) { + if (array_key_exists(CURLOPT_POSTFIELDS, $curlOptions)) { + // Small body content length can be calculated here. + $values = [strlen($curlOptions[CURLOPT_POSTFIELDS])]; + } elseif (!array_key_exists(CURLOPT_READFUNCTION, $curlOptions)) { + // Else if there is no body, forcing "Content-length" to 0 + $values = [0]; + } + } + foreach ($values as $value) { + $curlHeaders[] = $name . ': ' . $value; + } + } + /* + * curl-client does not support "Expect-Continue", but cURL adds "Expect" header by default. + * We can not suppress it, but we can set it to empty. + */ + $curlHeaders[] = 'Expect:'; + + return $curlHeaders; + } + + /** + * Sends a PSR-7 request in an asynchronous way. + * + * Exceptions related to processing the request are available from the returned Promise. + * + * @param RequestInterface $request + * + * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception. + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If creating the body stream fails. + * @throws Exception\RequestException On invalid request. + * + * @since 1.6 \UnexpectedValueException replaced with RequestException + * @since 1.0 + */ + public function sendAsyncRequest(RequestInterface $request) + { + if (!$this->multiRunner instanceof MultiRunner) { + $this->multiRunner = new MultiRunner(); + } + + $handle = curl_init(); + $responseBuilder = $this->createResponseBuilder(); + $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); + curl_setopt_array($handle, $requestOptions); + + $core = new PromiseCore($request, $handle, $responseBuilder); + $promise = new CurlPromise($core, $this->multiRunner); + $this->multiRunner->add($core); + + return $promise; + } +} diff --git a/admin/vendor/php-http/curl-client/src/CurlPromise.php b/admin/vendor/php-http/curl-client/src/CurlPromise.php new file mode 100644 index 0000000..d69494c --- /dev/null +++ b/admin/vendor/php-http/curl-client/src/CurlPromise.php @@ -0,0 +1,111 @@ + + */ +class CurlPromise implements Promise +{ + /** + * Shared promise core. + * + * @var PromiseCore + */ + private $core; + + /** + * Requests runner. + * + * @var MultiRunner + */ + private $runner; + + /** + * Create new promise. + * + * @param PromiseCore $core Shared promise core + * @param MultiRunner $runner Simultaneous requests runner + */ + public function __construct(PromiseCore $core, MultiRunner $runner) + { + $this->core = $core; + $this->runner = $runner; + } + + /** + * Add behavior for when the promise is resolved or rejected. + * + * If you do not care about one of the cases, you can set the corresponding callable to null + * The callback will be called when the response or exception arrived and never more than once. + * + * @param callable|null $onFulfilled Called when a response will be available + * @param callable|null $onRejected Called when an error happens. + * + * You must always return the Response in the interface or throw an Exception + * + * @return Promise Always returns a new promise which is resolved with value of the executed + * callback (onFulfilled / onRejected) + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + { + if ($onFulfilled) { + $this->core->addOnFulfilled($onFulfilled); + } + if ($onRejected) { + $this->core->addOnRejected($onRejected); + } + + return new self($this->core, $this->runner); + } + + /** + * Get the state of the promise, one of PENDING, FULFILLED or REJECTED. + * + * @return string + */ + public function getState() + { + return $this->core->getState(); + } + + /** + * Wait for the promise to be fulfilled or rejected. + * + * When this method returns, the request has been resolved and the appropriate callable has terminated. + * + * When called with the unwrap option + * + * @param bool $unwrap Whether to return resolved value / throw reason or not + * + * @return \Psr\Http\Message\ResponseInterface|null Resolved value, null if $unwrap is set to false + * + * @throws \Http\Client\Exception The rejection reason + */ + public function wait($unwrap = true) + { + $this->runner->wait($this->core); + + if ($unwrap) { + if ($this->core->getState() === self::REJECTED) { + throw $this->core->getException(); + } + + return $this->core->getResponse(); + } + + return null; + } +} diff --git a/admin/vendor/php-http/curl-client/src/MultiRunner.php b/admin/vendor/php-http/curl-client/src/MultiRunner.php new file mode 100644 index 0000000..13f43a9 --- /dev/null +++ b/admin/vendor/php-http/curl-client/src/MultiRunner.php @@ -0,0 +1,131 @@ + + */ +class MultiRunner +{ + /** + * cURL multi handle. + * + * @var resource|null + */ + private $multiHandle; + + /** + * Awaiting cores. + * + * @var PromiseCore[] + */ + private $cores = []; + + /** + * Release resources if still active. + */ + public function __destruct() + { + if (is_resource($this->multiHandle)) { + curl_multi_close($this->multiHandle); + } + } + + /** + * Add promise to runner. + * + * @param PromiseCore $core + */ + public function add(PromiseCore $core): void + { + foreach ($this->cores as $existed) { + if ($existed === $core) { + return; + } + } + + $this->cores[] = $core; + + if (null === $this->multiHandle) { + $this->multiHandle = curl_multi_init(); + } + curl_multi_add_handle($this->multiHandle, $core->getHandle()); + } + + /** + * Remove promise from runner. + * + * @param PromiseCore $core + */ + public function remove(PromiseCore $core): void + { + foreach ($this->cores as $index => $existed) { + if ($existed === $core) { + curl_multi_remove_handle($this->multiHandle, $core->getHandle()); + unset($this->cores[$index]); + + return; + } + } + } + + /** + * Wait for request(s) to be completed. + * + * @param PromiseCore|null $targetCore + */ + public function wait(?PromiseCore $targetCore = null): void + { + do { + $status = curl_multi_exec($this->multiHandle, $active); + $info = curl_multi_info_read($this->multiHandle); + if (false !== $info) { + $core = $this->findCoreByHandle($info['handle']); + + if (null === $core) { + // We have no promise for this handle. Drop it. + curl_multi_remove_handle($this->multiHandle, $info['handle']); + continue; + } + + if (CURLE_OK === $info['result']) { + $core->fulfill(); + } else { + $error = curl_error($core->getHandle()); + $core->reject(new RequestException($error, $core->getRequest())); + } + $this->remove($core); + + // This is a promise we are waited for. So exiting wait(). + if ($core === $targetCore) { + return; + } + } + } while ($status === CURLM_CALL_MULTI_PERFORM || $active); + } + + /** + * Find core by handle. + * + * @param resource $handle + * + * @return PromiseCore|null + */ + private function findCoreByHandle($handle): ?PromiseCore + { + foreach ($this->cores as $core) { + if ($core->getHandle() === $handle) { + return $core; + } + } + + return null; + } +} diff --git a/admin/vendor/php-http/curl-client/src/PromiseCore.php b/admin/vendor/php-http/curl-client/src/PromiseCore.php new file mode 100644 index 0000000..b58776f --- /dev/null +++ b/admin/vendor/php-http/curl-client/src/PromiseCore.php @@ -0,0 +1,252 @@ + + */ +class PromiseCore +{ + /** + * HTTP request. + * + * @var RequestInterface + */ + private $request; + + /** + * cURL handle. + * + * @var resource + */ + private $handle; + + /** + * Response builder. + * + * @var ResponseBuilder + */ + private $responseBuilder; + + /** + * Promise state. + * + * @var string + */ + private $state; + + /** + * Exception. + * + * @var Exception|null + */ + private $exception = null; + + /** + * Functions to call when a response will be available. + * + * @var callable[] + */ + private $onFulfilled = []; + + /** + * Functions to call when an error happens. + * + * @var callable[] + */ + private $onRejected = []; + + /** + * Create shared core. + * + * @param RequestInterface $request HTTP request. + * @param resource|\CurlHandle $handle cURL handle. + * @param ResponseBuilder $responseBuilder Response builder. + * + * @throws \InvalidArgumentException If $handle is not a cURL resource. + */ + public function __construct( + RequestInterface $request, + $handle, + ResponseBuilder $responseBuilder + ) { + if (PHP_MAJOR_VERSION === 7) { + if (!is_resource($handle)) { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s given', + gettype($handle) + ) + ); + } elseif (get_resource_type($handle) !== 'curl') { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s resource given', + get_resource_type($handle) + ) + ); + } + } + + if (PHP_MAJOR_VERSION > 7 && !$handle instanceof \CurlHandle) { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s given', + get_debug_type($handle) + ) + ); + } + + $this->request = $request; + $this->handle = $handle; + $this->responseBuilder = $responseBuilder; + $this->state = Promise::PENDING; + } + + /** + * Add on fulfilled callback. + * + * @param callable $callback + */ + public function addOnFulfilled(callable $callback): void + { + if ($this->getState() === Promise::PENDING) { + $this->onFulfilled[] = $callback; + } elseif ($this->getState() === Promise::FULFILLED) { + $response = call_user_func($callback, $this->responseBuilder->getResponse()); + if ($response instanceof ResponseInterface) { + $this->responseBuilder->setResponse($response); + } + } + } + + /** + * Add on rejected callback. + * + * @param callable $callback + */ + public function addOnRejected(callable $callback): void + { + if ($this->getState() === Promise::PENDING) { + $this->onRejected[] = $callback; + } elseif ($this->getState() === Promise::REJECTED) { + $this->exception = call_user_func($callback, $this->exception); + } + } + + /** + * Return cURL handle. + * + * @return resource|\CurlHandle + */ + public function getHandle() + { + return $this->handle; + } + + /** + * Get the state of the promise, one of PENDING, FULFILLED or REJECTED. + * + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * Return request. + * + * @return RequestInterface + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Return the value of the promise (fulfilled). + * + * @return ResponseInterface Response object only when the Promise is fulfilled + */ + public function getResponse(): ResponseInterface + { + return $this->responseBuilder->getResponse(); + } + + /** + * Get the reason why the promise was rejected. + * + * If the exception is an instance of Http\Client\Exception\HttpException it will contain + * the response object with the status code and the http reason. + * + * @return \Throwable Exception Object only when the Promise is rejected + * + * @throws \LogicException When the promise is not rejected + */ + public function getException(): \Throwable + { + if (null === $this->exception) { + throw new \LogicException('Promise is not rejected'); + } + + return $this->exception; + } + + /** + * Fulfill promise. + */ + public function fulfill(): void + { + $this->state = Promise::FULFILLED; + $response = $this->responseBuilder->getResponse(); + try { + $response->getBody()->seek(0); + } catch (\RuntimeException $e) { + $exception = new Exception\TransferException($e->getMessage(), $e->getCode(), $e); + $this->reject($exception); + + return; + } + + while (count($this->onFulfilled) > 0) { + $callback = array_shift($this->onFulfilled); + $response = call_user_func($callback, $response); + } + + if ($response instanceof ResponseInterface) { + $this->responseBuilder->setResponse($response); + } + } + + /** + * Reject promise. + * + * @param Exception $exception Reject reason + */ + public function reject(Exception $exception): void + { + $this->exception = $exception; + $this->state = Promise::REJECTED; + + while (count($this->onRejected) > 0) { + $callback = array_shift($this->onRejected); + try { + $exception = call_user_func($callback, $this->exception); + $this->exception = $exception; + } catch (Exception $exception) { + $this->exception = $exception; + } + } + } +} diff --git a/admin/vendor/php-http/curl-client/src/ResponseBuilder.php b/admin/vendor/php-http/curl-client/src/ResponseBuilder.php new file mode 100644 index 0000000..c7250e7 --- /dev/null +++ b/admin/vendor/php-http/curl-client/src/ResponseBuilder.php @@ -0,0 +1,24 @@ +response = $response; + } +} diff --git a/admin/vendor/php-http/discovery/.php-cs-fixer.php b/admin/vendor/php-http/discovery/.php-cs-fixer.php new file mode 100644 index 0000000..4256eee --- /dev/null +++ b/admin/vendor/php-http/discovery/.php-cs-fixer.php @@ -0,0 +1,17 @@ +in(__DIR__.'/src') + ->name('*.php') +; + +$config = (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'trailing_comma_in_multiline' => false, // for methods this is incompatible with PHP 7 + ]) + ->setFinder($finder) +; + +return $config; diff --git a/admin/vendor/php-http/discovery/CHANGELOG.md b/admin/vendor/php-http/discovery/CHANGELOG.md new file mode 100644 index 0000000..a74e5cd --- /dev/null +++ b/admin/vendor/php-http/discovery/CHANGELOG.md @@ -0,0 +1,392 @@ +# Change Log + +## 1.20.0 - 2024-10-02 + +- [#268](https://github.com/php-http/discovery/pull/268) - Do not attempt to update lock file when it is not existing. +- [#267](https://github.com/php-http/discovery/pull/267) - Test with PHP 8.3 and 8.4 +- [#266](https://github.com/php-http/discovery/pull/266) - If wrapped client implements factories, use those instead of discovering new factories. + +## 1.19.4 - 2024-03-29 + +- [#264](https://github.com/php-http/discovery/pull/264) - Do not report a general conflict with `sebastian/comparator` but make sure we install the correct version for our tests. + +## 1.19.3 - 2024-03-28 + +- [#261](https://github.com/php-http/discovery/pull/261) - explicitly mark nullable parameters as nullable (avoid deprecation in PHP 8.4) + +## 1.19.2 - 2023-11-30 + +- [#253](https://github.com/php-http/discovery/pull/253) - Symfony 7 dropped the deprecated PHP-HTTP `HttpClient` interface from their HTTP client, do not discover the version 7 client when looking for the old interface. + +## 1.19.1 - 2023-07-11 + +- [#250](https://github.com/php-http/discovery/pull/250) - Fix: Buzz client instantiation using deprecated Message Factory Discovery, use PSR-17 factory discovery instead. + +## 1.19.0 - 2023-06-19 + +- [#249](https://github.com/php-http/discovery/pull/249) - Have composer plugin correctly install Symfony http client when nothing explicitly requires psr 18 resp. httplug. +- [#241](https://github.com/php-http/discovery/pull/241) - Support discovering PSR-17 factories of `httpsoft/http-message` package + +## 1.18.1 - 2023-05-17 + +- [#242](https://github.com/php-http/discovery/pull/242) - Better exception message when no legacy php-http message factories can be built. Also needs php-http/message-factory package and they are deprecated in favor of PSR-17 anyways. + +## 1.18.0 - 2023-05-03 + +- [#235](https://github.com/php-http/discovery/pull/235) - Deprecate HttpClientDiscovery, use Psr18ClientDiscovery instead +- [#238](https://github.com/php-http/discovery/pull/238) - Skip requiring php-http/message-factory when installing symfony/http-client 6.3+ +- [#239](https://github.com/php-http/discovery/pull/239) - Skip auto-installing when the root package's extra.discovery is enough + +## 1.17.0 - 2023-04-26 + +- [#230](https://github.com/php-http/discovery/pull/230) - Add Psr18Client to make it straightforward to use PSR-18 +- [#232](https://github.com/php-http/discovery/pull/232) - Allow pinning the preferred implementations in composer.json +- [#233](https://github.com/php-http/discovery/pull/233) - Fix Psr17Factory::createServerRequestFromGlobals() when uploaded files have been moved + +## 1.16.0 - 2023-04-26 + +- [#225](https://github.com/php-http/discovery/pull/225) - Remove support for the abandoned Zend Diactoros which has been replaced with Laminas Diactoros; marked the zend library as conflict in composer.json to avoid confusion +- [#227](https://github.com/php-http/discovery/pull/227) - Fix handling requests with nested files + +## 1.15.3 - 2023-03-31 + +- [#224](https://github.com/php-http/discovery/pull/224) - Fix regression with Magento classloader + +## 1.15.2 - 2023-02-11 + +- [#219](https://github.com/php-http/discovery/pull/219) - Fix handling of replaced packages + +## 1.15.1 - 2023-02-10 + +- [#214](https://github.com/php-http/discovery/pull/214) - Fix resolving deps for psr/http-message-implementation +- [#216](https://github.com/php-http/discovery/pull/216) - Fix keeping platform requirements when rebooting composer +- [#217](https://github.com/php-http/discovery/pull/217) - Set extra.plugin-optional composer flag + +## 1.15.0 - 2023-02-09 + +- [#209](https://github.com/php-http/discovery/pull/209) - Add generic `Psr17Factory` class +- [#208](https://github.com/php-http/discovery/pull/208) - Add composer plugin to auto-install missing implementations. + When libraries require an http implementation but no packages providing that implementation is installed in the application, the plugin will automatically install one. + This is only done for libraries that directly require php-http/discovery to avoid unexpected dependency installation. + +## 1.14.3 - 2022-07-11 + +- [#207](https://github.com/php-http/discovery/pull/207) - Updates Exception to extend Throwable solving static analysis errors for consumers + +## 1.14.2 - 2022-05-25 + +- [#202](https://github.com/php-http/discovery/pull/202) - Avoid error when the Symfony PSR-18 client exists but its dependencies are not installed + +## 1.14.1 - 2021-09-18 + +- [#199](https://github.com/php-http/discovery/pull/199) - Fixes message factory discovery for `laminas-diactoros ^2.7` + +## 1.14.0 - 2021-06-21 + +- Deprecate puli as it has been unmaintained for a long time and is not compatible with composer 2 https://github.com/php-http/discovery/pull/195 + +## 1.13.0 - 2020-11-27 + +- Support discovering PSR-17 factories of `slim/psr7` package https://github.com/php-http/discovery/pull/192 + +## 1.12.0 - 2020-09-22 + +- Support discovering HttpClient of `php-http/guzzle7-adapter` https://github.com/php-http/discovery/pull/189 + +## 1.11.0 - 2020-09-22 + +- Use correct method name to find Uri Factory in PSR17 https://github.com/php-http/discovery/pull/181 + +## 1.10.0 - 2020-09-04 + +- Discover PSR-18 implementation of phalcon + +## 1.9.1 - 2020-07-13 + +### Fixed + +- Support PHP 7.4 and 8.0 + +## 1.9.0 - 2020-07-02 + +### Added + +- Support discovering PSR-18 factories of `guzzlehttp/guzzle` 7+ + +## 1.8.0 - 2020-06-14 + +### Added + +- Support discovering PSR-17 factories of `guzzlehttp/psr7` package +- Support discovering PSR-17 factories of `laminas/laminas-diactoros` package +- `ClassDiscovery::getStrategies()` to retrieve the list of current strategies. + +### Fixed + +- Ignore exception during discovery when Symfony HttplugClient checks if HTTPlug is available. + +## 1.7.4 - 2020-01-03 + +### Fixed + +- Improve conditions on Symfony's async HTTPlug client. + +## 1.7.3 - 2019-12-27 + +### Fixed + +- Enough conditions to only use Symfony HTTP client if all needed components are available. + +## 1.7.2 - 2019-12-27 + +### Fixed + +- Allow a condition to specify an interface and not just classes. + +## 1.7.1 - 2019-12-26 + +### Fixed + +- Better conditions to see if Symfony's HTTP clients are available. + +## 1.7.0 - 2019-06-30 + +### Added + +- Dropped support for PHP < 7.1 +- Support for `symfony/http-client` + +## 1.6.1 - 2019-02-23 + +### Fixed + +- MockClientStrategy also provides the mock client when requesting an async client + +## 1.6.0 - 2019-01-23 + +### Added + +- Support for PSR-17 factories +- Support for PSR-18 clients + +## 1.5.2 - 2018-12-31 + +Corrected mistakes in 1.5.1. The different between 1.5.2 and 1.5.0 is that +we removed some PHP 7 code. + +https://github.com/php-http/discovery/compare/1.5.0...1.5.2 + +## 1.5.1 - 2018-12-31 + +This version added new features by mistake. These are reverted in 1.5.2. + +Do not use 1.5.1. + +### Fixed + +- Removed PHP 7 code + +## 1.5.0 - 2018-12-30 + +### Added + +- Support for `nyholm/psr7` version 1.0. +- `ClassDiscovery::safeClassExists` which will help Magento users. +- Support for HTTPlug 2.0 +- Support for Buzz 1.0 +- Better error message when nothing found by introducing a new exception: `NoCandidateFoundException`. + +### Fixed + +- Fixed condition evaluation, it should stop after first invalid condition. + +## 1.4.0 - 2018-02-06 + +### Added + +- Discovery support for nyholm/psr7 + +## 1.3.0 - 2017-08-03 + +### Added + +- Discovery support for CakePHP adapter +- Discovery support for Zend adapter +- Discovery support for Artax adapter + +## 1.2.1 - 2017-03-02 + +### Fixed + +- Fixed minor issue with `MockClientStrategy`, also added more tests. + +## 1.2.0 - 2017-02-12 + +### Added + +- MockClientStrategy class. + +## 1.1.1 - 2016-11-27 + +### Changed + +- Made exception messages clearer. `StrategyUnavailableException` is no longer the previous exception to `DiscoveryFailedException`. +- `CommonClassesStrategy` is using `self` instead of `static`. Using `static` makes no sense when `CommonClassesStrategy` is final. + +## 1.1.0 - 2016-10-20 + +### Added + +- Discovery support for Slim Framework factories + +## 1.0.0 - 2016-07-18 + +### Added + +- Added back `Http\Discovery\NotFoundException` to preserve BC with 0.8 version. You may upgrade from 0.8.x and 0.9.x to 1.0.0 without any BC breaks. +- Added interface `Http\Discovery\Exception` which is implemented by all our exceptions + +### Changed + +- Puli strategy renamed to Puli Beta strategy to prevent incompatibility with a future Puli stable + +### Deprecated + +- For BC reasons, the old `Http\Discovery\NotFoundException` (extending the new exception) will be thrown until version 2.0 + + +## 0.9.1 - 2016-06-28 + +### Changed + +- Dropping PHP 5.4 support because we use the ::class constant. + + +## 0.9.0 - 2016-06-25 + +### Added + +- Discovery strategies to find classes + +### Changed + +- [Puli](http://puli.io) made optional +- Improved exceptions +- **[BC] `NotFoundException` moved to `Http\Discovery\Exception\NotFoundException`** + + +## 0.8.0 - 2016-02-11 + +### Changed + +- Puli composer plugin must be installed separately + + +## 0.7.0 - 2016-01-15 + +### Added + +- Temporary puli.phar (Beta 10) executable + +### Changed + +- Updated HTTPlug dependencies +- Updated Puli dependencies +- Local configuration to make tests passing + +### Removed + +- Puli CLI dependency + + +## 0.6.4 - 2016-01-07 + +### Fixed + +- Puli [not working](https://twitter.com/PuliPHP/status/685132540588507137) with the latest json-schema + + +## 0.6.3 - 2016-01-04 + +### Changed + +- Adjust Puli dependencies + + +## 0.6.2 - 2016-01-04 + +### Changed + +- Make Puli CLI a requirement + + +## 0.6.1 - 2016-01-03 + +### Changed + +- More flexible Puli requirement + + +## 0.6.0 - 2015-12-30 + +### Changed + +- Use [Puli](http://puli.io) for discovery +- Improved exception messages + + +## 0.5.0 - 2015-12-25 + +### Changed + +- Updated message factory dependency (php-http/message) + + +## 0.4.0 - 2015-12-17 + +### Added + +- Array condition evaluation in the Class Discovery + +### Removed + +- Message factories (moved to php-http/utils) + + +## 0.3.0 - 2015-11-18 + +### Added + +- HTTP Async Client Discovery +- Stream factories + +### Changed + +- Discoveries and Factories are final +- Message and Uri factories have the type in their names +- Diactoros Message factory uses Stream factory internally + +### Fixed + +- Improved docblocks for API documentation generation + + +## 0.2.0 - 2015-10-31 + +### Changed + +- Renamed AdapterDiscovery to ClientDiscovery + + +## 0.1.1 - 2015-06-13 + +### Fixed + +- Bad HTTP Adapter class name for Guzzle 5 + + +## 0.1.0 - 2015-06-12 + +### Added + +- Initial release diff --git a/admin/vendor/php-http/discovery/LICENSE b/admin/vendor/php-http/discovery/LICENSE new file mode 100644 index 0000000..4558d6f --- /dev/null +++ b/admin/vendor/php-http/discovery/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/php-http/discovery/README.md b/admin/vendor/php-http/discovery/README.md new file mode 100644 index 0000000..2b5e8e5 --- /dev/null +++ b/admin/vendor/php-http/discovery/README.md @@ -0,0 +1,122 @@ +# HTTPlug Discovery + +[![Latest Version](https://img.shields.io/github/release/php-http/discovery.svg?style=flat-square)](https://github.com/php-http/discovery/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Tests](https://github.com/php-http/discovery/actions/workflows/ci.yml/badge.svg?branch=1.x)](https://github.com/php-http/discovery/actions/workflows/ci.yml?query=branch%3A1.x) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/discovery.svg?style=flat-square)](https://packagist.org/packages/php-http/discovery) + +**This library provides auto-discovery and auto-installation of well-known PSR-17, PSR-18 and HTTPlug implementations.** + + +## Install + +Via Composer + +``` bash +composer require php-http/discovery +``` + + +## Usage as a library author + +Please see the [official documentation](http://php-http.readthedocs.org/en/latest/discovery.html). + +If your library/SDK needs a PSR-18 client, here is a quick example. + +First, you need to install a PSR-18 client and a PSR-17 factory implementations. +This should be done only for dev dependencies as you don't want to force a +specific implementation on your users: + +```bash +composer require --dev symfony/http-client +composer require --dev nyholm/psr7 +``` + +Then, you can disable the Composer plugin embeded in `php-http/discovery` +because you just installed the dev dependencies you need for testing: + +```bash +composer config allow-plugins.php-http/discovery false +``` + +Finally, you need to require `php-http/discovery` and the generic implementations +that your library is going to need: + +```bash +composer require 'php-http/discovery:^1.17' +composer require 'psr/http-client-implementation:*' +composer require 'psr/http-factory-implementation:*' +``` + +Now, you're ready to make an HTTP request: + +```php +use Http\Discovery\Psr18Client; + +$client = new Psr18Client(); + +$request = $client->createRequest('GET', 'https://example.com'); +$response = $client->sendRequest($request); +``` + +Internally, this code will use whatever PSR-7, PSR-17 and PSR-18 implementations +that your users have installed. + + +## Usage as a library user + +If you use a library/SDK that requires `php-http/discovery`, you can configure +the auto-discovery mechanism to use a specific implementation when many are +available in your project. + +For example, if you have both `nyholm/psr7` and `guzzlehttp/guzzle` in your +project, you can tell `php-http/discovery` to use `guzzlehttp/guzzle` instead of +`nyholm/psr7` by running the following command: + +```bash +composer config extra.discovery.psr/http-factory-implementation GuzzleHttp\\Psr7\\HttpFactory +``` + +This will update your `composer.json` file to add the following configuration: + +```json +{ + "extra": { + "discovery": { + "psr/http-factory-implementation": "GuzzleHttp\\Psr7\\HttpFactory" + } + } +} +``` + +Don't forget to run `composer install` to apply the changes, and ensure that +the composer plugin is enabled: + +```bash +composer config allow-plugins.php-http/discovery true +composer install +``` + + +## Testing + +``` bash +composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/admin/vendor/php-http/discovery/composer.json b/admin/vendor/php-http/discovery/composer.json new file mode 100644 index 0000000..9d718bb --- /dev/null +++ b/admin/vendor/php-http/discovery/composer.json @@ -0,0 +1,64 @@ +{ + "name": "php-http/discovery", + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "type": "composer-plugin", + "license": "MIT", + "keywords": ["http", "discovery", "client", "adapter", "message", "factory", "psr7", "psr17"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require": { + "php": "^7.1 || ^8.0", + "composer-plugin-api": "^1.0|^2.0" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1", + "sebastian/comparator": "^3.0.5 || ^4.0.8" + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Discovery\\": "spec/" + } + }, + "scripts": { + "test": [ + "vendor/bin/phpspec run", + "vendor/bin/simple-phpunit --group NothingInstalled" + ], + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "prefer-stable": true, + "minimum-stability": "beta" +} diff --git a/admin/vendor/php-http/discovery/src/ClassDiscovery.php b/admin/vendor/php-http/discovery/src/ClassDiscovery.php new file mode 100644 index 0000000..5ea4697 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/ClassDiscovery.php @@ -0,0 +1,255 @@ + + * @author Márk Sági-Kazár + * @author Tobias Nyholm + */ +abstract class ClassDiscovery +{ + /** + * A list of strategies to find classes. + * + * @var DiscoveryStrategy[] + */ + private static $strategies = [ + Strategy\GeneratedDiscoveryStrategy::class, + Strategy\CommonClassesStrategy::class, + Strategy\CommonPsr17ClassesStrategy::class, + Strategy\PuliBetaStrategy::class, + ]; + + private static $deprecatedStrategies = [ + Strategy\PuliBetaStrategy::class => true, + ]; + + /** + * Discovery cache to make the second time we use discovery faster. + * + * @var array + */ + private static $cache = []; + + /** + * Finds a class. + * + * @param string $type + * + * @return string|\Closure + * + * @throws DiscoveryFailedException + */ + protected static function findOneByType($type) + { + // Look in the cache + if (null !== ($class = self::getFromCache($type))) { + return $class; + } + + static $skipStrategy; + $skipStrategy ?? $skipStrategy = self::safeClassExists(Strategy\GeneratedDiscoveryStrategy::class) ? false : Strategy\GeneratedDiscoveryStrategy::class; + + $exceptions = []; + foreach (self::$strategies as $strategy) { + if ($skipStrategy === $strategy) { + continue; + } + + try { + $candidates = $strategy::getCandidates($type); + } catch (StrategyUnavailableException $e) { + if (!isset(self::$deprecatedStrategies[$strategy])) { + $exceptions[] = $e; + } + + continue; + } + + foreach ($candidates as $candidate) { + if (isset($candidate['condition'])) { + if (!self::evaluateCondition($candidate['condition'])) { + continue; + } + } + + // save the result for later use + self::storeInCache($type, $candidate); + + return $candidate['class']; + } + + $exceptions[] = new NoCandidateFoundException($strategy, $candidates); + } + + throw DiscoveryFailedException::create($exceptions); + } + + /** + * Get a value from cache. + * + * @param string $type + * + * @return string|null + */ + private static function getFromCache($type) + { + if (!isset(self::$cache[$type])) { + return; + } + + $candidate = self::$cache[$type]; + if (isset($candidate['condition'])) { + if (!self::evaluateCondition($candidate['condition'])) { + return; + } + } + + return $candidate['class']; + } + + /** + * Store a value in cache. + * + * @param string $type + * @param string $class + */ + private static function storeInCache($type, $class) + { + self::$cache[$type] = $class; + } + + /** + * Set new strategies and clear the cache. + * + * @param string[] $strategies list of fully qualified class names that implement DiscoveryStrategy + */ + public static function setStrategies(array $strategies) + { + self::$strategies = $strategies; + self::clearCache(); + } + + /** + * Returns the currently configured discovery strategies as fully qualified class names. + * + * @return string[] + */ + public static function getStrategies(): iterable + { + return self::$strategies; + } + + /** + * Append a strategy at the end of the strategy queue. + * + * @param string $strategy Fully qualified class name of a DiscoveryStrategy + */ + public static function appendStrategy($strategy) + { + self::$strategies[] = $strategy; + self::clearCache(); + } + + /** + * Prepend a strategy at the beginning of the strategy queue. + * + * @param string $strategy Fully qualified class name to a DiscoveryStrategy + */ + public static function prependStrategy($strategy) + { + array_unshift(self::$strategies, $strategy); + self::clearCache(); + } + + public static function clearCache() + { + self::$cache = []; + } + + /** + * Evaluates conditions to boolean. + * + * @return bool + */ + protected static function evaluateCondition($condition) + { + if (is_string($condition)) { + // Should be extended for functions, extensions??? + return self::safeClassExists($condition); + } + if (is_callable($condition)) { + return (bool) $condition(); + } + if (is_bool($condition)) { + return $condition; + } + if (is_array($condition)) { + foreach ($condition as $c) { + if (false === static::evaluateCondition($c)) { + // Immediately stop execution if the condition is false + return false; + } + } + + return true; + } + + return false; + } + + /** + * Get an instance of the $class. + * + * @param string|\Closure $class a FQCN of a class or a closure that instantiate the class + * + * @return object + * + * @throws ClassInstantiationFailedException + */ + protected static function instantiateClass($class) + { + try { + if (is_string($class)) { + return new $class(); + } + + if (is_callable($class)) { + return $class(); + } + } catch (\Exception $e) { + throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e); + } + + throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string'); + } + + /** + * We need a "safe" version of PHP's "class_exists" because Magento has a bug + * (or they call it a "feature"). Magento is throwing an exception if you do class_exists() + * on a class that ends with "Factory" and if that file does not exits. + * + * This function catches all potential exceptions and makes sure to always return a boolean. + * + * @param string $class + * + * @return bool + */ + public static function safeClassExists($class) + { + try { + return class_exists($class) || interface_exists($class); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/admin/vendor/php-http/discovery/src/Composer/Plugin.php b/admin/vendor/php-http/discovery/src/Composer/Plugin.php new file mode 100644 index 0000000..9339f8b --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Composer/Plugin.php @@ -0,0 +1,474 @@ + + * + * @internal + */ +class Plugin implements PluginInterface, EventSubscriberInterface +{ + /** + * Describes, for every supported virtual implementation, which packages + * provide said implementation and which extra dependencies each package + * requires to provide the implementation. + */ + private const PROVIDE_RULES = [ + 'php-http/async-client-implementation' => [ + 'symfony/http-client:>=6.3' => ['guzzlehttp/promises', 'psr/http-factory-implementation', 'php-http/httplug'], + 'symfony/http-client' => ['guzzlehttp/promises', 'php-http/message-factory', 'psr/http-factory-implementation', 'php-http/httplug'], + 'php-http/guzzle7-adapter' => [], + 'php-http/guzzle6-adapter' => [], + 'php-http/curl-client' => [], + 'php-http/react-adapter' => [], + ], + 'php-http/client-implementation' => [ + 'symfony/http-client:>=6.3' => ['psr/http-factory-implementation', 'php-http/httplug'], + 'symfony/http-client' => ['php-http/message-factory', 'psr/http-factory-implementation', 'php-http/httplug'], + 'php-http/guzzle7-adapter' => [], + 'php-http/guzzle6-adapter' => [], + 'php-http/cakephp-adapter' => [], + 'php-http/curl-client' => [], + 'php-http/react-adapter' => [], + 'php-http/buzz-adapter' => [], + 'php-http/artax-adapter' => [], + 'kriswallsmith/buzz:^1' => [], + ], + 'psr/http-client-implementation' => [ + 'symfony/http-client' => ['psr/http-factory-implementation', 'psr/http-client'], + 'guzzlehttp/guzzle' => [], + 'kriswallsmith/buzz:^1' => [], + ], + 'psr/http-message-implementation' => [ + 'php-http/discovery' => ['psr/http-factory-implementation'], + ], + 'psr/http-factory-implementation' => [ + 'nyholm/psr7' => [], + 'guzzlehttp/psr7:>=2' => [], + 'slim/psr7' => [], + 'laminas/laminas-diactoros' => [], + 'phalcon/cphalcon:^4' => [], + 'http-interop/http-factory-guzzle' => [], + 'http-interop/http-factory-diactoros' => [], + 'http-interop/http-factory-slim' => [], + 'httpsoft/http-message' => [], + ], + ]; + + /** + * Describes which package should be preferred on the left side + * depending on which one is already installed on the right side. + */ + private const STICKYNESS_RULES = [ + 'symfony/http-client' => 'symfony/framework-bundle', + 'php-http/guzzle7-adapter' => 'guzzlehttp/guzzle:^7', + 'php-http/guzzle6-adapter' => 'guzzlehttp/guzzle:^6', + 'php-http/guzzle5-adapter' => 'guzzlehttp/guzzle:^5', + 'php-http/cakephp-adapter' => 'cakephp/cakephp', + 'php-http/react-adapter' => 'react/event-loop', + 'php-http/buzz-adapter' => 'kriswallsmith/buzz:^0.15.1', + 'php-http/artax-adapter' => 'amphp/artax:^3', + 'http-interop/http-factory-guzzle' => 'guzzlehttp/psr7:^1', + 'http-interop/http-factory-slim' => 'slim/slim:^3', + ]; + + private const INTERFACE_MAP = [ + 'php-http/async-client-implementation' => [ + 'Http\Client\HttpAsyncClient', + ], + 'php-http/client-implementation' => [ + 'Http\Client\HttpClient', + ], + 'psr/http-client-implementation' => [ + 'Psr\Http\Client\ClientInterface', + ], + 'psr/http-factory-implementation' => [ + 'Psr\Http\Message\RequestFactoryInterface', + 'Psr\Http\Message\ResponseFactoryInterface', + 'Psr\Http\Message\ServerRequestFactoryInterface', + 'Psr\Http\Message\StreamFactoryInterface', + 'Psr\Http\Message\UploadedFileFactoryInterface', + 'Psr\Http\Message\UriFactoryInterface', + ], + ]; + + public static function getSubscribedEvents(): array + { + return [ + ScriptEvents::PRE_AUTOLOAD_DUMP => 'preAutoloadDump', + ScriptEvents::POST_UPDATE_CMD => 'postUpdate', + ]; + } + + public function activate(Composer $composer, IOInterface $io): void + { + } + + public function deactivate(Composer $composer, IOInterface $io) + { + } + + public function uninstall(Composer $composer, IOInterface $io) + { + } + + public function postUpdate(Event $event) + { + $composer = $event->getComposer(); + $repo = $composer->getRepositoryManager()->getLocalRepository(); + $requires = [ + $composer->getPackage()->getRequires(), + $composer->getPackage()->getDevRequires(), + ]; + $pinnedAbstractions = []; + $pinned = $composer->getPackage()->getExtra()['discovery'] ?? []; + foreach (self::INTERFACE_MAP as $abstraction => $interfaces) { + foreach (isset($pinned[$abstraction]) ? [] : $interfaces as $interface) { + if (!isset($pinned[$interface])) { + continue 2; + } + } + $pinnedAbstractions[$abstraction] = true; + } + + $missingRequires = $this->getMissingRequires($repo, $requires, 'project' === $composer->getPackage()->getType(), $pinnedAbstractions); + $missingRequires = [ + 'require' => array_fill_keys(array_merge([], ...array_values($missingRequires[0])), '*'), + 'require-dev' => array_fill_keys(array_merge([], ...array_values($missingRequires[1])), '*'), + 'remove' => array_fill_keys(array_merge([], ...array_values($missingRequires[2])), '*'), + ]; + + if (!$missingRequires = array_filter($missingRequires)) { + return; + } + + $composerJsonContents = file_get_contents(Factory::getComposerFile()); + $this->updateComposerJson($missingRequires, $composer->getConfig()->get('sort-packages')); + + $installer = null; + // Find the composer installer, hack borrowed from symfony/flex + foreach (debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT) as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Installer) { + $installer = $trace['object']; + break; + } + } + + if (!$installer) { + return; + } + + $event->stopPropagation(); + + $dispatcher = $composer->getEventDispatcher(); + $disableScripts = !method_exists($dispatcher, 'setRunScripts') || !((array) $dispatcher)["\0*\0runScripts"]; + $composer = Factory::create($event->getIO(), null, false, $disableScripts); + + /** @var Installer $installer */ + $installer = clone $installer; + if (method_exists($installer, 'setAudit')) { + $trace['object']->setAudit(false); + } + // we need a clone of the installer to preserve its configuration state but with our own service objects + $installer->__construct( + $event->getIO(), + $composer->getConfig(), + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $composer->getEventDispatcher(), + $composer->getAutoloadGenerator() + ); + if (method_exists($installer, 'setPlatformRequirementFilter')) { + $installer->setPlatformRequirementFilter(((array) $trace['object'])["\0*\0platformRequirementFilter"]); + } + + if (0 !== $installer->run()) { + file_put_contents(Factory::getComposerFile(), $composerJsonContents); + + return; + } + + $versionSelector = new VersionSelector(ClassDiscovery::safeClassExists(RepositorySet::class) ? new RepositorySet() : new Pool()); + $updateComposerJson = false; + + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) { + foreach (['require', 'require-dev'] as $key) { + if (!isset($missingRequires[$key][$package->getName()])) { + continue; + } + $updateComposerJson = true; + $missingRequires[$key][$package->getName()] = $versionSelector->findRecommendedRequireVersion($package); + } + } + + if ($updateComposerJson) { + $this->updateComposerJson($missingRequires, $composer->getConfig()->get('sort-packages')); + $this->updateComposerLock($composer, $event->getIO()); + } + } + + public function getMissingRequires(InstalledRepositoryInterface $repo, array $requires, bool $isProject, array $pinnedAbstractions): array + { + $allPackages = []; + $devPackages = method_exists($repo, 'getDevPackageNames') ? array_fill_keys($repo->getDevPackageNames(), true) : []; + + // One must require "php-http/discovery" + // to opt-in for auto-installation of virtual package implementations + if (!isset($requires[0]['php-http/discovery'])) { + $requires = [[], []]; + } + + foreach ($repo->getPackages() as $package) { + $allPackages[$package->getName()] = true; + + if (1 < \count($names = $package->getNames(false))) { + $allPackages += array_fill_keys($names, false); + + if (isset($devPackages[$package->getName()])) { + $devPackages += $names; + } + } + + if (isset($package->getRequires()['php-http/discovery'])) { + $requires[(int) isset($devPackages[$package->getName()])] += $package->getRequires(); + } + } + + $missingRequires = [[], [], []]; + $versionParser = new VersionParser(); + + if (ClassDiscovery::safeClassExists(\Phalcon\Http\Message\RequestFactory::class, false)) { + $missingRequires[0]['psr/http-factory-implementation'] = []; + $missingRequires[1]['psr/http-factory-implementation'] = []; + } + + foreach ($requires as $dev => $rules) { + $abstractions = []; + $rules = array_intersect_key(self::PROVIDE_RULES, $rules); + + while ($rules) { + $abstraction = key($rules); + + if (isset($pinnedAbstractions[$abstraction])) { + unset($rules[$abstraction]); + continue; + } + + $abstractions[] = $abstraction; + + foreach (array_shift($rules) as $candidate => $deps) { + [$candidate, $version] = explode(':', $candidate, 2) + [1 => null]; + + if (!isset($allPackages[$candidate])) { + continue; + } + if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) { + continue; + } + if ($isProject && !$dev && isset($devPackages[$candidate])) { + $missingRequires[0][$abstraction] = [$candidate]; + $missingRequires[2][$abstraction] = [$candidate]; + } else { + $missingRequires[$dev][$abstraction] = []; + } + + foreach ($deps as $dep) { + if (isset(self::PROVIDE_RULES[$dep])) { + $rules[$dep] = self::PROVIDE_RULES[$dep]; + } elseif (!isset($allPackages[$dep])) { + $missingRequires[$dev][$abstraction][] = $dep; + } elseif ($isProject && !$dev && isset($devPackages[$dep])) { + $missingRequires[0][$abstraction][] = $dep; + $missingRequires[2][$abstraction][] = $dep; + } + } + break; + } + } + + while ($abstractions) { + $abstraction = array_shift($abstractions); + + if (isset($missingRequires[$dev][$abstraction])) { + continue; + } + $candidates = self::PROVIDE_RULES[$abstraction]; + + foreach ($candidates as $candidate => $deps) { + [$candidate, $version] = explode(':', $candidate, 2) + [1 => null]; + + if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) { + continue; + } + if (isset($allPackages[$candidate]) && (!$isProject || $dev || !isset($devPackages[$candidate]))) { + continue 2; + } + } + + foreach (array_intersect_key(self::STICKYNESS_RULES, $candidates) as $candidate => $stickyRule) { + [$stickyName, $stickyVersion] = explode(':', $stickyRule, 2) + [1 => null]; + if (!isset($allPackages[$stickyName]) || ($isProject && !$dev && isset($devPackages[$stickyName]))) { + continue; + } + if (null !== $stickyVersion && !$repo->findPackage($stickyName, $versionParser->parseConstraints($stickyVersion))) { + continue; + } + + $candidates = [$candidate => $candidates[$candidate]]; + break; + } + + $dep = key($candidates); + [$dep] = explode(':', $dep, 2); + $missingRequires[$dev][$abstraction] = [$dep]; + + if ($isProject && !$dev && isset($devPackages[$dep])) { + $missingRequires[2][$abstraction][] = $dep; + } + } + } + + $missingRequires[1] = array_diff_key($missingRequires[1], $missingRequires[0]); + + return $missingRequires; + } + + public function preAutoloadDump(Event $event) + { + $filesystem = new Filesystem(); + // Double realpath() on purpose, see https://bugs.php.net/72738 + $vendorDir = $filesystem->normalizePath(realpath(realpath($event->getComposer()->getConfig()->get('vendor-dir')))); + $filesystem->ensureDirectoryExists($vendorDir.'/composer'); + $pinned = $event->getComposer()->getPackage()->getExtra()['discovery'] ?? []; + $candidates = []; + + $allInterfaces = array_merge(...array_values(self::INTERFACE_MAP)); + foreach ($pinned as $abstraction => $class) { + if (isset(self::INTERFACE_MAP[$abstraction])) { + $interfaces = self::INTERFACE_MAP[$abstraction]; + } elseif (false !== $k = array_search($abstraction, $allInterfaces, true)) { + $interfaces = [$allInterfaces[$k]]; + } else { + throw new \UnexpectedValueException(sprintf('Invalid "extra.discovery" pinned in composer.json: "%s" is not one of ["%s"].', $abstraction, implode('", "', array_keys(self::INTERFACE_MAP)))); + } + + foreach ($interfaces as $interface) { + $candidates[] = sprintf("case %s: return [['class' => %s]];\n", var_export($interface, true), var_export($class, true)); + } + } + + $file = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php'; + + if (!$candidates) { + if (file_exists($file)) { + unlink($file); + } + + return; + } + + $candidates = implode(' ', $candidates); + $code = <<getComposer()->getPackage(); + $autoload = $rootPackage->getAutoload(); + $autoload['classmap'][] = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php'; + $rootPackage->setAutoload($autoload); + } + + private function updateComposerJson(array $missingRequires, bool $sortPackages) + { + $file = Factory::getComposerFile(); + $contents = file_get_contents($file); + + $manipulator = new JsonManipulator($contents); + + foreach ($missingRequires as $key => $packages) { + foreach ($packages as $package => $constraint) { + if ('remove' === $key) { + $manipulator->removeSubNode('require-dev', $package); + } else { + $manipulator->addLink($key, $package, $constraint, $sortPackages); + } + } + } + + file_put_contents($file, $manipulator->getContents()); + } + + private function updateComposerLock(Composer $composer, IOInterface $io) + { + if (false === $composer->getConfig()->get('lock')) { + return; + } + + $lock = substr(Factory::getComposerFile(), 0, -4).'lock'; + $composerJson = file_get_contents(Factory::getComposerFile()); + $lockFile = new JsonFile($lock, null, $io); + $locker = ClassDiscovery::safeClassExists(RepositorySet::class) + ? new Locker($io, $lockFile, $composer->getInstallationManager(), $composerJson) + : new Locker($io, $lockFile, $composer->getRepositoryManager(), $composer->getInstallationManager(), $composerJson); + + if (!$locker->isLocked()) { + return; + } + + $lockData = $locker->getLockData(); + $lockData['content-hash'] = Locker::getContentHash($composerJson); + $lockFile->write($lockData); + } +} diff --git a/admin/vendor/php-http/discovery/src/Exception.php b/admin/vendor/php-http/discovery/src/Exception.php new file mode 100644 index 0000000..0fa8c76 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception.php @@ -0,0 +1,12 @@ + + */ +interface Exception extends \Throwable +{ +} diff --git a/admin/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php b/admin/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php new file mode 100644 index 0000000..e95bf5d --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php @@ -0,0 +1,14 @@ + + */ +final class ClassInstantiationFailedException extends \RuntimeException implements Exception +{ +} diff --git a/admin/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php b/admin/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php new file mode 100644 index 0000000..304b727 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php @@ -0,0 +1,51 @@ + + */ +final class DiscoveryFailedException extends \Exception implements Exception +{ + /** + * @var \Exception[] + */ + private $exceptions; + + /** + * @param string $message + * @param \Exception[] $exceptions + */ + public function __construct($message, array $exceptions = []) + { + $this->exceptions = $exceptions; + + parent::__construct($message); + } + + /** + * @param \Exception[] $exceptions + */ + public static function create($exceptions) + { + $message = 'Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors'; + foreach ($exceptions as $e) { + $message .= "\n - ".$e->getMessage(); + } + $message .= "\n\n"; + + return new self($message, $exceptions); + } + + /** + * @return \Exception[] + */ + public function getExceptions() + { + return $this->exceptions; + } +} diff --git a/admin/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php b/admin/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php new file mode 100644 index 0000000..32f65db --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php @@ -0,0 +1,47 @@ + + */ +final class NoCandidateFoundException extends \Exception implements Exception +{ + /** + * @param string $strategy + */ + public function __construct($strategy, array $candidates) + { + $classes = array_map( + function ($a) { + return $a['class']; + }, + $candidates + ); + + $message = sprintf( + 'No valid candidate found using strategy "%s". We tested the following candidates: %s.', + $strategy, + implode(', ', array_map([$this, 'stringify'], $classes)) + ); + + parent::__construct($message); + } + + private function stringify($mixed) + { + if (is_string($mixed)) { + return $mixed; + } + + if (is_array($mixed) && 2 === count($mixed)) { + return sprintf('%s::%s', $this->stringify($mixed[0]), $mixed[1]); + } + + return is_object($mixed) ? get_class($mixed) : gettype($mixed); + } +} diff --git a/admin/vendor/php-http/discovery/src/Exception/NotFoundException.php b/admin/vendor/php-http/discovery/src/Exception/NotFoundException.php new file mode 100644 index 0000000..ef8b9c5 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/NotFoundException.php @@ -0,0 +1,16 @@ + + */ +/* final */ class NotFoundException extends \RuntimeException implements Exception +{ +} diff --git a/admin/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php b/admin/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php new file mode 100644 index 0000000..a6ade73 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php @@ -0,0 +1,12 @@ + + */ +final class PuliUnavailableException extends StrategyUnavailableException +{ +} diff --git a/admin/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php b/admin/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php new file mode 100644 index 0000000..89ecf35 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php @@ -0,0 +1,15 @@ + + */ +class StrategyUnavailableException extends \RuntimeException implements Exception +{ +} diff --git a/admin/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php b/admin/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php new file mode 100644 index 0000000..a0c4d5b --- /dev/null +++ b/admin/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php @@ -0,0 +1,32 @@ + + */ +final class HttpAsyncClientDiscovery extends ClassDiscovery +{ + /** + * Finds an HTTP Async Client. + * + * @return HttpAsyncClient + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $asyncClient = static::findOneByType(HttpAsyncClient::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException('No HTTPlug async clients found. Make sure to install a package providing "php-http/async-client-implementation". Example: "php-http/guzzle6-adapter".', 0, $e); + } + + return static::instantiateClass($asyncClient); + } +} diff --git a/admin/vendor/php-http/discovery/src/HttpClientDiscovery.php b/admin/vendor/php-http/discovery/src/HttpClientDiscovery.php new file mode 100644 index 0000000..2501e5b --- /dev/null +++ b/admin/vendor/php-http/discovery/src/HttpClientDiscovery.php @@ -0,0 +1,34 @@ + + * + * @deprecated This will be removed in 2.0. Consider using Psr18ClientDiscovery. + */ +final class HttpClientDiscovery extends ClassDiscovery +{ + /** + * Finds an HTTP Client. + * + * @return HttpClient + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $client = static::findOneByType(HttpClient::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException('No HTTPlug clients found. Make sure to install a package providing "php-http/client-implementation". Example: "php-http/guzzle6-adapter".', 0, $e); + } + + return static::instantiateClass($client); + } +} diff --git a/admin/vendor/php-http/discovery/src/MessageFactoryDiscovery.php b/admin/vendor/php-http/discovery/src/MessageFactoryDiscovery.php new file mode 100644 index 0000000..4ae104a --- /dev/null +++ b/admin/vendor/php-http/discovery/src/MessageFactoryDiscovery.php @@ -0,0 +1,34 @@ + + * + * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery. + */ +final class MessageFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a Message Factory. + * + * @return MessageFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $messageFactory = static::findOneByType(MessageFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException('No php-http message factories found. Note that the php-http message factories are deprecated in favor of the PSR-17 message factories. To use the legacy Guzzle, Diactoros or Slim Framework factories of php-http, install php-http/message and php-http/message-factory and the chosen message implementation.', 0, $e); + } + + return static::instantiateClass($messageFactory); + } +} diff --git a/admin/vendor/php-http/discovery/src/NotFoundException.php b/admin/vendor/php-http/discovery/src/NotFoundException.php new file mode 100644 index 0000000..559afac --- /dev/null +++ b/admin/vendor/php-http/discovery/src/NotFoundException.php @@ -0,0 +1,16 @@ + + * + * @deprecated since since version 1.0, and will be removed in 2.0. Use {@link \Http\Discovery\Exception\NotFoundException} instead. + */ +final class NotFoundException extends RealNotFoundException +{ +} diff --git a/admin/vendor/php-http/discovery/src/Psr17Factory.php b/admin/vendor/php-http/discovery/src/Psr17Factory.php new file mode 100644 index 0000000..f8fbfd1 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Psr17Factory.php @@ -0,0 +1,303 @@ + + * Copyright (c) 2015 Michael Dowling + * Copyright (c) 2015 Márk Sági-Kazár + * Copyright (c) 2015 Graham Campbell + * Copyright (c) 2016 Tobias Schultze + * Copyright (c) 2016 George Mponos + * Copyright (c) 2016-2018 Tobias Nyholm + * + * @author Nicolas Grekas + */ +class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface +{ + private $requestFactory; + private $responseFactory; + private $serverRequestFactory; + private $streamFactory; + private $uploadedFileFactory; + private $uriFactory; + + public function __construct( + ?RequestFactoryInterface $requestFactory = null, + ?ResponseFactoryInterface $responseFactory = null, + ?ServerRequestFactoryInterface $serverRequestFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?UploadedFileFactoryInterface $uploadedFileFactory = null, + ?UriFactoryInterface $uriFactory = null + ) { + $this->requestFactory = $requestFactory; + $this->responseFactory = $responseFactory; + $this->serverRequestFactory = $serverRequestFactory; + $this->streamFactory = $streamFactory; + $this->uploadedFileFactory = $uploadedFileFactory; + $this->uriFactory = $uriFactory; + + $this->setFactory($requestFactory); + $this->setFactory($responseFactory); + $this->setFactory($serverRequestFactory); + $this->setFactory($streamFactory); + $this->setFactory($uploadedFileFactory); + $this->setFactory($uriFactory); + } + + /** + * @param UriInterface|string $uri + */ + public function createRequest(string $method, $uri): RequestInterface + { + $factory = $this->requestFactory ?? $this->setFactory(Psr17FactoryDiscovery::findRequestFactory()); + + return $factory->createRequest(...\func_get_args()); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + $factory = $this->responseFactory ?? $this->setFactory(Psr17FactoryDiscovery::findResponseFactory()); + + return $factory->createResponse(...\func_get_args()); + } + + /** + * @param UriInterface|string $uri + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + $factory = $this->serverRequestFactory ?? $this->setFactory(Psr17FactoryDiscovery::findServerRequestFactory()); + + return $factory->createServerRequest(...\func_get_args()); + } + + public function createServerRequestFromGlobals(?array $server = null, ?array $get = null, ?array $post = null, ?array $cookie = null, ?array $files = null, ?StreamInterface $body = null): ServerRequestInterface + { + $server = $server ?? $_SERVER; + $request = $this->createServerRequest($server['REQUEST_METHOD'] ?? 'GET', $this->createUriFromGlobals($server), $server); + + return $this->buildServerRequestFromGlobals($request, $server, $files ?? $_FILES) + ->withQueryParams($get ?? $_GET) + ->withParsedBody($post ?? $_POST) + ->withCookieParams($cookie ?? $_COOKIE) + ->withBody($body ?? $this->createStreamFromFile('php://input', 'r+')); + } + + public function createStream(string $content = ''): StreamInterface + { + $factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); + + return $factory->createStream($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + $factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); + + return $factory->createStreamFromFile($filename, $mode); + } + + /** + * @param resource $resource + */ + public function createStreamFromResource($resource): StreamInterface + { + $factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); + + return $factory->createStreamFromResource($resource); + } + + public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface + { + $factory = $this->uploadedFileFactory ?? $this->setFactory(Psr17FactoryDiscovery::findUploadedFileFactory()); + + return $factory->createUploadedFile(...\func_get_args()); + } + + public function createUri(string $uri = ''): UriInterface + { + $factory = $this->uriFactory ?? $this->setFactory(Psr17FactoryDiscovery::findUriFactory()); + + return $factory->createUri(...\func_get_args()); + } + + public function createUriFromGlobals(?array $server = null): UriInterface + { + return $this->buildUriFromGlobals($this->createUri(''), $server ?? $_SERVER); + } + + private function setFactory($factory) + { + if (!$this->requestFactory && $factory instanceof RequestFactoryInterface) { + $this->requestFactory = $factory; + } + if (!$this->responseFactory && $factory instanceof ResponseFactoryInterface) { + $this->responseFactory = $factory; + } + if (!$this->serverRequestFactory && $factory instanceof ServerRequestFactoryInterface) { + $this->serverRequestFactory = $factory; + } + if (!$this->streamFactory && $factory instanceof StreamFactoryInterface) { + $this->streamFactory = $factory; + } + if (!$this->uploadedFileFactory && $factory instanceof UploadedFileFactoryInterface) { + $this->uploadedFileFactory = $factory; + } + if (!$this->uriFactory && $factory instanceof UriFactoryInterface) { + $this->uriFactory = $factory; + } + + return $factory; + } + + private function buildServerRequestFromGlobals(ServerRequestInterface $request, array $server, array $files): ServerRequestInterface + { + $request = $request + ->withProtocolVersion(isset($server['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1') + ->withUploadedFiles($this->normalizeFiles($files)); + + $headers = []; + foreach ($server as $k => $v) { + if (0 === strpos($k, 'HTTP_')) { + $k = substr($k, 5); + } elseif (!\in_array($k, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + continue; + } + $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))); + + $headers[$k] = $v; + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $headers['Authorization'] = 'Basic '.base64_encode($_SERVER['PHP_AUTH_USER'].':'.($_SERVER['PHP_AUTH_PW'] ?? '')); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + foreach ($headers as $k => $v) { + try { + $request = $request->withHeader($k, $v); + } catch (\InvalidArgumentException $e) { + // ignore invalid headers + } + } + + return $request; + } + + private function buildUriFromGlobals(UriInterface $uri, array $server): UriInterface + { + $uri = $uri->withScheme(!empty($server['HTTPS']) && 'off' !== strtolower($server['HTTPS']) ? 'https' : 'http'); + + $hasPort = false; + if (isset($server['HTTP_HOST'])) { + $parts = parse_url('http://'.$server['HTTP_HOST']); + + $uri = $uri->withHost($parts['host'] ?? 'localhost'); + + if ($parts['port'] ?? false) { + $hasPort = true; + $uri = $uri->withPort($parts['port']); + } + } else { + $uri = $uri->withHost($server['SERVER_NAME'] ?? $server['SERVER_ADDR'] ?? 'localhost'); + } + + if (!$hasPort && isset($server['SERVER_PORT'])) { + $uri = $uri->withPort($server['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($server['REQUEST_URI'])) { + $requestUriParts = explode('?', $server['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($server['QUERY_STRING'])) { + $uri = $uri->withQuery($server['QUERY_STRING']); + } + + return $uri; + } + + private function normalizeFiles(array $files): array + { + foreach ($files as $k => $v) { + if ($v instanceof UploadedFileInterface) { + continue; + } + if (!\is_array($v)) { + unset($files[$k]); + } elseif (!isset($v['tmp_name'])) { + $files[$k] = $this->normalizeFiles($v); + } else { + $files[$k] = $this->createUploadedFileFromSpec($v); + } + } + + return $files; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private function createUploadedFileFromSpec(array $value) + { + if (!is_array($tmpName = $value['tmp_name'])) { + $file = is_file($tmpName) ? $this->createStreamFromFile($tmpName, 'r') : $this->createStream(); + + return $this->createUploadedFile($file, $value['size'], $value['error'], $value['name'], $value['type']); + } + + foreach ($tmpName as $k => $v) { + $tmpName[$k] = $this->createUploadedFileFromSpec([ + 'tmp_name' => $v, + 'size' => $value['size'][$k] ?? null, + 'error' => $value['error'][$k] ?? null, + 'name' => $value['name'][$k] ?? null, + 'type' => $value['type'][$k] ?? null, + ]); + } + + return $tmpName; + } +} diff --git a/admin/vendor/php-http/discovery/src/Psr17FactoryDiscovery.php b/admin/vendor/php-http/discovery/src/Psr17FactoryDiscovery.php new file mode 100644 index 0000000..e4348b4 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Psr17FactoryDiscovery.php @@ -0,0 +1,137 @@ + + */ +final class Psr17FactoryDiscovery extends ClassDiscovery +{ + private static function createException($type, Exception $e) + { + return new RealNotFoundException( + 'No PSR-17 '.$type.' found. Install a package from this list: https://packagist.org/providers/psr/http-factory-implementation', + 0, + $e + ); + } + + /** + * @return RequestFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findRequestFactory() + { + try { + $messageFactory = static::findOneByType(RequestFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('request factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return ResponseFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findResponseFactory() + { + try { + $messageFactory = static::findOneByType(ResponseFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('response factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return ServerRequestFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findServerRequestFactory() + { + try { + $messageFactory = static::findOneByType(ServerRequestFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('server request factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return StreamFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findStreamFactory() + { + try { + $messageFactory = static::findOneByType(StreamFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('stream factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return UploadedFileFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findUploadedFileFactory() + { + try { + $messageFactory = static::findOneByType(UploadedFileFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('uploaded file factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return UriFactoryInterface + * + * @throws RealNotFoundException + */ + public static function findUriFactory() + { + try { + $messageFactory = static::findOneByType(UriFactoryInterface::class); + } catch (DiscoveryFailedException $e) { + throw self::createException('url factory', $e); + } + + return static::instantiateClass($messageFactory); + } + + /** + * @return UriFactoryInterface + * + * @throws RealNotFoundException + * + * @deprecated This will be removed in 2.0. Consider using the findUriFactory() method. + */ + public static function findUrlFactory() + { + return static::findUriFactory(); + } +} diff --git a/admin/vendor/php-http/discovery/src/Psr18Client.php b/admin/vendor/php-http/discovery/src/Psr18Client.php new file mode 100644 index 0000000..48df783 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Psr18Client.php @@ -0,0 +1,52 @@ + + */ +class Psr18Client extends Psr17Factory implements ClientInterface +{ + private $client; + + public function __construct( + ?ClientInterface $client = null, + ?RequestFactoryInterface $requestFactory = null, + ?ResponseFactoryInterface $responseFactory = null, + ?ServerRequestFactoryInterface $serverRequestFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?UploadedFileFactoryInterface $uploadedFileFactory = null, + ?UriFactoryInterface $uriFactory = null + ) { + $requestFactory ?? $requestFactory = $client instanceof RequestFactoryInterface ? $client : null; + $responseFactory ?? $responseFactory = $client instanceof ResponseFactoryInterface ? $client : null; + $serverRequestFactory ?? $serverRequestFactory = $client instanceof ServerRequestFactoryInterface ? $client : null; + $streamFactory ?? $streamFactory = $client instanceof StreamFactoryInterface ? $client : null; + $uploadedFileFactory ?? $uploadedFileFactory = $client instanceof UploadedFileFactoryInterface ? $client : null; + $uriFactory ?? $uriFactory = $client instanceof UriFactoryInterface ? $client : null; + + parent::__construct($requestFactory, $responseFactory, $serverRequestFactory, $streamFactory, $uploadedFileFactory, $uriFactory); + + $this->client = $client ?? Psr18ClientDiscovery::find(); + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->client->sendRequest($request); + } +} diff --git a/admin/vendor/php-http/discovery/src/Psr18ClientDiscovery.php b/admin/vendor/php-http/discovery/src/Psr18ClientDiscovery.php new file mode 100644 index 0000000..3f95418 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Psr18ClientDiscovery.php @@ -0,0 +1,33 @@ + + */ +final class Psr18ClientDiscovery extends ClassDiscovery +{ + /** + * Finds a PSR-18 HTTP Client. + * + * @return ClientInterface + * + * @throws RealNotFoundException + */ + public static function find() + { + try { + $client = static::findOneByType(ClientInterface::class); + } catch (DiscoveryFailedException $e) { + throw new RealNotFoundException('No PSR-18 clients found. Make sure to install a package providing "psr/http-client-implementation". Example: "php-http/guzzle7-adapter".', 0, $e); + } + + return static::instantiateClass($client); + } +} diff --git a/admin/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php b/admin/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php new file mode 100644 index 0000000..0fa4240 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php @@ -0,0 +1,185 @@ + + * + * Don't miss updating src/Composer/Plugin.php when adding a new supported class. + */ +final class CommonClassesStrategy implements DiscoveryStrategy +{ + /** + * @var array + */ + private static $classes = [ + MessageFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleMessageFactory::class, 'condition' => [GuzzleRequest::class, GuzzleMessageFactory::class]], + ['class' => DiactorosMessageFactory::class, 'condition' => [DiactorosRequest::class, DiactorosMessageFactory::class]], + ['class' => SlimMessageFactory::class, 'condition' => [SlimRequest::class, SlimMessageFactory::class]], + ], + StreamFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleStreamFactory::class, 'condition' => [GuzzleRequest::class, GuzzleStreamFactory::class]], + ['class' => DiactorosStreamFactory::class, 'condition' => [DiactorosRequest::class, DiactorosStreamFactory::class]], + ['class' => SlimStreamFactory::class, 'condition' => [SlimRequest::class, SlimStreamFactory::class]], + ], + UriFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleUriFactory::class, 'condition' => [GuzzleRequest::class, GuzzleUriFactory::class]], + ['class' => DiactorosUriFactory::class, 'condition' => [DiactorosRequest::class, DiactorosUriFactory::class]], + ['class' => SlimUriFactory::class, 'condition' => [SlimRequest::class, SlimUriFactory::class]], + ], + HttpAsyncClient::class => [ + ['class' => SymfonyHttplug::class, 'condition' => [SymfonyHttplug::class, Promise::class, [self::class, 'isPsr17FactoryInstalled']]], + ['class' => Guzzle7::class, 'condition' => Guzzle7::class], + ['class' => Guzzle6::class, 'condition' => Guzzle6::class], + ['class' => Curl::class, 'condition' => Curl::class], + ['class' => React::class, 'condition' => React::class], + ], + HttpClient::class => [ + ['class' => SymfonyHttplug::class, 'condition' => [SymfonyHttplug::class, [self::class, 'isPsr17FactoryInstalled'], [self::class, 'isSymfonyImplementingHttpClient']]], + ['class' => Guzzle7::class, 'condition' => Guzzle7::class], + ['class' => Guzzle6::class, 'condition' => Guzzle6::class], + ['class' => Guzzle5::class, 'condition' => Guzzle5::class], + ['class' => Curl::class, 'condition' => Curl::class], + ['class' => Socket::class, 'condition' => Socket::class], + ['class' => Buzz::class, 'condition' => Buzz::class], + ['class' => React::class, 'condition' => React::class], + ['class' => Cake::class, 'condition' => Cake::class], + ['class' => Artax::class, 'condition' => Artax::class], + [ + 'class' => [self::class, 'buzzInstantiate'], + 'condition' => [\Buzz\Client\FileGetContents::class, \Buzz\Message\ResponseBuilder::class], + ], + ], + Psr18Client::class => [ + [ + 'class' => [self::class, 'symfonyPsr18Instantiate'], + 'condition' => [SymfonyPsr18::class, Psr17RequestFactory::class], + ], + [ + 'class' => GuzzleHttp::class, + 'condition' => [self::class, 'isGuzzleImplementingPsr18'], + ], + [ + 'class' => [self::class, 'buzzInstantiate'], + 'condition' => [\Buzz\Client\FileGetContents::class, \Buzz\Message\ResponseBuilder::class], + ], + ], + ]; + + public static function getCandidates($type) + { + if (Psr18Client::class === $type) { + return self::getPsr18Candidates(); + } + + return self::$classes[$type] ?? []; + } + + /** + * @return array The return value is always an array with zero or more elements. Each + * element is an array with two keys ['class' => string, 'condition' => mixed]. + */ + private static function getPsr18Candidates() + { + $candidates = self::$classes[Psr18Client::class]; + + // HTTPlug 2.0 clients implements PSR18Client too. + foreach (self::$classes[HttpClient::class] as $c) { + if (!is_string($c['class'])) { + continue; + } + try { + if (ClassDiscovery::safeClassExists($c['class']) && is_subclass_of($c['class'], Psr18Client::class)) { + $candidates[] = $c; + } + } catch (\Throwable $e) { + trigger_error(sprintf('Got exception "%s (%s)" while checking if a PSR-18 Client is available', get_class($e), $e->getMessage()), E_USER_WARNING); + } + } + + return $candidates; + } + + public static function buzzInstantiate() + { + return new \Buzz\Client\FileGetContents(Psr17FactoryDiscovery::findResponseFactory()); + } + + public static function symfonyPsr18Instantiate() + { + return new SymfonyPsr18(null, Psr17FactoryDiscovery::findResponseFactory(), Psr17FactoryDiscovery::findStreamFactory()); + } + + public static function isGuzzleImplementingPsr18() + { + return defined('GuzzleHttp\ClientInterface::MAJOR_VERSION'); + } + + public static function isSymfonyImplementingHttpClient() + { + return is_subclass_of(SymfonyHttplug::class, HttpClient::class); + } + + /** + * Can be used as a condition. + * + * @return bool + */ + public static function isPsr17FactoryInstalled() + { + try { + Psr17FactoryDiscovery::findResponseFactory(); + } catch (NotFoundException $e) { + return false; + } catch (\Throwable $e) { + trigger_error(sprintf('Got exception "%s (%s)" while checking if a PSR-17 ResponseFactory is available', get_class($e), $e->getMessage()), E_USER_WARNING); + + return false; + } + + return true; + } +} diff --git a/admin/vendor/php-http/discovery/src/Strategy/CommonPsr17ClassesStrategy.php b/admin/vendor/php-http/discovery/src/Strategy/CommonPsr17ClassesStrategy.php new file mode 100644 index 0000000..04cf4ba --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Strategy/CommonPsr17ClassesStrategy.php @@ -0,0 +1,104 @@ + + * + * Don't miss updating src/Composer/Plugin.php when adding a new supported class. + */ +final class CommonPsr17ClassesStrategy implements DiscoveryStrategy +{ + /** + * @var array + */ + private static $classes = [ + RequestFactoryInterface::class => [ + 'Phalcon\Http\Message\RequestFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\RequestFactory', + 'Http\Factory\Guzzle\RequestFactory', + 'Http\Factory\Slim\RequestFactory', + 'Laminas\Diactoros\RequestFactory', + 'Slim\Psr7\Factory\RequestFactory', + 'HttpSoft\Message\RequestFactory', + ], + ResponseFactoryInterface::class => [ + 'Phalcon\Http\Message\ResponseFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\ResponseFactory', + 'Http\Factory\Guzzle\ResponseFactory', + 'Http\Factory\Slim\ResponseFactory', + 'Laminas\Diactoros\ResponseFactory', + 'Slim\Psr7\Factory\ResponseFactory', + 'HttpSoft\Message\ResponseFactory', + ], + ServerRequestFactoryInterface::class => [ + 'Phalcon\Http\Message\ServerRequestFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\ServerRequestFactory', + 'Http\Factory\Guzzle\ServerRequestFactory', + 'Http\Factory\Slim\ServerRequestFactory', + 'Laminas\Diactoros\ServerRequestFactory', + 'Slim\Psr7\Factory\ServerRequestFactory', + 'HttpSoft\Message\ServerRequestFactory', + ], + StreamFactoryInterface::class => [ + 'Phalcon\Http\Message\StreamFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\StreamFactory', + 'Http\Factory\Guzzle\StreamFactory', + 'Http\Factory\Slim\StreamFactory', + 'Laminas\Diactoros\StreamFactory', + 'Slim\Psr7\Factory\StreamFactory', + 'HttpSoft\Message\StreamFactory', + ], + UploadedFileFactoryInterface::class => [ + 'Phalcon\Http\Message\UploadedFileFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\UploadedFileFactory', + 'Http\Factory\Guzzle\UploadedFileFactory', + 'Http\Factory\Slim\UploadedFileFactory', + 'Laminas\Diactoros\UploadedFileFactory', + 'Slim\Psr7\Factory\UploadedFileFactory', + 'HttpSoft\Message\UploadedFileFactory', + ], + UriFactoryInterface::class => [ + 'Phalcon\Http\Message\UriFactory', + 'Nyholm\Psr7\Factory\Psr17Factory', + 'GuzzleHttp\Psr7\HttpFactory', + 'Http\Factory\Diactoros\UriFactory', + 'Http\Factory\Guzzle\UriFactory', + 'Http\Factory\Slim\UriFactory', + 'Laminas\Diactoros\UriFactory', + 'Slim\Psr7\Factory\UriFactory', + 'HttpSoft\Message\UriFactory', + ], + ]; + + public static function getCandidates($type) + { + $candidates = []; + if (isset(self::$classes[$type])) { + foreach (self::$classes[$type] as $class) { + $candidates[] = ['class' => $class, 'condition' => [$class]]; + } + } + + return $candidates; + } +} diff --git a/admin/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php b/admin/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php new file mode 100644 index 0000000..1eadb14 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php @@ -0,0 +1,23 @@ + + */ +interface DiscoveryStrategy +{ + /** + * Find a resource of a specific type. + * + * @param string $type + * + * @return array The return value is always an array with zero or more elements. Each + * element is an array with two keys ['class' => string, 'condition' => mixed]. + * + * @throws StrategyUnavailableException if we cannot use this strategy + */ + public static function getCandidates($type); +} diff --git a/admin/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php b/admin/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php new file mode 100644 index 0000000..77b9d27 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php @@ -0,0 +1,24 @@ + + */ +final class MockClientStrategy implements DiscoveryStrategy +{ + public static function getCandidates($type) + { + if (is_a(HttpClient::class, $type, true) || is_a(HttpAsyncClient::class, $type, true)) { + return [['class' => Mock::class, 'condition' => Mock::class]]; + } + + return []; + } +} diff --git a/admin/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php b/admin/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php new file mode 100644 index 0000000..74b78b8 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php @@ -0,0 +1,90 @@ + + * @author Márk Sági-Kazár + */ +class PuliBetaStrategy implements DiscoveryStrategy +{ + /** + * @var GeneratedPuliFactory + */ + protected static $puliFactory; + + /** + * @var Discovery + */ + protected static $puliDiscovery; + + /** + * @return GeneratedPuliFactory + * + * @throws PuliUnavailableException + */ + private static function getPuliFactory() + { + if (null === self::$puliFactory) { + if (!defined('PULI_FACTORY_CLASS')) { + throw new PuliUnavailableException('Puli Factory is not available'); + } + + $puliFactoryClass = PULI_FACTORY_CLASS; + + if (!ClassDiscovery::safeClassExists($puliFactoryClass)) { + throw new PuliUnavailableException('Puli Factory class does not exist'); + } + + self::$puliFactory = new $puliFactoryClass(); + } + + return self::$puliFactory; + } + + /** + * Returns the Puli discovery layer. + * + * @return Discovery + * + * @throws PuliUnavailableException + */ + private static function getPuliDiscovery() + { + if (!isset(self::$puliDiscovery)) { + $factory = self::getPuliFactory(); + $repository = $factory->createRepository(); + + self::$puliDiscovery = $factory->createDiscovery($repository); + } + + return self::$puliDiscovery; + } + + public static function getCandidates($type) + { + $returnData = []; + $bindings = self::getPuliDiscovery()->findBindings($type); + + foreach ($bindings as $binding) { + $condition = true; + if ($binding->hasParameterValue('depends')) { + $condition = $binding->getParameterValue('depends'); + } + $returnData[] = ['class' => $binding->getClassName(), 'condition' => $condition]; + } + + return $returnData; + } +} diff --git a/admin/vendor/php-http/discovery/src/StreamFactoryDiscovery.php b/admin/vendor/php-http/discovery/src/StreamFactoryDiscovery.php new file mode 100644 index 0000000..e11c49a --- /dev/null +++ b/admin/vendor/php-http/discovery/src/StreamFactoryDiscovery.php @@ -0,0 +1,34 @@ + + * + * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery. + */ +final class StreamFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a Stream Factory. + * + * @return StreamFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $streamFactory = static::findOneByType(StreamFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException('No stream factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.', 0, $e); + } + + return static::instantiateClass($streamFactory); + } +} diff --git a/admin/vendor/php-http/discovery/src/UriFactoryDiscovery.php b/admin/vendor/php-http/discovery/src/UriFactoryDiscovery.php new file mode 100644 index 0000000..db3add2 --- /dev/null +++ b/admin/vendor/php-http/discovery/src/UriFactoryDiscovery.php @@ -0,0 +1,34 @@ + + * + * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery. + */ +final class UriFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a URI Factory. + * + * @return UriFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $uriFactory = static::findOneByType(UriFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException('No uri factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.', 0, $e); + } + + return static::instantiateClass($uriFactory); + } +} diff --git a/admin/vendor/php-http/httplug/.php-cs-fixer.dist.php b/admin/vendor/php-http/httplug/.php-cs-fixer.dist.php new file mode 100644 index 0000000..83809c2 --- /dev/null +++ b/admin/vendor/php-http/httplug/.php-cs-fixer.dist.php @@ -0,0 +1,16 @@ +in(__DIR__.'/src') + ->name('*.php') +; + +$config = (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + ]) + ->setFinder($finder) +; + +return $config; diff --git a/admin/vendor/php-http/httplug/CHANGELOG.md b/admin/vendor/php-http/httplug/CHANGELOG.md new file mode 100644 index 0000000..4c3d863 --- /dev/null +++ b/admin/vendor/php-http/httplug/CHANGELOG.md @@ -0,0 +1,144 @@ +# Change Log + + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [2.4.1] - 2024-09-23 + +- Updated code to not raise warnings for nullable parameters in PHP 8.4. + +## [2.4.0] - 2023-04-14 + +### Changed + +- Allow `psr/http-message` v2 in addition to v1 +- Deprecate `Http\Client\HttpClient`, use [PSR-18](https://www.php-fig.org/psr/psr-18/) instead + +## [2.3.0] - 2022-02-21 + +### Changed + +- Enabled the `$onRejected` callback of `HttpRejectedPromise` to return a promise for implementing a retry + mechanism [#168](https://github.com/php-http/httplug/pull/168) + +## [2.2.0] - 2020-07-13 + +### Changed + +- Support PHP 7.1-8.0 + +## [2.1.0] - 2019-12-27 + +### Changed + +- `Http\Client\Exception\NetworkException` no longer extends `Http\Client\Exception\RequestException`, + in accordance with [PSR-18](https://www.php-fig.org/psr/psr-18/) + +## [2.0.0] - 2018-10-31 + +This version is no BC break for consumers using HTTPlug. However, HTTP clients that +implement HTTPlug need to adjust because we add return type declarations. + +### Added + +- Support for PSR-18 (HTTP client). + +### Changed + +- **BC Break:** `HttpClient::sendRequest(RequestInterface $request)` has a return type annotation. The new +signature is `HttpClient::sendRequest(RequestInterface $request): ResponseInterface`. +- **BC Break:** `RequestException::getRequest()` has a return type annotation. The new +signature is `RequestException::getRequest(): RequestInterface`. + +### Removed + +- PHP 5 support + + +## [1.1.0] - 2016-08-31 + +### Added + +- HttpFulfilledPromise and HttpRejectedPromise which respect the HttpAsyncClient interface + + +## [1.0.0] - 2016-01-26 + +### Removed + +- Stability configuration from composer + + +## [1.0.0-RC1] - 2016-01-12 + +### Changed + +- Updated package files +- Updated promise dependency to RC1 + + +## [1.0.0-beta] - 2015-12-17 + +### Added + +- Puli configuration and binding types + +### Changed + +- Exception concept + + +## [1.0.0-alpha3] - 2015-12-13 + +### Changed + +- Async client does not throw exceptions + +### Removed + +- Promise interface moved to its own repository: [php-http/promise](https://github.com/php-http/promise) + + +## [1.0.0-alpha2] - 2015-11-16 + +### Added + +- Async client and Promise interface + + +## [1.0.0-alpha] - 2015-10-26 + +### Added + +- Better domain exceptions. + +### Changed + +- Purpose of the library: general HTTP CLient abstraction. + +### Removed + +- Request options: they should be configured at construction time. +- Multiple request sending: should be done asynchronously using Async Client. +- `getName` method + + +## 0.1.0 - 2015-06-03 + +### Added + +- Initial release + + +[Unreleased]: https://github.com/php-http/httplug/compare/v2.0.0...HEAD +[2.0.0]: https://github.com/php-http/httplug/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/php-http/httplug/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/php-http/httplug/compare/v1.0.0-RC1...v1.0.0 +[1.0.0-RC1]: https://github.com/php-http/httplug/compare/v1.0.0-beta...v1.0.0-RC1 +[1.0.0-beta]: https://github.com/php-http/httplug/compare/v1.0.0-alpha3...v1.0.0-beta +[1.0.0-alpha3]: https://github.com/php-http/httplug/compare/v1.0.0-alpha2...v1.0.0-alpha3 +[1.0.0-alpha2]: https://github.com/php-http/httplug/compare/v1.0.0-alpha...v1.0.0-alpha2 +[1.0.0-alpha]: https://github.com/php-http/httplug/compare/v0.1.0...v1.0.0-alpha diff --git a/admin/vendor/php-http/httplug/LICENSE b/admin/vendor/php-http/httplug/LICENSE new file mode 100644 index 0000000..8cd264c --- /dev/null +++ b/admin/vendor/php-http/httplug/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Eric GELOEN +Copyright (c) 2015 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/php-http/httplug/README.md b/admin/vendor/php-http/httplug/README.md new file mode 100644 index 0000000..a9b476a --- /dev/null +++ b/admin/vendor/php-http/httplug/README.md @@ -0,0 +1,54 @@ +# HTTPlug + +[![Latest Version](https://img.shields.io/github/release/php-http/httplug.svg?style=flat-square)](https://github.com/php-http/httplug/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://github.com/php-http/httplug/actions/workflows/ci.yml/badge.svg)](https://github.com/php-http/httplug/actions/workflows/ci.yml) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/httplug.svg?style=flat-square)](https://packagist.org/packages/php-http/httplug) + +[![Email](https://img.shields.io/badge/email-team@httplug.io-blue.svg?style=flat-square)](mailto:team@httplug.io) + +**HTTPlug, the HTTP client abstraction for PHP.** + + +## Intro + +HTTP client standard built on [PSR-7](http://www.php-fig.org/psr/psr-7/) HTTP +messages. The HttpAsyncClient defines an asynchronous HTTP client for PHP. + +This package also provides a synchronous HttpClient interface with the same +method signature as the [PSR-18](http://www.php-fig.org/psr/psr-18/) client. +For synchronous requests, we recommend using PSR-18 directly. + + +## History + +HTTPlug is the official successor of the [ivory http adapter](https://github.com/egeloen/ivory-http-adapter). +HTTPlug is a predecessor of [PSR-18](http://www.php-fig.org/psr/psr-18/) + + +## Install + +Via Composer + +``` bash +$ composer require php-http/httplug +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org). + + +## Testing + +``` bash +$ composer test +``` + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/admin/vendor/php-http/httplug/composer.json b/admin/vendor/php-http/httplug/composer.json new file mode 100644 index 0000000..7391770 --- /dev/null +++ b/admin/vendor/php-http/httplug/composer.json @@ -0,0 +1,40 @@ +{ + "name": "php-http/httplug", + "description": "HTTPlug, the HTTP client abstraction for PHP", + "keywords": [ + "http", + "client" + ], + "homepage": "http://httplug.io", + "license": "MIT", + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "require": { + "php": "^7.1 || ^8.0", + "php-http/promise": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + } +} diff --git a/admin/vendor/php-http/httplug/puli.json b/admin/vendor/php-http/httplug/puli.json new file mode 100644 index 0000000..4168331 --- /dev/null +++ b/admin/vendor/php-http/httplug/puli.json @@ -0,0 +1,12 @@ +{ + "version": "1.0", + "name": "php-http/httplug", + "binding-types": { + "Http\\Client\\HttpAsyncClient": { + "description": "Async HTTP Client" + }, + "Http\\Client\\HttpClient": { + "description": "HTTP Client" + } + } +} diff --git a/admin/vendor/php-http/httplug/src/Exception.php b/admin/vendor/php-http/httplug/src/Exception.php new file mode 100644 index 0000000..4df164c --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception.php @@ -0,0 +1,14 @@ + + */ +interface Exception extends PsrClientException +{ +} diff --git a/admin/vendor/php-http/httplug/src/Exception/HttpException.php b/admin/vendor/php-http/httplug/src/Exception/HttpException.php new file mode 100644 index 0000000..8af32f1 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception/HttpException.php @@ -0,0 +1,65 @@ + + */ +class HttpException extends RequestException +{ + /** + * @var ResponseInterface + */ + protected $response; + + /** + * @param string $message + */ + public function __construct( + $message, + RequestInterface $request, + ResponseInterface $response, + ?\Exception $previous = null + ) { + parent::__construct($message, $request, $previous); + + $this->response = $response; + $this->code = $response->getStatusCode(); + } + + /** + * Returns the response. + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } + + /** + * Factory method to create a new exception with a normalized error message. + */ + public static function create( + RequestInterface $request, + ResponseInterface $response, + ?\Exception $previous = null + ) { + $message = sprintf( + '[url] %s [http method] %s [status code] %s [reason phrase] %s', + $request->getRequestTarget(), + $request->getMethod(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + return new static($message, $request, $response, $previous); + } +} diff --git a/admin/vendor/php-http/httplug/src/Exception/NetworkException.php b/admin/vendor/php-http/httplug/src/Exception/NetworkException.php new file mode 100644 index 0000000..ce5f4d7 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception/NetworkException.php @@ -0,0 +1,28 @@ + + */ +class NetworkException extends TransferException implements PsrNetworkException +{ + use RequestAwareTrait; + + /** + * @param string $message + */ + public function __construct($message, RequestInterface $request, ?\Exception $previous = null) + { + $this->setRequest($request); + + parent::__construct($message, 0, $previous); + } +} diff --git a/admin/vendor/php-http/httplug/src/Exception/RequestAwareTrait.php b/admin/vendor/php-http/httplug/src/Exception/RequestAwareTrait.php new file mode 100644 index 0000000..f507982 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception/RequestAwareTrait.php @@ -0,0 +1,23 @@ +request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/admin/vendor/php-http/httplug/src/Exception/RequestException.php b/admin/vendor/php-http/httplug/src/Exception/RequestException.php new file mode 100644 index 0000000..dbed296 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception/RequestException.php @@ -0,0 +1,29 @@ + + */ +class RequestException extends TransferException implements PsrRequestException +{ + use RequestAwareTrait; + + /** + * @param string $message + */ + public function __construct($message, RequestInterface $request, ?\Exception $previous = null) + { + $this->setRequest($request); + + parent::__construct($message, 0, $previous); + } +} diff --git a/admin/vendor/php-http/httplug/src/Exception/TransferException.php b/admin/vendor/php-http/httplug/src/Exception/TransferException.php new file mode 100644 index 0000000..a858cf5 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Exception/TransferException.php @@ -0,0 +1,14 @@ + + */ +class TransferException extends \RuntimeException implements Exception +{ +} diff --git a/admin/vendor/php-http/httplug/src/HttpAsyncClient.php b/admin/vendor/php-http/httplug/src/HttpAsyncClient.php new file mode 100644 index 0000000..c3b9d61 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/HttpAsyncClient.php @@ -0,0 +1,25 @@ + + */ +interface HttpAsyncClient +{ + /** + * Sends a PSR-7 request in an asynchronous way. + * + * Exceptions related to processing the request are available from the returned Promise. + * + * @return Promise resolves a PSR-7 Response or fails with an Http\Client\Exception + * + * @throws \Exception If processing the request is impossible (eg. bad configuration). + */ + public function sendAsyncRequest(RequestInterface $request); +} diff --git a/admin/vendor/php-http/httplug/src/HttpClient.php b/admin/vendor/php-http/httplug/src/HttpClient.php new file mode 100644 index 0000000..22b94aa --- /dev/null +++ b/admin/vendor/php-http/httplug/src/HttpClient.php @@ -0,0 +1,17 @@ +response = $response; + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return new self($onFulfilled($this->response)); + } catch (Exception $e) { + return new HttpRejectedPromise($e); + } + } + + public function getState() + { + return Promise::FULFILLED; + } + + public function wait($unwrap = true) + { + if ($unwrap) { + return $this->response; + } + } +} diff --git a/admin/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php b/admin/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php new file mode 100644 index 0000000..a489ad4 --- /dev/null +++ b/admin/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php @@ -0,0 +1,49 @@ +exception = $exception; + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + { + if (null === $onRejected) { + return $this; + } + + try { + $result = $onRejected($this->exception); + if ($result instanceof Promise) { + return $result; + } + + return new HttpFulfilledPromise($result); + } catch (Exception $e) { + return new self($e); + } + } + + public function getState() + { + return Promise::REJECTED; + } + + public function wait($unwrap = true) + { + if ($unwrap) { + throw $this->exception; + } + } +} diff --git a/admin/vendor/php-http/message/.php-cs-fixer.dist.php b/admin/vendor/php-http/message/.php-cs-fixer.dist.php new file mode 100644 index 0000000..55bd8ed --- /dev/null +++ b/admin/vendor/php-http/message/.php-cs-fixer.dist.php @@ -0,0 +1,19 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/spec') + ->name('*.php') +; + +$config = new PhpCsFixer\Config(); + +return $config + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'single_line_throw' => false, + 'trailing_comma_in_multiline' => false, // for methods this is incompatible with PHP 7 + ]) + ->setFinder($finder) +; diff --git a/admin/vendor/php-http/message/CHANGELOG.md b/admin/vendor/php-http/message/CHANGELOG.md new file mode 100644 index 0000000..24575c9 --- /dev/null +++ b/admin/vendor/php-http/message/CHANGELOG.md @@ -0,0 +1,278 @@ +# Changelog + + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [1.16.1] - 2024-03-07 + +- Adjust phpdoc to avoid warnings about return types. + +## [1.16.0] - 2023-05-17 + +- Remove direct dependency on `php-http/message-factory` as it is only needed for the deprecated Httplug factories. + Upgrade to PSR-17 message factories provided by your HTTP client implementation. + If you need the Httplug factories for the time being, you can `composer require php-http/message-factory` in your application. + +## [1.15.0] - 2023-05-10 + +**If you use the decorator classes, you might need to adjust your code to the parameter and return type declarations added in PSR-7** + +- Actually make compatible with PSR-7 1.1 and 2.0 +- Drop support for PHP 7.1 + +## [1.14.0] - 2023-04-14 (broken) + +**This release is not actually compatible with PSR-7 1.1 or 2.0** + +- Allow installation with http-message (PSR-7) version 2 in addition to version 1. +- Support for PHP 8.2 + +## [1.13.0] - 2022-02-11 + +- Added `Formatter::formatResponseForRequest()` to allow the formatter to get context from the request to decide what of the response to output. +- Deprecated `Formatter::formatResponse()` in favor of the new `formatResponseForRequest` method. + +## [1.12.0] - 2021-08-29 + +- Added support for adjusting binary detection regex in FullHttpMessageFormatter. + +## [1.11.2] - 2021-08-03 + +- Support GuzzleHttp/Psr7 version 2.0 in the (deprecated) GuzzleStreamFactory. + +## [1.11.1] - 2021-05-24 + +- Support GuzzleHttp/Psr7 version 2.0 in the (deprecated) GuzzleUriFactory. + +## [1.11.0] - 2020-02-01 + +- Migrated from `zendframework/zend-diactoros` to `laminas/laminas-diactoros`. + Users are encouraged to update their dependencies by simply replacing the Zend package with the Laminas package. + Due to the [laminas-zendframework-brige](https://github.com/laminas/laminas-zendframework-bridge), BC changes + are not expected and legacy code does not need to be refactored (though it is + [recommended and simple](https://docs.laminas.dev/migration/)). +- The diactoros factories of `php-http/message` will return objects from the `Laminas\Diactoros\` namespace, if + the respective classes are available via autoloading, but continue to return objects from `Zend\Diactoros\` + namespace otherwise. + +- Allow to specify the hashing algorithm for WSSE authentication. + +## [1.10.0] - 2020-11-11 + +- Added support for PHP 8.0. + +## [1.9.1] - 2020-10-13 + +- Improved detection of binary stream to not consider newlines, carriage return or tabs as binary. + +## [1.9.0] - 2020-08-17 + +- Omitted binary body in FullHttpMessageFormatter and CurlCommandFormatter. + `[binary stream omitted]` will be shown instead. + +### Added + +- New Header authentication method for arbitrary header authentication. + +## [1.8.0] - 2019-08-05 + +### Changed + +- Raised minimum PHP version to 7.1 + +### Fixed + +- Fatal error on `CurlCommandFormatter` when body is larger than `escapeshellarg` allowed length. +- Do not read stream in message formatter if stream is not seekable. + +## [1.7.2] - 2018-10-30 + +### Fixed + +- FilteredStream uses `@trigger_error` instead of throwing exceptions to not + break careless users. You still need to fix your stream code to respect + `isSeekable`. Seeking does not work as expected, and we will add exceptions + in version 2. + +## [1.7.1] - 2018-10-29 + +### Fixed + +- FilteredStream is not actually seekable + + +## [1.7.0] - 2018-08-15 + +### Fixed + +- Fix CurlCommandFormatter for binary request payloads +- Fix QueryParam authentication to assemble proper URL regardless of PHP `arg_separator.output` directive +- Do not pass `null` parameters to `Clue\StreamFilter\fun` + +### Changed + +- Dropped tests on HHVM + + +## [1.6.0] - 2017-07-05 + +### Added + +- CookieUtil::parseDate to create a date from cookie date string + +### Fixed + +- Fix curl command of CurlFormatter when there is an user-agent header + + +## [1.5.0] - 2017-02-14 + +### Added + +- Check for empty string in Stream factories +- Cookie::createWithoutValidation Static constructor to create a cookie. Will not perform any attribute validation during instantiation. +- Cookie::isValid Method to check if cookie attributes are valid. + +### Fixed + +- FilteredStream::getSize returns null because the contents size is unknown. +- Stream factories does not rewinds streams. The previous behavior was not coherent between factories and inputs. + +### Deprecated + +- FilteredStream::getReadFilter The read filter is internal and should never be used by consuming code. +- FilteredStream::getWriteFilter We did not implement writing to the streams at all. And if we do, the filter is an internal information and should not be used by consuming code. + + +## [1.4.1] - 2016-12-16 + +### Fixed + +- Cookie::matchPath Cookie with root path (`/`) will not match sub path (e.g. `/cookie`). + + +## [1.4.0] - 2016-10-20 + +### Added + +- Message, stream and URI factories for [Slim Framework](https://github.com/slimphp/Slim) +- BufferedStream that allow you to decorate a non-seekable stream with a seekable one. +- cUrlFormatter to be able to redo the request with a cURL command + + +## [1.3.1] - 2016-07-15 + +### Fixed + +- FullHttpMessageFormatter will not read from streams that you cannot rewind (non-seekable) +- FullHttpMessageFormatter will not read from the stream if $maxBodyLength is zero +- FullHttpMessageFormatter rewinds streams after they are read + + +## [1.3.0] - 2016-07-14 + +### Added + +- FullHttpMessageFormatter to include headers and body in the formatted message + +### Fixed + +- #41: Response builder broke header value + + +## [1.2.0] - 2016-03-29 + +### Added + +- The RequestMatcher is built after the Symfony RequestMatcher and separates + scheme, host and path expressions and provides an option to filter on the + method +- New RequestConditional authentication method using request matchers +- Add automatic basic auth info detection based on the URL + +### Changed + +- Improved ResponseBuilder + +### Deprecated + +- RegexRequestMatcher, use RequestMatcher instead +- Matching authenitcation method, use RequestConditional instead + + +## [1.1.0] - 2016-02-25 + +### Added + + - Add a request matcher interface and regex implementation + - Add a callback request matcher implementation + - Add a ResponseBuilder, to create PSR7 Response from a string + +### Fixed + + - Fix casting string on a FilteredStream not filtering the output + + +## [1.0.0] - 2016-01-27 + + +## [0.2.0] - 2015-12-29 + +### Added + +- Autoregistration of stream filters using Composer autoload +- Cookie +- [Apigen](http://www.apigen.org/) configuration + + +## [0.1.2] - 2015-12-26 + +### Added + +- Request and response factory bindings + +### Fixed + +- Chunk filter namespace in Dechunk stream + + +## [0.1.1] - 2015-12-25 + +### Added + +- Formatter + + +## 0.1.0 - 2015-12-24 + +### Added + +- Authentication +- Encoding +- Message decorator +- Message factory (Guzzle, Diactoros) + + +[Unreleased]: https://github.com/php-http/message/compare/1.10.0...HEAD +[1.10.0]: https://github.com/php-http/message/compare/1.9.1...1.10.0 +[1.9.1]: https://github.com/php-http/message/compare/1.9.0...1.9.1 +[1.9.0]: https://github.com/php-http/message/compare/1.8.0...1.9.0 +[1.8.0]: https://github.com/php-http/message/compare/1.7.2...1.8.0 +[1.7.2]: https://github.com/php-http/message/compare/v1.7.1...1.7.2 +[1.7.1]: https://github.com/php-http/message/compare/1.7.0...v1.7.1 +[1.7.0]: https://github.com/php-http/message/compare/1.6.0...1.7.0 +[1.6.0]: https://github.com/php-http/message/compare/1.5.0...1.6.0 +[1.5.0]: https://github.com/php-http/message/compare/v1.4.1...1.5.0 +[1.4.1]: https://github.com/php-http/message/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/php-http/message/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/php-http/message/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/php-http/message/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/php-http/message/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/php-http/message/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/php-http/message/compare/0.2.0...v1.0.0 +[0.2.0]: https://github.com/php-http/message/compare/v0.1.2...0.2.0 +[0.1.2]: https://github.com/php-http/message/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/php-http/message/compare/v0.1.0...v0.1.1 diff --git a/admin/vendor/php-http/message/LICENSE b/admin/vendor/php-http/message/LICENSE new file mode 100644 index 0000000..4558d6f --- /dev/null +++ b/admin/vendor/php-http/message/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/php-http/message/README.md b/admin/vendor/php-http/message/README.md new file mode 100644 index 0000000..f1d79f7 --- /dev/null +++ b/admin/vendor/php-http/message/README.md @@ -0,0 +1,51 @@ +# HTTP Message + +[![Latest Version](https://img.shields.io/github/release/php-http/message.svg?style=flat-square)](https://github.com/php-http/message/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![tests](https://img.shields.io/github/actions/workflow/status/php-http/message/tests.yml?branch=1.x&label=tests&style=flat-square)](https://github.com/php-http/message/actions/workflows/tests.yml) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message.svg?style=flat-square)](https://packagist.org/packages/php-http/message) + +**HTTP Message related tools.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/message +``` + + +## Intro + +This package contains various PSR-7 tools which might be useful in an HTTP workflow: + +- Authentication method implementations +- Various Stream encoding tools +- Message decorators +- Message factory implementations for Guzzle PSR-7 and Diactoros +- Cookie implementation +- Request matchers + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/message.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Credits + +Thanks to [Cuzzle](https://github.com/namshi/cuzzle) for inpiration for the `CurlCommandFormatter`. + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/admin/vendor/php-http/message/apigen.neon b/admin/vendor/php-http/message/apigen.neon new file mode 100644 index 0000000..0ba1a18 --- /dev/null +++ b/admin/vendor/php-http/message/apigen.neon @@ -0,0 +1,6 @@ +source: + - src/ + +destination: build/api/ + +templateTheme: bootstrap diff --git a/admin/vendor/php-http/message/composer.json b/admin/vendor/php-http/message/composer.json new file mode 100644 index 0000000..37a86a7 --- /dev/null +++ b/admin/vendor/php-http/message/composer.json @@ -0,0 +1,63 @@ +{ + "name": "php-http/message", + "description": "HTTP Message related tools", + "keywords": [ + "message", + "http", + "psr-7" + ], + "homepage": "http://php-http.org", + "license": "MIT", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "clue/stream-filter": "^1.5", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "ergebnis/composer-normalize": "^2.6", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "slim/slim": "^3.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "laminas/laminas-diactoros": "Used with Diactoros Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "ergebnis/composer-normalize": true + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Message\\": "spec/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + } +} diff --git a/admin/vendor/php-http/message/puli.json b/admin/vendor/php-http/message/puli.json new file mode 100644 index 0000000..024a85d --- /dev/null +++ b/admin/vendor/php-http/message/puli.json @@ -0,0 +1,111 @@ +{ + "version": "1.0", + "name": "php-http/message", + "bindings": { + "064d003d-78a1-48c4-8f3b-1f92ff25da69": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\MessageFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Request" + } + }, + "0836751e-6558-4d1b-8993-4a52012947c3": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\ResponseFactory" + }, + "1d127622-dc61-4bfa-b9da-d221548d72c3": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\RequestFactory" + }, + "2438c2d0-0658-441f-8855-ddaf0f87d54d": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\MessageFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Request" + } + }, + "253aa08c-d705-46e7-b1d2-e28c97eef792": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\RequestFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Request" + } + }, + "273a34f9-62f4-4ba1-9801-b1284d49ff89": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\GuzzleStreamFactory", + "type": "Http\\Message\\StreamFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Stream" + } + }, + "304b83db-b594-4d83-ae75-1f633adf92f7": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\GuzzleUriFactory", + "type": "Http\\Message\\UriFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Uri" + } + }, + "3f4bc1cd-aa95-4702-9fa7-65408e471691": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\DiactorosUriFactory", + "type": "Http\\Message\\UriFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Uri" + } + }, + "4672a6ee-ad9e-4109-a5d1-b7d46f26c7a1": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\MessageFactory" + }, + "6234e947-d3bd-43eb-97d5-7f9e22e6bb1b": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\ResponseFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Response" + } + }, + "6a9ad6ce-d82c-470f-8e30-60f21d9d95bf": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\SlimUriFactory", + "type": "Http\\Message\\UriFactory" + }, + "72c2afa0-ea56-4d03-adb6-a9f241a8a734": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\SlimStreamFactory", + "type": "Http\\Message\\StreamFactory" + }, + "95c1be8f-39fe-4abd-8351-92cb14379a75": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\DiactorosStreamFactory", + "type": "Http\\Message\\StreamFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Stream" + } + }, + "a018af27-7590-4dcf-83a1-497f95604cd6": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\ResponseFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Response" + } + }, + "c07955b1-de46-43db-923b-d07fae9382cb": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\RequestFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Request" + } + } + } +} diff --git a/admin/vendor/php-http/message/src/Authentication.php b/admin/vendor/php-http/message/src/Authentication.php new file mode 100644 index 0000000..0fe0cb3 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication.php @@ -0,0 +1,25 @@ + + */ +interface Authentication +{ + /** + * Alter the request to add the authentication credentials. + * + * To do that, the implementation might use pre-stored credentials or do + * separate HTTP requests to obtain a valid token. + * + * @param RequestInterface $request The request without authentication information + * + * @return RequestInterface The request with added authentication information + */ + public function authenticate(RequestInterface $request); +} diff --git a/admin/vendor/php-http/message/src/Authentication/AutoBasicAuth.php b/admin/vendor/php-http/message/src/Authentication/AutoBasicAuth.php new file mode 100644 index 0000000..6120016 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/AutoBasicAuth.php @@ -0,0 +1,45 @@ + + */ +final class AutoBasicAuth implements Authentication +{ + /** + * Whether user info should be removed from the URI. + * + * @var bool + */ + private $shouldRemoveUserInfo; + + /** + * @param bool|true $shouldRremoveUserInfo + */ + public function __construct($shouldRremoveUserInfo = true) + { + $this->shouldRemoveUserInfo = (bool) $shouldRremoveUserInfo; + } + + public function authenticate(RequestInterface $request) + { + $uri = $request->getUri(); + $userInfo = $uri->getUserInfo(); + + if (!empty($userInfo)) { + if ($this->shouldRemoveUserInfo) { + $request = $request->withUri($uri->withUserInfo('')); + } + + $request = $request->withHeader('Authorization', sprintf('Basic %s', base64_encode($userInfo))); + } + + return $request; + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/BasicAuth.php b/admin/vendor/php-http/message/src/Authentication/BasicAuth.php new file mode 100644 index 0000000..85b13a2 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/BasicAuth.php @@ -0,0 +1,41 @@ + + */ +final class BasicAuth implements Authentication +{ + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @param string $username + * @param string $password + */ + public function __construct($username, $password) + { + $this->username = $username; + $this->password = $password; + } + + public function authenticate(RequestInterface $request) + { + $header = sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->username, $this->password))); + + return $request->withHeader('Authorization', $header); + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/Bearer.php b/admin/vendor/php-http/message/src/Authentication/Bearer.php new file mode 100644 index 0000000..287de2d --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/Bearer.php @@ -0,0 +1,34 @@ + + */ +final class Bearer implements Authentication +{ + /** + * @var string + */ + private $token; + + /** + * @param string $token + */ + public function __construct($token) + { + $this->token = $token; + } + + public function authenticate(RequestInterface $request) + { + $header = sprintf('Bearer %s', $this->token); + + return $request->withHeader('Authorization', $header); + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/Chain.php b/admin/vendor/php-http/message/src/Authentication/Chain.php new file mode 100644 index 0000000..d1d36d5 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/Chain.php @@ -0,0 +1,44 @@ + + */ +final class Chain implements Authentication +{ + /** + * @var Authentication[] + */ + private $authenticationChain = []; + + /** + * @param Authentication[] $authenticationChain + */ + public function __construct(array $authenticationChain = []) + { + foreach ($authenticationChain as $authentication) { + if (!$authentication instanceof Authentication) { + throw new \InvalidArgumentException( + 'Members of the authentication chain must be of type Http\Message\Authentication' + ); + } + } + + $this->authenticationChain = $authenticationChain; + } + + public function authenticate(RequestInterface $request) + { + foreach ($this->authenticationChain as $authentication) { + $request = $authentication->authenticate($request); + } + + return $request; + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/Header.php b/admin/vendor/php-http/message/src/Authentication/Header.php new file mode 100644 index 0000000..5b1b2e0 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/Header.php @@ -0,0 +1,33 @@ +name = $name; + $this->value = $value; + } + + public function authenticate(RequestInterface $request) + { + return $request->withHeader($this->name, $this->value); + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/Matching.php b/admin/vendor/php-http/message/src/Authentication/Matching.php new file mode 100644 index 0000000..cbef52e --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/Matching.php @@ -0,0 +1,66 @@ + + * + * @deprecated since since version 1.2, and will be removed in 2.0. Use {@link RequestConditional} instead. + */ +final class Matching implements Authentication +{ + /** + * @var Authentication + */ + private $authentication; + + /** + * @var CallbackRequestMatcher + */ + private $matcher; + + public function __construct(Authentication $authentication, ?callable $matcher = null) + { + if (is_null($matcher)) { + $matcher = function () { + return true; + }; + } + + $this->authentication = $authentication; + $this->matcher = new CallbackRequestMatcher($matcher); + } + + public function authenticate(RequestInterface $request) + { + if ($this->matcher->matches($request)) { + return $this->authentication->authenticate($request); + } + + return $request; + } + + /** + * Creates a matching authentication for an URL. + * + * @param string $url + * + * @return self + */ + public static function createUrlMatcher(Authentication $authentication, $url) + { + $matcher = function (RequestInterface $request) use ($url) { + return preg_match($url, $request->getRequestTarget()); + }; + + return new static($authentication, $matcher); + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/QueryParam.php b/admin/vendor/php-http/message/src/Authentication/QueryParam.php new file mode 100644 index 0000000..bc7b020 --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/QueryParam.php @@ -0,0 +1,44 @@ + + */ +final class QueryParam implements Authentication +{ + /** + * @var array + */ + private $params = []; + + public function __construct(array $params) + { + $this->params = $params; + } + + public function authenticate(RequestInterface $request) + { + $uri = $request->getUri(); + $query = $uri->getQuery(); + $params = []; + + parse_str($query, $params); + + $params = array_merge($params, $this->params); + + $query = http_build_query($params, '', '&'); + + $uri = $uri->withQuery($query); + + return $request->withUri($uri); + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/RequestConditional.php b/admin/vendor/php-http/message/src/Authentication/RequestConditional.php new file mode 100644 index 0000000..fefe44e --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/RequestConditional.php @@ -0,0 +1,40 @@ + + */ +final class RequestConditional implements Authentication +{ + /** + * @var RequestMatcher + */ + private $requestMatcher; + + /** + * @var Authentication + */ + private $authentication; + + public function __construct(RequestMatcher $requestMatcher, Authentication $authentication) + { + $this->requestMatcher = $requestMatcher; + $this->authentication = $authentication; + } + + public function authenticate(RequestInterface $request) + { + if ($this->requestMatcher->matches($request)) { + return $this->authentication->authenticate($request); + } + + return $request; + } +} diff --git a/admin/vendor/php-http/message/src/Authentication/Wsse.php b/admin/vendor/php-http/message/src/Authentication/Wsse.php new file mode 100644 index 0000000..9191efe --- /dev/null +++ b/admin/vendor/php-http/message/src/Authentication/Wsse.php @@ -0,0 +1,64 @@ + + */ +final class Wsse implements Authentication +{ + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @var string + */ + private $hashAlgorithm; + + /** + * @param string $username + * @param string $password + * @param string $hashAlgorithm To use a better hashing algorithm than the weak sha1, pass the algorithm to use, e.g. "sha512" + */ + public function __construct($username, $password, $hashAlgorithm = 'sha1') + { + $this->username = $username; + $this->password = $password; + if (false === in_array($hashAlgorithm, hash_algos())) { + throw new \InvalidArgumentException(sprintf('Unaccepted hashing algorithm: %s', $hashAlgorithm)); + } + $this->hashAlgorithm = $hashAlgorithm; + } + + public function authenticate(RequestInterface $request) + { + $nonce = substr(md5(uniqid(uniqid().'_', true)), 0, 16); + $created = date('c'); + $digest = base64_encode(hash($this->hashAlgorithm, base64_decode($nonce).$created.$this->password, true)); + + $wsse = sprintf( + 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', + $this->username, + $digest, + $nonce, + $created + ); + + return $request + ->withHeader('Authorization', 'WSSE profile="UsernameToken"') + ->withHeader('X-WSSE', $wsse) + ; + } +} diff --git a/admin/vendor/php-http/message/src/Builder/ResponseBuilder.php b/admin/vendor/php-http/message/src/Builder/ResponseBuilder.php new file mode 100644 index 0000000..4c3ecfc --- /dev/null +++ b/admin/vendor/php-http/message/src/Builder/ResponseBuilder.php @@ -0,0 +1,146 @@ +response = $response; + } + + /** + * Return response. + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } + + /** + * Add headers represented by an array of header lines. + * + * @param string[] $headers response headers as array of header lines + * + * @return $this + * + * @throws \UnexpectedValueException for invalid header values + * @throws \InvalidArgumentException for invalid status code arguments + */ + public function setHeadersFromArray(array $headers) + { + $status = array_shift($headers); + $this->setStatus($status); + + foreach ($headers as $headerLine) { + $headerLine = trim($headerLine); + if ('' === $headerLine) { + continue; + } + + $this->addHeader($headerLine); + } + + return $this; + } + + /** + * Add headers represented by a single string. + * + * @param string $headers response headers as single string + * + * @return $this + * + * @throws \InvalidArgumentException if $headers is not a string on object with __toString() + * @throws \UnexpectedValueException for invalid header values + */ + public function setHeadersFromString($headers) + { + if (!(is_string($headers) + || (is_object($headers) && method_exists($headers, '__toString'))) + ) { + throw new \InvalidArgumentException( + sprintf( + '%s expects parameter 1 to be a string, %s given', + __METHOD__, + is_object($headers) ? get_class($headers) : gettype($headers) + ) + ); + } + + $this->setHeadersFromArray(explode("\r\n", $headers)); + + return $this; + } + + /** + * Set response status from a status string. + * + * @param string $statusLine response status as a string + * + * @return $this + * + * @throws \InvalidArgumentException for invalid status line + */ + public function setStatus($statusLine) + { + $parts = explode(' ', $statusLine, 3); + if (count($parts) < 2 || 0 !== strpos(strtolower($parts[0]), 'http/')) { + throw new \InvalidArgumentException( + sprintf('"%s" is not a valid HTTP status line', $statusLine) + ); + } + + $reasonPhrase = count($parts) > 2 ? $parts[2] : ''; + $this->response = $this->response + ->withStatus((int) $parts[1], $reasonPhrase) + ->withProtocolVersion(substr($parts[0], 5)); + + return $this; + } + + /** + * Add header represented by a string. + * + * @param string $headerLine response header as a string + * + * @return $this + * + * @throws \InvalidArgumentException for invalid header names or values + */ + public function addHeader($headerLine) + { + $parts = explode(':', $headerLine, 2); + if (2 !== count($parts)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not a valid HTTP header line', $headerLine) + ); + } + $name = trim($parts[0]); + $value = trim($parts[1]); + if ($this->response->hasHeader($name)) { + $this->response = $this->response->withAddedHeader($name, $value); + } else { + $this->response = $this->response->withHeader($name, $value); + } + + return $this; + } +} diff --git a/admin/vendor/php-http/message/src/Cookie.php b/admin/vendor/php-http/message/src/Cookie.php new file mode 100644 index 0000000..b79fe0a --- /dev/null +++ b/admin/vendor/php-http/message/src/Cookie.php @@ -0,0 +1,524 @@ + + * + * @see http://tools.ietf.org/search/rfc6265 + */ +final class Cookie +{ + /** + * @var string + */ + private $name; + + /** + * @var string|null + */ + private $value; + + /** + * @var int|null + */ + private $maxAge; + + /** + * @var string|null + */ + private $domain; + + /** + * @var string + */ + private $path; + + /** + * @var bool + */ + private $secure; + + /** + * @var bool + */ + private $httpOnly; + + /** + * Expires attribute is HTTP 1.0 only and should be avoided. + * + * @var \DateTime|null + */ + private $expires; + + /** + * @param string $name + * @param string|null $value + * @param int|null $maxAge + * @param string|null $domain + * @param string|null $path + * @param bool $secure + * @param bool $httpOnly + * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided. + * + * @throws \InvalidArgumentException if name, value or max age is not valid + */ + public function __construct( + $name, + $value = null, + $maxAge = null, + $domain = null, + $path = null, + $secure = false, + $httpOnly = false, + ?\DateTime $expires = null + ) { + $this->validateName($name); + $this->validateValue($value); + $this->validateMaxAge($maxAge); + + $this->name = $name; + $this->value = $value; + $this->maxAge = $maxAge; + $this->expires = $expires; + $this->domain = $this->normalizeDomain($domain); + $this->path = $this->normalizePath($path); + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + } + + /** + * Creates a new cookie without any attribute validation. + * + * @param string $name + * @param string|null $value + * @param int $maxAge + * @param string|null $domain + * @param string|null $path + * @param bool $secure + * @param bool $httpOnly + * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided. + */ + public static function createWithoutValidation( + $name, + $value = null, + $maxAge = null, + $domain = null, + $path = null, + $secure = false, + $httpOnly = false, + ?\DateTime $expires = null + ) { + $cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires); + $cookie->name = $name; + $cookie->value = $value; + $cookie->maxAge = $maxAge; + + return $cookie; + } + + /** + * Returns the name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the value. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Checks if there is a value. + * + * @return bool + */ + public function hasValue() + { + return isset($this->value); + } + + /** + * Sets the value. + * + * @param string|null $value + * + * @return Cookie + */ + public function withValue($value) + { + $this->validateValue($value); + + $new = clone $this; + $new->value = $value; + + return $new; + } + + /** + * Returns the max age. + * + * @return int|null + */ + public function getMaxAge() + { + return $this->maxAge; + } + + /** + * Checks if there is a max age. + * + * @return bool + */ + public function hasMaxAge() + { + return isset($this->maxAge); + } + + /** + * Sets the max age. + * + * @param int|null $maxAge + * + * @return Cookie + */ + public function withMaxAge($maxAge) + { + $this->validateMaxAge($maxAge); + + $new = clone $this; + $new->maxAge = $maxAge; + + return $new; + } + + /** + * Returns the expiration time. + * + * @return \DateTime|null + */ + public function getExpires() + { + return $this->expires; + } + + /** + * Checks if there is an expiration time. + * + * @return bool + */ + public function hasExpires() + { + return isset($this->expires); + } + + /** + * Sets the expires. + * + * @return Cookie + */ + public function withExpires(?\DateTime $expires = null) + { + $new = clone $this; + $new->expires = $expires; + + return $new; + } + + /** + * Checks if the cookie is expired. + * + * @return bool + */ + public function isExpired() + { + return isset($this->expires) and $this->expires < new \DateTime(); + } + + /** + * Returns the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Checks if there is a domain. + * + * @return bool + */ + public function hasDomain() + { + return isset($this->domain); + } + + /** + * Sets the domain. + * + * @param string|null $domain + * + * @return Cookie + */ + public function withDomain($domain) + { + $new = clone $this; + $new->domain = $this->normalizeDomain($domain); + + return $new; + } + + /** + * Checks whether this cookie is meant for this domain. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.3 + * + * @param string $domain + * + * @return bool + */ + public function matchDomain($domain) + { + // Domain is not set or exact match + if (!$this->hasDomain() || 0 === strcasecmp($domain, $this->domain)) { + return true; + } + + // Domain is not an IP address + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain); + } + + /** + * Returns the path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the path. + * + * @param string|null $path + * + * @return Cookie + */ + public function withPath($path) + { + $new = clone $this; + $new->path = $this->normalizePath($path); + + return $new; + } + + /** + * Checks whether this cookie is meant for this path. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param string $path + * + * @return bool + */ + public function matchPath($path) + { + return $this->path === $path || (0 === strpos($path, rtrim($this->path, '/').'/')); + } + + /** + * Checks whether this cookie may only be sent over HTTPS. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Sets whether this cookie should only be sent over HTTPS. + * + * @param bool $secure + * + * @return Cookie + */ + public function withSecure($secure) + { + $new = clone $this; + $new->secure = (bool) $secure; + + return $new; + } + + /** + * Check whether this cookie may not be accessed through Javascript. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Sets whether this cookie may not be accessed through Javascript. + * + * @param bool $httpOnly + * + * @return Cookie + */ + public function withHttpOnly($httpOnly) + { + $new = clone $this; + $new->httpOnly = (bool) $httpOnly; + + return $new; + } + + /** + * Checks if this cookie represents the same cookie as $cookie. + * + * This does not compare the values, only name, domain and path. + * + * @return bool + */ + public function match(self $cookie) + { + return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path; + } + + /** + * Validates cookie attributes. + * + * @return bool + */ + public function isValid() + { + try { + $this->validateName($this->name); + $this->validateValue($this->value); + $this->validateMaxAge($this->maxAge); + } catch (\InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Validates the name attribute. + * + * @see http://tools.ietf.org/search/rfc2616#section-2.2 + * + * @param string $name + * + * @throws \InvalidArgumentException if the name is empty or contains invalid characters + */ + private function validateName($name) + { + if (strlen($name) < 1) { + throw new \InvalidArgumentException('The name cannot be empty'); + } + + // Name attribute is a token as per spec in RFC 2616 + if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + } + + /** + * Validates a value. + * + * @see http://tools.ietf.org/html/rfc6265#section-4.1.1 + * + * @param string|null $value + * + * @throws \InvalidArgumentException if the value contains invalid characters + */ + private function validateValue($value) + { + if (isset($value)) { + if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) { + throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value)); + } + } + } + + /** + * Validates a Max-Age attribute. + * + * @param int|null $maxAge + * + * @throws \InvalidArgumentException if the Max-Age is not an empty or integer value + */ + private function validateMaxAge($maxAge) + { + if (isset($maxAge)) { + if (!is_int($maxAge)) { + throw new \InvalidArgumentException('Max-Age must be integer'); + } + } + } + + /** + * Remove the leading '.' and lowercase the domain as per spec in RFC 6265. + * + * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3 + * @see http://tools.ietf.org/html/rfc6265#section-5.1.3 + * @see http://tools.ietf.org/html/rfc6265#section-5.2.3 + * + * @param string|null $domain + * + * @return string + */ + private function normalizeDomain($domain) + { + if (isset($domain)) { + $domain = ltrim(strtolower($domain), '.'); + } + + return $domain; + } + + /** + * Processes path as per spec in RFC 6265. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.4 + * @see http://tools.ietf.org/html/rfc6265#section-5.2.4 + * + * @param string|null $path + * + * @return string + */ + private function normalizePath($path) + { + if (null !== $path) { + $path = rtrim($path, '/'); + } + + if (empty($path) or '/' !== substr($path, 0, 1)) { + $path = '/'; + } + + return $path; + } +} diff --git a/admin/vendor/php-http/message/src/CookieJar.php b/admin/vendor/php-http/message/src/CookieJar.php new file mode 100644 index 0000000..159a99f --- /dev/null +++ b/admin/vendor/php-http/message/src/CookieJar.php @@ -0,0 +1,206 @@ + + */ +final class CookieJar implements \Countable, \IteratorAggregate +{ + /** + * @var \SplObjectStorage + */ + private $cookies; + + public function __construct() + { + $this->cookies = new \SplObjectStorage(); + } + + /** + * Checks if there is a cookie. + * + * @return bool + */ + public function hasCookie(Cookie $cookie) + { + return $this->cookies->contains($cookie); + } + + /** + * Adds a cookie. + */ + public function addCookie(Cookie $cookie) + { + if (!$this->hasCookie($cookie)) { + $cookies = $this->getMatchingCookies($cookie); + + foreach ($cookies as $matchingCookie) { + if ($cookie->getValue() !== $matchingCookie->getValue() || $cookie->getMaxAge() > $matchingCookie->getMaxAge()) { + $this->removeCookie($matchingCookie); + + continue; + } + } + + if ($cookie->hasValue()) { + $this->cookies->attach($cookie); + } + } + } + + /** + * Removes a cookie. + */ + public function removeCookie(Cookie $cookie) + { + $this->cookies->detach($cookie); + } + + /** + * Returns the cookies. + * + * @return Cookie[] + */ + public function getCookies() + { + $match = function ($matchCookie) { + return true; + }; + + return $this->findMatchingCookies($match); + } + + /** + * Returns all matching cookies. + * + * @return Cookie[] + */ + public function getMatchingCookies(Cookie $cookie) + { + $match = function ($matchCookie) use ($cookie) { + return $matchCookie->match($cookie); + }; + + return $this->findMatchingCookies($match); + } + + /** + * Finds matching cookies based on a callable. + * + * @return Cookie[] + */ + private function findMatchingCookies(callable $match) + { + $cookies = []; + + foreach ($this->cookies as $cookie) { + if ($match($cookie)) { + $cookies[] = $cookie; + } + } + + return $cookies; + } + + /** + * Checks if there are cookies. + * + * @return bool + */ + public function hasCookies() + { + return $this->cookies->count() > 0; + } + + /** + * Sets the cookies and removes any previous one. + * + * @param Cookie[] $cookies + */ + public function setCookies(array $cookies) + { + $this->clear(); + $this->addCookies($cookies); + } + + /** + * Adds some cookies. + * + * @param Cookie[] $cookies + */ + public function addCookies(array $cookies) + { + foreach ($cookies as $cookie) { + $this->addCookie($cookie); + } + } + + /** + * Removes some cookies. + * + * @param Cookie[] $cookies + */ + public function removeCookies(array $cookies) + { + foreach ($cookies as $cookie) { + $this->removeCookie($cookie); + } + } + + /** + * Removes cookies which match the given parameters. + * + * Null means that parameter should not be matched + * + * @param string|null $name + * @param string|null $domain + * @param string|null $path + */ + public function removeMatchingCookies($name = null, $domain = null, $path = null) + { + $match = function ($cookie) use ($name, $domain, $path) { + $match = true; + + if (isset($name)) { + $match = $match && ($cookie->getName() === $name); + } + + if (isset($domain)) { + $match = $match && $cookie->matchDomain($domain); + } + + if (isset($path)) { + $match = $match && $cookie->matchPath($path); + } + + return $match; + }; + + $cookies = $this->findMatchingCookies($match); + + $this->removeCookies($cookies); + } + + /** + * Removes all cookies. + */ + public function clear() + { + $this->cookies = new \SplObjectStorage(); + } + + #[\ReturnTypeWillChange] + public function count() + { + return $this->cookies->count(); + } + + #[\ReturnTypeWillChange] + public function getIterator() + { + return clone $this->cookies; + } +} diff --git a/admin/vendor/php-http/message/src/CookieUtil.php b/admin/vendor/php-http/message/src/CookieUtil.php new file mode 100644 index 0000000..44c5314 --- /dev/null +++ b/admin/vendor/php-http/message/src/CookieUtil.php @@ -0,0 +1,53 @@ + + */ +trait MessageDecorator +{ + /** + * @var MessageInterface + */ + private $message; + + /** + * Returns the decorated message. + * + * Since the underlying Message is immutable as well + * exposing it is not an issue, because it's state cannot be altered + */ + public function getMessage(): MessageInterface + { + return $this->message; + } + + public function getProtocolVersion(): string + { + return $this->message->getProtocolVersion(); + } + + public function withProtocolVersion(string $version): MessageInterface + { + $new = clone $this; + $new->message = $this->message->withProtocolVersion($version); + + return $new; + } + + public function getHeaders(): array + { + return $this->message->getHeaders(); + } + + public function hasHeader(string $header): bool + { + return $this->message->hasHeader($header); + } + + public function getHeader(string $header): array + { + return $this->message->getHeader($header); + } + + public function getHeaderLine(string $header): string + { + return $this->message->getHeaderLine($header); + } + + public function withHeader(string $header, $value): MessageInterface + { + $new = clone $this; + $new->message = $this->message->withHeader($header, $value); + + return $new; + } + + public function withAddedHeader(string $header, $value): MessageInterface + { + $new = clone $this; + $new->message = $this->message->withAddedHeader($header, $value); + + return $new; + } + + public function withoutHeader(string $header): MessageInterface + { + $new = clone $this; + $new->message = $this->message->withoutHeader($header); + + return $new; + } + + public function getBody(): StreamInterface + { + return $this->message->getBody(); + } + + public function withBody(StreamInterface $body): MessageInterface + { + $new = clone $this; + $new->message = $this->message->withBody($body); + + return $new; + } +} diff --git a/admin/vendor/php-http/message/src/Decorator/RequestDecorator.php b/admin/vendor/php-http/message/src/Decorator/RequestDecorator.php new file mode 100644 index 0000000..1392a76 --- /dev/null +++ b/admin/vendor/php-http/message/src/Decorator/RequestDecorator.php @@ -0,0 +1,66 @@ + + */ +trait RequestDecorator +{ + use MessageDecorator { + getMessage as getRequest; + } + + /** + * Exchanges the underlying request with another. + */ + public function withRequest(RequestInterface $request): RequestInterface + { + $new = clone $this; + $new->message = $request; + + return $new; + } + + public function getRequestTarget(): string + { + return $this->message->getRequestTarget(); + } + + public function withRequestTarget(string $requestTarget): RequestInterface + { + $new = clone $this; + $new->message = $this->message->withRequestTarget($requestTarget); + + return $new; + } + + public function getMethod(): string + { + return $this->message->getMethod(); + } + + public function withMethod(string $method): RequestInterface + { + $new = clone $this; + $new->message = $this->message->withMethod($method); + + return $new; + } + + public function getUri(): UriInterface + { + return $this->message->getUri(); + } + + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface + { + $new = clone $this; + $new->message = $this->message->withUri($uri, $preserveHost); + + return $new; + } +} diff --git a/admin/vendor/php-http/message/src/Decorator/ResponseDecorator.php b/admin/vendor/php-http/message/src/Decorator/ResponseDecorator.php new file mode 100644 index 0000000..0b75438 --- /dev/null +++ b/admin/vendor/php-http/message/src/Decorator/ResponseDecorator.php @@ -0,0 +1,44 @@ + + */ +trait ResponseDecorator +{ + use MessageDecorator { + getMessage as getResponse; + } + + /** + * Exchanges the underlying response with another. + */ + public function withResponse(ResponseInterface $response): ResponseInterface + { + $new = clone $this; + $new->message = $response; + + return $new; + } + + public function getStatusCode(): int + { + return $this->message->getStatusCode(); + } + + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface + { + $new = clone $this; + $new->message = $this->message->withStatus($code, $reasonPhrase); + + return $new; + } + + public function getReasonPhrase(): string + { + return $this->message->getReasonPhrase(); + } +} diff --git a/admin/vendor/php-http/message/src/Decorator/StreamDecorator.php b/admin/vendor/php-http/message/src/Decorator/StreamDecorator.php new file mode 100644 index 0000000..90d93b4 --- /dev/null +++ b/admin/vendor/php-http/message/src/Decorator/StreamDecorator.php @@ -0,0 +1,93 @@ + + */ +trait StreamDecorator +{ + /** + * @var StreamInterface + */ + protected $stream; + + public function __toString(): string + { + return $this->stream->__toString(); + } + + public function close(): void + { + $this->stream->close(); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function rewind(): void + { + $this->stream->rewind(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function write(string $string): int + { + return $this->stream->write($string); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function read(int $length): string + { + return $this->stream->read($length); + } + + public function getContents(): string + { + return $this->stream->getContents(); + } + + public function getMetadata(?string $key = null) + { + return $this->stream->getMetadata($key); + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/ChunkStream.php b/admin/vendor/php-http/message/src/Encoding/ChunkStream.php new file mode 100644 index 0000000..362ed78 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/ChunkStream.php @@ -0,0 +1,30 @@ + + */ +class ChunkStream extends FilteredStream +{ + protected function readFilter(): string + { + return 'chunk'; + } + + protected function writeFilter(): string + { + return 'dechunk'; + } + + protected function fill(): void + { + parent::fill(); + + if ($this->stream->eof()) { + $this->buffer .= "0\r\n\r\n"; + } + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/CompressStream.php b/admin/vendor/php-http/message/src/Encoding/CompressStream.php new file mode 100644 index 0000000..7e89388 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/CompressStream.php @@ -0,0 +1,39 @@ + + */ +class CompressStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 15, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15]); + } + + protected function readFilter(): string + { + return 'zlib.deflate'; + } + + protected function writeFilter(): string + { + return 'zlib.inflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/DechunkStream.php b/admin/vendor/php-http/message/src/Encoding/DechunkStream.php new file mode 100644 index 0000000..c1fe3a6 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/DechunkStream.php @@ -0,0 +1,23 @@ + + */ +class DechunkStream extends FilteredStream +{ + protected function readFilter(): string + { + return 'dechunk'; + } + + protected function writeFilter(): string + { + return 'chunk'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/DecompressStream.php b/admin/vendor/php-http/message/src/Encoding/DecompressStream.php new file mode 100644 index 0000000..aa3fdf0 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/DecompressStream.php @@ -0,0 +1,39 @@ + + */ +class DecompressStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 15]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15, 'level' => $level]); + } + + protected function readFilter(): string + { + return 'zlib.inflate'; + } + + protected function writeFilter(): string + { + return 'zlib.deflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/DeflateStream.php b/admin/vendor/php-http/message/src/Encoding/DeflateStream.php new file mode 100644 index 0000000..d8d8a8a --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/DeflateStream.php @@ -0,0 +1,35 @@ + + */ +class DeflateStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => -15, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15]); + } + + protected function readFilter(): string + { + return 'zlib.deflate'; + } + + protected function writeFilter(): string + { + return 'zlib.inflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/Filter/Chunk.php b/admin/vendor/php-http/message/src/Encoding/Filter/Chunk.php new file mode 100644 index 0000000..7a9e18f --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/Filter/Chunk.php @@ -0,0 +1,27 @@ + + */ +class Chunk extends \php_user_filter +{ + public function filter($in, $out, &$consumed, $closing): int + { + while ($bucket = stream_bucket_make_writeable($in)) { + $lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n"); + stream_bucket_append($out, $lenbucket); + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + + $lenbucket = stream_bucket_new($this->stream, "\r\n"); + stream_bucket_append($out, $lenbucket); + } + + return PSFS_PASS_ON; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/FilteredStream.php b/admin/vendor/php-http/message/src/Encoding/FilteredStream.php new file mode 100644 index 0000000..4bc44a6 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/FilteredStream.php @@ -0,0 +1,214 @@ + + */ +abstract class FilteredStream implements StreamInterface +{ + use StreamDecorator { + rewind as private doRewind; + seek as private doSeek; + } + public const BUFFER_SIZE = 8192; + + /** + * @var callable + */ + protected $readFilterCallback; + + /** + * @var resource + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $readFilter; + + /** + * @var callable + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $writeFilterCallback; + + /** + * @var resource + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $writeFilter; + + /** + * Internal buffer. + * + * @var string + */ + protected $buffer = ''; + + /** + * @param mixed|null $readFilterOptions + * @param mixed|null $writeFilterOptions deprecated since 1.5, will be removed in 2.0 + */ + public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) + { + if (null !== $readFilterOptions) { + $this->readFilterCallback = Filter\fun($this->readFilter(), $readFilterOptions); + } else { + $this->readFilterCallback = Filter\fun($this->readFilter()); + } + + if (null !== $writeFilterOptions) { + $this->writeFilterCallback = Filter\fun($this->writeFilter(), $writeFilterOptions); + + @trigger_error('The $writeFilterOptions argument is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + } else { + $this->writeFilterCallback = Filter\fun($this->writeFilter()); + } + + $this->stream = $stream; + } + + public function read(int $length): string + { + if (strlen($this->buffer) >= $length) { + $read = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $read; + } + + if ($this->stream->eof()) { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + $read = $this->buffer; + $this->buffer = ''; + $this->fill(); + + return $read.$this->read($length - strlen($read)); + } + + public function eof(): bool + { + return $this->stream->eof() && '' === $this->buffer; + } + + /** + * Buffer is filled by reading underlying stream. + * + * Callback is reading once more even if the stream is ended. + * This allow to get last data in the PHP buffer otherwise this + * bug is present : https://bugs.php.net/bug.php?id=48725 + */ + protected function fill(): void + { + $readFilterCallback = $this->readFilterCallback; + $this->buffer .= $readFilterCallback($this->stream->read(self::BUFFER_SIZE)); + + if ($this->stream->eof()) { + $this->buffer .= $readFilterCallback(); + } + } + + public function getContents(): string + { + $buffer = ''; + + while (!$this->eof()) { + $buf = $this->read(self::BUFFER_SIZE); + // Using a loose equality here to match on '' and false. + if (null == $buf) { + break; + } + + $buffer .= $buf; + } + + return $buffer; + } + + /** + * Always returns null because we can't tell the size of a stream when we filter. + */ + public function getSize(): ?int + { + return null; + } + + public function __toString(): string + { + return $this->getContents(); + } + + /** + * Filtered streams are not seekable. + * + * We would need to buffer and process everything to allow seeking. + */ + public function isSeekable(): bool + { + return false; + } + + /** + * Filtered streams are not seekable and can thus not be rewound. + */ + public function rewind(): void + { + @trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED); + $this->doRewind(); + } + + /** + * Filtered streams are not seekable. + */ + public function seek(int $offset, int $whence = SEEK_SET): void + { + @trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED); + $this->doSeek($offset, $whence); + } + + /** + * Returns the read filter name. + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + public function getReadFilter(): string + { + @trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->readFilter(); + } + + /** + * Returns the write filter name. + */ + abstract protected function readFilter(): string; + + /** + * Returns the write filter name. + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + public function getWriteFilter(): string + { + @trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->writeFilter(); + } + + /** + * Returns the write filter name. + */ + abstract protected function writeFilter(): string; +} diff --git a/admin/vendor/php-http/message/src/Encoding/GzipDecodeStream.php b/admin/vendor/php-http/message/src/Encoding/GzipDecodeStream.php new file mode 100644 index 0000000..78ecc84 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/GzipDecodeStream.php @@ -0,0 +1,39 @@ + + */ +class GzipDecodeStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 31]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31, 'level' => $level]); + } + + protected function readFilter(): string + { + return 'zlib.inflate'; + } + + protected function writeFilter(): string + { + return 'zlib.deflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/GzipEncodeStream.php b/admin/vendor/php-http/message/src/Encoding/GzipEncodeStream.php new file mode 100644 index 0000000..7ffa499 --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/GzipEncodeStream.php @@ -0,0 +1,39 @@ + + */ +class GzipEncodeStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 31, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31]); + } + + protected function readFilter(): string + { + return 'zlib.deflate'; + } + + protected function writeFilter(): string + { + return 'zlib.inflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Encoding/InflateStream.php b/admin/vendor/php-http/message/src/Encoding/InflateStream.php new file mode 100644 index 0000000..1abe88e --- /dev/null +++ b/admin/vendor/php-http/message/src/Encoding/InflateStream.php @@ -0,0 +1,39 @@ + + */ +class InflateStream extends FilteredStream +{ + /** + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => -15]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15, 'level' => $level]); + } + + protected function readFilter(): string + { + return 'zlib.inflate'; + } + + protected function writeFilter(): string + { + return 'zlib.deflate'; + } +} diff --git a/admin/vendor/php-http/message/src/Exception.php b/admin/vendor/php-http/message/src/Exception.php new file mode 100644 index 0000000..80d4cd9 --- /dev/null +++ b/admin/vendor/php-http/message/src/Exception.php @@ -0,0 +1,10 @@ + + * + * The formatResponseForRequest method will be added to this interface in the next major version, replacing the formatRequest method. + * Meanwhile, callers SHOULD check the formatter for the existence of formatResponseForRequest and call that if available. + * + * @method string formatResponseForRequest(ResponseInterface $response, RequestInterface $request) Formats a response in context of its request. + */ +interface Formatter +{ + /** + * Formats a request. + * + * @return string + */ + public function formatRequest(RequestInterface $request); + + /** + * @deprecated since 1.13, use formatResponseForRequest() instead + * + * Formats a response. + * + * @return string + */ + public function formatResponse(ResponseInterface $response); +} diff --git a/admin/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php b/admin/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php new file mode 100644 index 0000000..1878cad --- /dev/null +++ b/admin/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php @@ -0,0 +1,97 @@ + + */ +class CurlCommandFormatter implements Formatter +{ + public function formatRequest(RequestInterface $request) + { + $command = sprintf('curl %s', escapeshellarg((string) $request->getUri()->withFragment(''))); + if ('1.0' === $request->getProtocolVersion()) { + $command .= ' --http1.0'; + } elseif ('2.0' === $request->getProtocolVersion()) { + $command .= ' --http2'; + } + + $method = strtoupper($request->getMethod()); + if ('HEAD' === $method) { + $command .= ' --head'; + } elseif ('GET' !== $method) { + $command .= ' --request '.$method; + } + + $command .= $this->getHeadersAsCommandOptions($request); + + $body = $request->getBody(); + if ($body->getSize() > 0) { + // escapeshellarg argument max length on Windows, but longer body in curl command would be impractical anyways + if ($body->getSize() > 8192) { + $data = '[too long stream omitted]'; + } elseif ($body->isSeekable()) { + $data = $body->__toString(); + $body->rewind(); + // all non-printable ASCII characters and except for \t, \r, \n + if (preg_match('/([\x00-\x09\x0C\x0E-\x1F\x7F])/', $data)) { + $data = '[binary stream omitted]'; + } + } else { + $data = '[non-seekable stream omitted]'; + } + $escapedData = @escapeshellarg($data); + if (empty($escapedData)) { + $escapedData = 'We couldn\'t not escape the data properly'; + } + + $command .= sprintf(' --data %s', $escapedData); + } + + return $command; + } + + public function formatResponse(ResponseInterface $response) + { + return ''; + } + + /** + * Formats a response in context of its request. + * + * @return string + */ + public function formatResponseForRequest(ResponseInterface $response, RequestInterface $request) + { + return $this->formatResponse($response); + } + + /** + * @return string + */ + private function getHeadersAsCommandOptions(RequestInterface $request) + { + $command = ''; + foreach ($request->getHeaders() as $name => $values) { + if ('host' === strtolower($name) && $values[0] === $request->getUri()->getHost()) { + continue; + } + + if ('user-agent' === strtolower($name)) { + $command .= sprintf(' -A %s', escapeshellarg($values[0])); + + continue; + } + + $command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name))); + } + + return $command; + } +} diff --git a/admin/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php b/admin/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php new file mode 100644 index 0000000..116b2dd --- /dev/null +++ b/admin/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php @@ -0,0 +1,110 @@ + + */ +class FullHttpMessageFormatter implements Formatter +{ + /** + * The maximum length of the body. + * + * @var int|null + */ + private $maxBodyLength; + + /** + * @var string + */ + private $binaryDetectionRegex; + + /** + * @param int|null $maxBodyLength + * @param string $binaryDetectionRegex By default, this is all non-printable ASCII characters and except for \t, \r, \n + */ + public function __construct($maxBodyLength = 1000, string $binaryDetectionRegex = '/([\x00-\x09\x0C\x0E-\x1F\x7F])/') + { + $this->maxBodyLength = $maxBodyLength; + $this->binaryDetectionRegex = $binaryDetectionRegex; + } + + public function formatRequest(RequestInterface $request) + { + $message = sprintf( + "%s %s HTTP/%s\n", + $request->getMethod(), + $request->getRequestTarget(), + $request->getProtocolVersion() + ); + + foreach ($request->getHeaders() as $name => $values) { + $message .= $name.': '.implode(', ', $values)."\n"; + } + + return $this->addBody($request, $message); + } + + public function formatResponse(ResponseInterface $response) + { + $message = sprintf( + "HTTP/%s %s %s\n", + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + foreach ($response->getHeaders() as $name => $values) { + $message .= $name.': '.implode(', ', $values)."\n"; + } + + return $this->addBody($response, $message); + } + + /** + * Formats a response in context of its request. + * + * @return string + */ + public function formatResponseForRequest(ResponseInterface $response, RequestInterface $request) + { + return $this->formatResponse($response); + } + + /** + * Add the message body if the stream is seekable. + * + * @param string $message + * + * @return string + */ + private function addBody(MessageInterface $request, $message) + { + $message .= "\n"; + $stream = $request->getBody(); + if (!$stream->isSeekable() || 0 === $this->maxBodyLength) { + // Do not read the stream + return $message; + } + + $data = $stream->__toString(); + $stream->rewind(); + + if (preg_match($this->binaryDetectionRegex, $data)) { + return $message.'[binary stream omitted]'; + } + + if (null === $this->maxBodyLength) { + return $message.$data; + } + + return $message.mb_substr($data, 0, $this->maxBodyLength); + } +} diff --git a/admin/vendor/php-http/message/src/Formatter/SimpleFormatter.php b/admin/vendor/php-http/message/src/Formatter/SimpleFormatter.php new file mode 100644 index 0000000..c8b0450 --- /dev/null +++ b/admin/vendor/php-http/message/src/Formatter/SimpleFormatter.php @@ -0,0 +1,46 @@ + + * @author Márk Sági-Kazár + */ +class SimpleFormatter implements Formatter +{ + public function formatRequest(RequestInterface $request) + { + return sprintf( + '%s %s %s', + $request->getMethod(), + $request->getUri()->__toString(), + $request->getProtocolVersion() + ); + } + + public function formatResponse(ResponseInterface $response) + { + return sprintf( + '%s %s %s', + $response->getStatusCode(), + $response->getReasonPhrase(), + $response->getProtocolVersion() + ); + } + + /** + * Formats a response in context of its request. + * + * @return string + */ + public function formatResponseForRequest(ResponseInterface $response, RequestInterface $request) + { + return $this->formatResponse($response); + } +} diff --git a/admin/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php b/admin/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php new file mode 100644 index 0000000..c13f573 --- /dev/null +++ b/admin/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php @@ -0,0 +1,80 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Diactoros PSR-17 factory + */ +final class DiactorosMessageFactory implements MessageFactory +{ + /** + * @var DiactorosStreamFactory + */ + private $streamFactory; + + public function __construct() + { + $this->streamFactory = new DiactorosStreamFactory(); + } + + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + if (class_exists(LaminasRequest::class)) { + return (new LaminasRequest( + $uri, + $method, + $this->streamFactory->createStream($body), + $headers + ))->withProtocolVersion($protocolVersion); + } + + return (new ZendRequest( + $uri, + $method, + $this->streamFactory->createStream($body), + $headers + ))->withProtocolVersion($protocolVersion); + } + + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + if (class_exists(LaminasResponse::class)) { + return (new LaminasResponse( + $this->streamFactory->createStream($body), + $statusCode, + $headers + ))->withProtocolVersion($protocolVersion); + } + + return (new ZendResponse( + $this->streamFactory->createStream($body), + $statusCode, + $headers + ))->withProtocolVersion($protocolVersion); + } +} diff --git a/admin/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php b/admin/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php new file mode 100644 index 0000000..8844f74 --- /dev/null +++ b/admin/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php @@ -0,0 +1,53 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Guzzle PSR-17 factory + */ +final class GuzzleMessageFactory implements MessageFactory +{ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return new Request( + $method, + $uri, + $headers, + $body, + $protocolVersion + ); + } + + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return new Response( + $statusCode, + $headers, + $body, + $protocolVersion, + $reasonPhrase + ); + } +} diff --git a/admin/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php b/admin/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php new file mode 100644 index 0000000..118799c --- /dev/null +++ b/admin/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php @@ -0,0 +1,72 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Slim PSR-17 factory + */ +final class SlimMessageFactory implements MessageFactory +{ + /** + * @var SlimStreamFactory + */ + private $streamFactory; + + /** + * @var SlimUriFactory + */ + private $uriFactory; + + public function __construct() + { + $this->streamFactory = new SlimStreamFactory(); + $this->uriFactory = new SlimUriFactory(); + } + + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Request( + $method, + $this->uriFactory->createUri($uri), + new Headers($headers), + [], + [], + $this->streamFactory->createStream($body), + [] + ))->withProtocolVersion($protocolVersion); + } + + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Response( + $statusCode, + new Headers($headers), + $this->streamFactory->createStream($body) + ))->withProtocolVersion($protocolVersion); + } +} diff --git a/admin/vendor/php-http/message/src/RequestMatcher.php b/admin/vendor/php-http/message/src/RequestMatcher.php new file mode 100644 index 0000000..94fe532 --- /dev/null +++ b/admin/vendor/php-http/message/src/RequestMatcher.php @@ -0,0 +1,26 @@ + + */ +interface RequestMatcher +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param RequestInterface $request The PSR7 request to check for a match + * + * @return bool true if the request matches, false otherwise + */ + public function matches(RequestInterface $request); +} diff --git a/admin/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php b/admin/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php new file mode 100644 index 0000000..659e6e3 --- /dev/null +++ b/admin/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php @@ -0,0 +1,29 @@ + + */ +final class CallbackRequestMatcher implements RequestMatcher +{ + /** + * @var callable + */ + private $callback; + + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + public function matches(RequestInterface $request) + { + return (bool) call_user_func($this->callback, $request); + } +} diff --git a/admin/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php b/admin/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php new file mode 100644 index 0000000..253d9bc --- /dev/null +++ b/admin/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php @@ -0,0 +1,38 @@ + + * + * @deprecated since version 1.2 and will be removed in 2.0. Use {@link RequestMatcher} instead. + */ +final class RegexRequestMatcher implements RequestMatcher +{ + /** + * Matching regex. + * + * @var string + */ + private $regex; + + /** + * @param string $regex + */ + public function __construct($regex) + { + $this->regex = $regex; + } + + public function matches(RequestInterface $request) + { + return (bool) preg_match($this->regex, (string) $request->getUri()); + } +} diff --git a/admin/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php b/admin/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php new file mode 100644 index 0000000..4b28ccc --- /dev/null +++ b/admin/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php @@ -0,0 +1,76 @@ + + * @author Joel Wurtz + */ +final class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $host; + + /** + * @var array + */ + private $methods = []; + + /** + * @var string[] + */ + private $schemes = []; + + /** + * The regular expressions used for path or host must be specified without delimiter. + * You do not need to escape the forward slash / to match it. + * + * @param string|null $path Regular expression for the path + * @param string|null $host Regular expression for the hostname + * @param string|string[]|null $methods Method or list of methods to match + * @param string|string[]|null $schemes Scheme or list of schemes to match (e.g. http or https) + */ + public function __construct($path = null, $host = null, $methods = [], $schemes = []) + { + $this->path = $path; + $this->host = $host; + $this->methods = array_map('strtoupper', (array) $methods); + $this->schemes = array_map('strtolower', (array) $schemes); + } + + /** + * @api + */ + public function matches(RequestInterface $request) + { + if ($this->schemes && !in_array($request->getUri()->getScheme(), $this->schemes)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods)) { + return false; + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getUri()->getPath()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getUri()->getHost())) { + return false; + } + + return true; + } +} diff --git a/admin/vendor/php-http/message/src/Stream/BufferedStream.php b/admin/vendor/php-http/message/src/Stream/BufferedStream.php new file mode 100644 index 0000000..289ff8c --- /dev/null +++ b/admin/vendor/php-http/message/src/Stream/BufferedStream.php @@ -0,0 +1,237 @@ +stream = $stream; + $this->size = $stream->getSize(); + + if ($useFileBuffer) { + $this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+'); + } else { + $this->resource = fopen('php://memory', 'rw+'); + } + + if (false === $this->resource) { + throw new \RuntimeException('Cannot create a resource over temp or memory implementation'); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + + return $this->getContents(); + } catch (\Throwable $throwable) { + return ''; + } + } + + public function close(): void + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot close on a detached stream'); + } + + $this->stream->close(); + fclose($this->resource); + } + + public function detach() + { + if (null === $this->resource) { + return null; + } + + // Force reading the remaining data of the stream + $this->getContents(); + + $resource = $this->resource; + $this->stream->close(); + $this->stream = null; + $this->resource = null; + + return $resource; + } + + public function getSize(): ?int + { + if (null === $this->resource) { + return null; + } + + if (null === $this->size && $this->stream->eof()) { + return $this->written; + } + + return $this->size; + } + + public function tell(): int + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot tell on a detached stream'); + } + + $tell = ftell($this->resource); + if (false === $tell) { + throw new \RuntimeException('ftell failed'); + } + + return $tell; + } + + public function eof(): bool + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot call eof on a detached stream'); + } + + // We are at the end only when both our resource and underlying stream are at eof + return $this->stream->eof() && (ftell($this->resource) === $this->written); + } + + public function isSeekable(): bool + { + return null !== $this->resource; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot seek on a detached stream'); + } + + fseek($this->resource, $offset, $whence); + } + + public function rewind(): void + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot rewind on a detached stream'); + } + + rewind($this->resource); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new \RuntimeException('Cannot write on this stream'); + } + + public function isReadable(): bool + { + return null !== $this->resource; + } + + public function read(int $length): string + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot read on a detached stream'); + } + if ($length < 0) { + throw new \InvalidArgumentException('Can not read a negative amount of bytes'); + } + if (0 === $length) { + return ''; + } + + $read = ''; + + // First read from the resource + if (ftell($this->resource) !== $this->written) { + $read = fread($this->resource, $length); + } + if (false === $read) { + throw new \RuntimeException('Failed to read from resource'); + } + + $bytesRead = strlen($read); + + if ($bytesRead < $length) { + $streamRead = $this->stream->read($length - $bytesRead); + + // Write on the underlying stream what we read + $this->written += fwrite($this->resource, $streamRead); + $read .= $streamRead; + } + + return $read; + } + + public function getContents(): string + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot read on a detached stream'); + } + + $read = ''; + + while (!$this->eof()) { + $read .= $this->read(8192); + } + + return $read; + } + + public function getMetadata(?string $key = null) + { + if (null === $this->resource) { + if (null === $key) { + return []; + } + + return null; + } + + $metadata = stream_get_meta_data($this->resource); + + if (null === $key) { + return $metadata; + } + + if (!array_key_exists($key, $metadata)) { + return null; + } + + return $metadata[$key]; + } +} diff --git a/admin/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php b/admin/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php new file mode 100644 index 0000000..1b8c6f4 --- /dev/null +++ b/admin/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php @@ -0,0 +1,49 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Diactoros PSR-17 factory + */ +final class DiactorosStreamFactory implements StreamFactory +{ + public function createStream($body = null) + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (is_resource($body)) { + if (class_exists(LaminasStream::class)) { + return new LaminasStream($body); + } + + return new ZendStream($body); + } + + if (class_exists(LaminasStream::class)) { + $stream = new LaminasStream('php://memory', 'rw'); + } else { + $stream = new ZendStream('php://memory', 'rw'); + } + + if (null !== $body && '' !== $body) { + $stream->write((string) $body); + } + + return $stream; + } +} diff --git a/admin/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php b/admin/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php new file mode 100644 index 0000000..f74bc5b --- /dev/null +++ b/admin/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php @@ -0,0 +1,30 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Guzzle PSR-17 factory + */ +final class GuzzleStreamFactory implements StreamFactory +{ + public function createStream($body = null) + { + if (class_exists(Utils::class)) { + return Utils::streamFor($body); + } + + // legacy support for guzzle/psr7 1.* + return \GuzzleHttp\Psr7\stream_for($body); + } +} diff --git a/admin/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php b/admin/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php new file mode 100644 index 0000000..4cfa9ae --- /dev/null +++ b/admin/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php @@ -0,0 +1,40 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Slim PSR-17 factory + */ +final class SlimStreamFactory implements StreamFactory +{ + public function createStream($body = null) + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (is_resource($body)) { + return new Stream($body); + } + + $resource = fopen('php://memory', 'r+'); + $stream = new Stream($resource); + if (null !== $body && '' !== $body) { + $stream->write((string) $body); + } + + return $stream; + } +} diff --git a/admin/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php b/admin/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php new file mode 100644 index 0000000..84d5397 --- /dev/null +++ b/admin/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php @@ -0,0 +1,37 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Diactoros PSR-17 factory + */ +final class DiactorosUriFactory implements UriFactory +{ + public function createUri($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + if (class_exists(LaminasUri::class)) { + return new LaminasUri($uri); + } + + return new ZendUri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/admin/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php b/admin/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php new file mode 100644 index 0000000..deac230 --- /dev/null +++ b/admin/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php @@ -0,0 +1,31 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Guzzle PSR-17 factory + */ +final class GuzzleUriFactory implements UriFactory +{ + public function createUri($uri) + { + if (class_exists(Utils::class)) { + return Utils::uriFor($uri); + } + + return uri_for($uri); + } +} diff --git a/admin/vendor/php-http/message/src/UriFactory/SlimUriFactory.php b/admin/vendor/php-http/message/src/UriFactory/SlimUriFactory.php new file mode 100644 index 0000000..e4ff079 --- /dev/null +++ b/admin/vendor/php-http/message/src/UriFactory/SlimUriFactory.php @@ -0,0 +1,34 @@ + + * + * @deprecated This will be removed in php-http/message2.0. Consider using the official Slim PSR-17 factory + */ +final class SlimUriFactory implements UriFactory +{ + public function createUri($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return Uri::createFromString($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/admin/vendor/php-http/message/src/filters.php b/admin/vendor/php-http/message/src/filters.php new file mode 100644 index 0000000..15ed73d --- /dev/null +++ b/admin/vendor/php-http/message/src/filters.php @@ -0,0 +1,6 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/vendor/php-http/promise/README.md b/admin/vendor/php-http/promise/README.md new file mode 100644 index 0000000..8ee4aa6 --- /dev/null +++ b/admin/vendor/php-http/promise/README.md @@ -0,0 +1,48 @@ +# Promise + +[![Latest Version](https://img.shields.io/github/release/php-http/promise.svg?style=flat-square)](https://github.com/php-http/promise/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://github.com/php-http/promise/actions/workflows/tests.yml/badge.svg?branch=1.x)](https://github.com/php-http/promise/actions/workflows/tests.yml) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/promise.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/promise) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/promise.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/promise) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/promise.svg?style=flat-square)](https://packagist.org/packages/php-http/promise) + +**Promise used for asynchronous HTTP requests.** + +**Note:** This will eventually be removed/deprecated and replaced with the upcoming Promise PSR. + + +## Install + +Via Composer + +``` bash +$ composer require php-http/promise +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/components/promise.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/admin/vendor/php-http/promise/composer.json b/admin/vendor/php-http/promise/composer.json new file mode 100644 index 0000000..184fd60 --- /dev/null +++ b/admin/vendor/php-http/promise/composer.json @@ -0,0 +1,33 @@ +{ + "name": "php-http/promise", + "description": "Promise used for asynchronous HTTP requests", + "license": "MIT", + "keywords": ["promise"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php" : "^7.1 || ^8.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage" : "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.yml.ci" + } +} diff --git a/admin/vendor/php-http/promise/phpstan.neon.dist b/admin/vendor/php-http/promise/phpstan.neon.dist new file mode 100644 index 0000000..f8b94cf --- /dev/null +++ b/admin/vendor/php-http/promise/phpstan.neon.dist @@ -0,0 +1,6 @@ +parameters: + level: max + checkMissingIterableValueType: false + treatPhpDocTypesAsCertain: false + paths: + - src diff --git a/admin/vendor/php-http/promise/src/FulfilledPromise.php b/admin/vendor/php-http/promise/src/FulfilledPromise.php new file mode 100644 index 0000000..ed77d46 --- /dev/null +++ b/admin/vendor/php-http/promise/src/FulfilledPromise.php @@ -0,0 +1,51 @@ + + */ +final class FulfilledPromise implements Promise +{ + /** + * @var mixed + */ + private $result; + + /** + * @param mixed $result + */ + public function __construct($result) + { + $this->result = $result; + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return new self($onFulfilled($this->result)); + } catch (\Exception $e) { + return new RejectedPromise($e); + } + } + + public function getState() + { + return Promise::FULFILLED; + } + + public function wait($unwrap = true) + { + if ($unwrap) { + return $this->result; + } + + return null; + } +} diff --git a/admin/vendor/php-http/promise/src/Promise.php b/admin/vendor/php-http/promise/src/Promise.php new file mode 100644 index 0000000..e240311 --- /dev/null +++ b/admin/vendor/php-http/promise/src/Promise.php @@ -0,0 +1,69 @@ + + * @author Márk Sági-Kazár + */ +interface Promise +{ + /** + * Promise has not been fulfilled or rejected. + */ + const PENDING = 'pending'; + + /** + * Promise has been fulfilled. + */ + const FULFILLED = 'fulfilled'; + + /** + * Promise has been rejected. + */ + const REJECTED = 'rejected'; + + /** + * Adds behavior for when the promise is resolved or rejected (response will be available, or error happens). + * + * If you do not care about one of the cases, you can set the corresponding callable to null + * The callback will be called when the value arrived and never more than once. + * + * @param callable|null $onFulfilled called when a response will be available + * @param callable|null $onRejected called when an exception occurs + * + * @return Promise a new resolved promise with value of the executed callback (onFulfilled / onRejected) + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null); + + /** + * Returns the state of the promise, one of PENDING, FULFILLED or REJECTED. + * + * @return string + */ + public function getState(); + + /** + * Wait for the promise to be fulfilled or rejected. + * + * When this method returns, the request has been resolved and if callables have been + * specified, the appropriate one has terminated. + * + * When $unwrap is true (the default), the response is returned, or the exception thrown + * on failure. Otherwise, nothing is returned or thrown. + * + * @param bool $unwrap Whether to return resolved value / throw reason or not + * + * @return ($unwrap is true ? mixed : null) Resolved value, null if $unwrap is set to false + * + * @throws \Throwable the rejection reason if $unwrap is set to true and the request failed + */ + public function wait($unwrap = true); +} diff --git a/admin/vendor/php-http/promise/src/RejectedPromise.php b/admin/vendor/php-http/promise/src/RejectedPromise.php new file mode 100644 index 0000000..8c046ce --- /dev/null +++ b/admin/vendor/php-http/promise/src/RejectedPromise.php @@ -0,0 +1,48 @@ + + */ +final class RejectedPromise implements Promise +{ + /** + * @var \Throwable + */ + private $exception; + + public function __construct(\Throwable $exception) + { + $this->exception = $exception; + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return new FulfilledPromise($onRejected($this->exception)); + } catch (\Exception $e) { + return new self($e); + } + } + + public function getState() + { + return Promise::REJECTED; + } + + public function wait($unwrap = true) + { + if ($unwrap) { + throw $this->exception; + } + + return null; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-common/.github/dependabot.yml b/admin/vendor/phpdocumentor/reflection-common/.github/dependabot.yml new file mode 100644 index 0000000..c630ffa --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/admin/vendor/phpdocumentor/reflection-common/.github/workflows/push.yml b/admin/vendor/phpdocumentor/reflection-common/.github/workflows/push.yml new file mode 100644 index 0000000..484410e --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/.github/workflows/push.yml @@ -0,0 +1,223 @@ +on: + push: + branches: + - 2.x + pull_request: +name: Qa workflow +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: composer + uses: docker://composer + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: install --no-interaction --prefer-dist --optimize-autoloader + + - name: Install phive + run: make install-phive + + - name: Install PHAR dependencies + run: tools/phive.phar --no-progress install --copy --trust-gpg-keys 4AA394086372C20A,8A03EA3B385DBAA1 --force-accept-unsigned + + phpunit-with-coverage: + runs-on: ubuntu-latest + name: Unit tests + needs: setup + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + coverage: pcov + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ubuntu-latest-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ubuntu-latest-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Run PHPUnit + run: php tools/phpunit + + phpunit: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: + - ubuntu-latest + - windows-latest + - macOS-latest + php-versions: ['7.2', '7.3', '7.4', '8.0'] + name: Unit tests for PHP version ${{ matrix.php-versions }} on ${{ matrix.operating-system }} + needs: + - setup + - phpunit-with-coverage + steps: + - uses: actions/checkout@v2 + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Run PHPUnit + continue-on-error: true + run: php tools/phpunit + + codestyle: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: Code style check + uses: phpDocumentor/coding-standard@latest + with: + args: -s + + phpstan: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: PHPStan + uses: phpDocumentor/phpstan-ga@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: analyse src --configuration phpstan.neon + + psalm: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + tools: psalm + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Psalm + run: psalm --output-format=github + + bc_check: + name: BC Check + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: fetch tags + run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: Roave BC Check + uses: docker://nyholm/roave-bc-check-ga diff --git a/admin/vendor/phpdocumentor/reflection-common/LICENSE b/admin/vendor/phpdocumentor/reflection-common/LICENSE new file mode 100644 index 0000000..ed6926c --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 phpDocumentor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/admin/vendor/phpdocumentor/reflection-common/README.md b/admin/vendor/phpdocumentor/reflection-common/README.md new file mode 100644 index 0000000..70f830d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/README.md @@ -0,0 +1,11 @@ +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Qa workflow](https://github.com/phpDocumentor/ReflectionCommon/workflows/Qa%20workflow/badge.svg) +[![Coveralls Coverage](https://img.shields.io/coveralls/github/phpDocumentor/ReflectionCommon.svg)](https://coveralls.io/github/phpDocumentor/ReflectionCommon?branch=master) +[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/phpDocumentor/ReflectionCommon.svg)](https://scrutinizer-ci.com/g/phpDocumentor/ReflectionCommon/?branch=master) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/phpDocumentor/ReflectionCommon.svg)](https://scrutinizer-ci.com/g/phpDocumentor/ReflectionCommon/?branch=master) +[![Stable Version](https://img.shields.io/packagist/v/phpDocumentor/Reflection-Common.svg)](https://packagist.org/packages/phpDocumentor/Reflection-Common) +[![Unstable Version](https://img.shields.io/packagist/vpre/phpDocumentor/Reflection-Common.svg)](https://packagist.org/packages/phpDocumentor/Reflection-Common) + + +ReflectionCommon +================ diff --git a/admin/vendor/phpdocumentor/reflection-common/composer.json b/admin/vendor/phpdocumentor/reflection-common/composer.json new file mode 100644 index 0000000..4d128b4 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/composer.json @@ -0,0 +1,28 @@ +{ + "name": "phpdocumentor/reflection-common", + "keywords": ["phpdoc", "phpDocumentor", "reflection", "static analysis", "FQSEN"], + "homepage": "http://www.phpdoc.org", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "license": "MIT", + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "autoload" : { + "psr-4" : { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "require-dev": { + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + } +} diff --git a/admin/vendor/phpdocumentor/reflection-common/src/Element.php b/admin/vendor/phpdocumentor/reflection-common/src/Element.php new file mode 100644 index 0000000..8923e4f --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/src/Element.php @@ -0,0 +1,30 @@ +fqsen = $fqsen; + + if (isset($matches[2])) { + $this->name = $matches[2]; + } else { + $matches = explode('\\', $fqsen); + $name = end($matches); + assert(is_string($name)); + $this->name = trim($name, '()'); + } + } + + /** + * converts this class to string. + */ + public function __toString() : string + { + return $this->fqsen; + } + + /** + * Returns the name of the element without path. + */ + public function getName() : string + { + return $this->name; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-common/src/Location.php b/admin/vendor/phpdocumentor/reflection-common/src/Location.php new file mode 100644 index 0000000..177deed --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/src/Location.php @@ -0,0 +1,53 @@ +lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; + } + + /** + * Returns the line number that is covered by this location. + */ + public function getLineNumber() : int + { + return $this->lineNumber; + } + + /** + * Returns the column number (character position on a line) for this location object. + */ + public function getColumnNumber() : int + { + return $this->columnNumber; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-common/src/Project.php b/admin/vendor/phpdocumentor/reflection-common/src/Project.php new file mode 100644 index 0000000..57839fd --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-common/src/Project.php @@ -0,0 +1,25 @@ +create($docComment); +``` + +The `create` method will yield an object of type `\phpDocumentor\Reflection\DocBlock` +whose methods can be queried: + +```php +// Contains the summary for this DocBlock +$summary = $docblock->getSummary(); + +// Contains \phpDocumentor\Reflection\DocBlock\Description object +$description = $docblock->getDescription(); + +// You can either cast it to string +$description = (string) $docblock->getDescription(); + +// Or use the render method to get a string representation of the Description. +$description = $docblock->getDescription()->render(); +``` + +> For more examples it would be best to review the scripts in the [`/docs/examples` folder](/docs/examples). diff --git a/admin/vendor/phpdocumentor/reflection-docblock/composer.json b/admin/vendor/phpdocumentor/reflection-docblock/composer.json new file mode 100644 index 0000000..28edc60 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/composer.json @@ -0,0 +1,58 @@ +{ + "name": "phpdocumentor/reflection-docblock", + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "phpdocumentor/type-resolver": "^2.0", + "webmozart/assert": "^1.9.1 || ^2", + "phpdocumentor/reflection-common": "^2.2", + "ext-filter": "*", + "phpstan/phpdoc-parser": "^2.0", + "doctrine/deprecations": "^1.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": ["tests/unit", "tests/integration"] + } + }, + "config": { + "platform": { + "php":"7.4.0" + }, + "allow-plugins": { + "phpstan/extension-installer": true + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php new file mode 100644 index 0000000..90d8066 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php @@ -0,0 +1,228 @@ +summary = $summary; + $this->description = $description ?: new DocBlock\Description(''); + foreach ($tags as $tag) { + $this->addTag($tag); + } + + $this->context = $context; + $this->location = $location; + + $this->isTemplateEnd = $isTemplateEnd; + $this->isTemplateStart = $isTemplateStart; + } + + public function getSummary(): string + { + return $this->summary; + } + + public function getDescription(): DocBlock\Description + { + return $this->description; + } + + /** + * Returns the current context. + */ + public function getContext(): ?Types\Context + { + return $this->context; + } + + /** + * Returns the current location. + */ + public function getLocation(): ?Location + { + return $this->location; + } + + /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + */ + public function isTemplateStart(): bool + { + return $this->isTemplateStart; + } + + /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + */ + public function isTemplateEnd(): bool + { + return $this->isTemplateEnd; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return Tag[] + */ + public function getTagsByName(string $name): array + { + $result = []; + + foreach ($this->getTags() as $tag) { + if ($tag->getName() !== $name) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Returns an array of tags with type matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return TagWithType[] + */ + public function getTagsWithTypeByName(string $name): array + { + $result = []; + + foreach ($this->getTagsByName($name) as $tag) { + if (!$tag instanceof TagWithType) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. + */ + public function hasTag(string $name): bool + { + foreach ($this->getTags() as $tag) { + if ($tag->getName() === $name) { + return true; + } + } + + return false; + } + + /** + * Remove a tag from this DocBlock. + * + * @param Tag $tagToRemove The tag to remove. + */ + public function removeTag(Tag $tagToRemove): void + { + foreach ($this->tags as $key => $tag) { + if ($tag === $tagToRemove) { + unset($this->tags[$key]); + break; + } + } + } + + /** + * Adds a tag to this DocBlock. + * + * @param Tag $tag The tag to add. + */ + private function addTag(Tag $tag): void + { + $this->tags[] = $tag; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php new file mode 100644 index 0000000..a188ae3 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php @@ -0,0 +1,118 @@ +create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Tags\Formatter for the formatting of the body and tags. + */ +class Description +{ + private string $bodyTemplate; + + /** @var Tag[] */ + private array $tags; + + /** + * Initializes a Description with its body (template) and a listing of the tags used in the body template. + * + * @param Tag[] $tags + */ + public function __construct(string $bodyTemplate, array $tags = []) + { + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; + } + + /** + * Returns the body template. + */ + public function getBodyTemplate(): string + { + return $this->bodyTemplate; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. + */ + public function render(?Formatter $formatter = null): string + { + if ($this->tags === []) { + return vsprintf($this->bodyTemplate, []); + } + + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + + return vsprintf($this->bodyTemplate, $tags); + } + + /** + * Returns a plain string representation of this description. + */ + public function __toString(): string + { + return $this->render(); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php new file mode 100644 index 0000000..6915c16 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @@ -0,0 +1,179 @@ +tagFactory = $tagFactory; + } + + /** + * Returns the parsed text of this description. + */ + public function create(string $contents, ?TypeContext $context = null): Description + { + $tokens = $this->lex($contents); + $count = count($tokens); + $tagCount = 0; + $tags = []; + + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); + } + + return new Description(implode('', $tokens), $tags); + } + + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex(string $contents): array + { + $contents = $this->removeSuperfluousStartingWhitespace($contents); + + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === false) { + return [$contents]; + } + + return Utils::pregSplit( + '/\{ + # "{@}" and "{@*}" are not a valid inline tags. This ensures that we do not treat them as one, but treat + # them literally. + (?!(?:@\}|@\*\}) ) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \}/Sux', + $contents, + 0, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + */ + private function removeSuperfluousStartingWhitespace(string $contents): string + { + $lines = Utils::pregSplit("/\r\n?|\n/", $contents); + + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + // lines with a no length do not count as they are not indented at all + if (trim($lines[$i]) === '') { + continue; + } + + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + + return implode("\n", $lines); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php new file mode 100644 index 0000000..0fb24a6 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php @@ -0,0 +1,158 @@ +getFilePath(); + + $file = $this->getExampleFileContents($filename); + if ($file === null) { + return sprintf('** File not found : %s **', $filename); + } + + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } + + /** + * Registers the project's root directory where an 'examples' folder can be expected. + */ + public function setSourceDirectory(string $directory = ''): void + { + $this->sourceDirectory = $directory; + } + + /** + * Returns the project's root directory where an 'examples' folder can be expected. + */ + public function getSourceDirectory(): string + { + return $this->sourceDirectory; + } + + /** + * Registers a series of directories that may contain examples. + * + * @param string[] $directories + */ + public function setExampleDirectories(array $directories): void + { + $this->exampleDirectories = $directories; + } + + /** + * Returns a series of directories that may contain examples. + * + * @return string[] + */ + public function getExampleDirectories(): array + { + return $this->exampleDirectories; + } + + /** + * Attempts to find the requested example file and returns its contents or null if no file was found. + * + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: + * + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @return string[] all lines of the example file + */ + private function getExampleFileContents(string $filename): ?array + { + $normalizedPath = null; + + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; + } + } + + if ($normalizedPath === null) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + + $lines = $normalizedPath !== null && is_readable($normalizedPath) ? file($normalizedPath) : false; + + return $lines !== false ? $lines : null; + } + + /** + * Get example filepath based on the example directory inside your project. + */ + private function getExamplePathFromExampleDirectory(string $file): string + { + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; + } + + /** + * Returns a path to the example file in the given directory.. + */ + private function constructExamplePath(string $directory, string $file): string + { + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; + } + + /** + * Get example filepath based on sourcecode. + */ + private function getExamplePathFromSource(string $file): string + { + return sprintf( + '%s%s%s', + trim($this->getSourceDirectory(), '\\/'), + DIRECTORY_SEPARATOR, + trim($file, '"') + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php new file mode 100644 index 0000000..2c257dd --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php @@ -0,0 +1,156 @@ +indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + $this->lineEnding = $lineEnding; + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock $docblock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock): string + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength !== null ? $this->lineLength - strlen($indent) - 3 : null; + + $text = $this->removeTrailingSpaces( + $indent, + $this->addAsterisksForEachLine( + $indent, + $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) + ) + ); + + $comment = $firstIndent . "/**\n"; + if ($text) { + $comment .= $indent . ' * ' . $text . "\n"; + $comment .= $indent . " *\n"; + } + + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + + return str_replace("\n", $this->lineEnding, $comment . $indent . ' */'); + } + + private function removeTrailingSpaces(string $indent, string $text): string + { + return str_replace( + sprintf("\n%s * \n", $indent), + sprintf("\n%s *\n", $indent), + $text + ); + } + + private function addAsterisksForEachLine(string $indent, string $text): string + { + return str_replace( + "\n", + sprintf("\n%s * ", $indent), + $text + ); + } + + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength): string + { + $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() + : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + + return $text; + } + + return $text; + } + + private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment): string + { + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + + $tagText = str_replace( + "\n", + sprintf("\n%s * ", $indent), + $tagText + ); + + $comment .= sprintf("%s * %s\n", $indent, $tagText); + } + + return $comment; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php new file mode 100644 index 0000000..1d9e5fe --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,392 @@ + Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + */ +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; + + /** + * @var array|Tag|Factory> An array with a tag as a key, and an + * FQCN to a class that handles it as an array value. + */ + private array $tagHandlerMappings = [ + 'author' => Author::class, + 'covers' => Covers::class, + 'deprecated' => Deprecated::class, + 'link' => LinkTag::class, + 'see' => SeeTag::class, + 'since' => Since::class, + 'source' => Source::class, + 'uses' => Uses::class, + 'version' => Version::class, + ]; + + /** + * @var array> An array with an annotation as a key, and an + * FQCN to a class that handles it as an array value. + */ + private array $annotationMappings = []; + + /** + * @var ReflectionParameter[][] a lazy-loading cache containing parameters + * for each tagHandler that has been used. + */ + private array $tagHandlerParameterCache = []; + + private FqsenResolver $fqsenResolver; + + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private array $serviceLocator = []; + + private function __construct(FqsenResolver $fqsenResolver) + { + $this->fqsenResolver = $fqsenResolver; + + $this->addService($fqsenResolver, FqsenResolver::class); + } + + /** + * Initialize this tag factory with the means to resolve an FQSEN. + * + * @see self::registerTagHandler() to add a new tag handler to the existing default list. + */ + public static function createInstance(FqsenResolver $fqsenResolver): self + { + $tagFactory = new self($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + + $typeResolver = new TypeResolver($fqsenResolver); + + $phpstanTagFactory = new AbstractPHPStanFactory( + new ParamFactory($typeResolver, $descriptionFactory), + new VarFactory($typeResolver, $descriptionFactory), + new ReturnFactory($typeResolver, $descriptionFactory), + new PropertyFactory($typeResolver, $descriptionFactory), + new PropertyReadFactory($typeResolver, $descriptionFactory), + new PropertyWriteFactory($typeResolver, $descriptionFactory), + new MethodFactory($typeResolver, $descriptionFactory), + new MixinFactory($typeResolver, $descriptionFactory), + new ImplementsFactory($typeResolver, $descriptionFactory), + new ExtendsFactory($typeResolver, $descriptionFactory), + new TemplateFactory($typeResolver, $descriptionFactory), + new TemplateCovariantFactory($typeResolver, $descriptionFactory), + new ThrowsFactory($typeResolver, $descriptionFactory), + ); + + $tagFactory->addService($descriptionFactory); + $tagFactory->addService($typeResolver); + $tagFactory->registerTagHandler('param', $phpstanTagFactory); + $tagFactory->registerTagHandler('var', $phpstanTagFactory); + $tagFactory->registerTagHandler('return', $phpstanTagFactory); + $tagFactory->registerTagHandler('property', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-read', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-write', $phpstanTagFactory); + $tagFactory->registerTagHandler('method', $phpstanTagFactory); + $tagFactory->registerTagHandler('mixin', $phpstanTagFactory); + $tagFactory->registerTagHandler('extends', $phpstanTagFactory); + $tagFactory->registerTagHandler('implements', $phpstanTagFactory); + $tagFactory->registerTagHandler('template', $phpstanTagFactory); + $tagFactory->registerTagHandler('template-covariant', $phpstanTagFactory); + $tagFactory->registerTagHandler('template-extends', $phpstanTagFactory); + $tagFactory->registerTagHandler('template-implements', $phpstanTagFactory); + $tagFactory->registerTagHandler('throws', $phpstanTagFactory); + + return $tagFactory; + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + if (!$context) { + $context = new TypeContext(''); + } + + [$tagName, $tagBody] = $this->extractTagParts($tagLine); + + return $this->createTag(trim($tagBody), $tagName, $context); + } + + /** + * @param mixed $value + */ + public function addParameter(string $name, $value): void + { + $this->serviceLocator[$name] = $value; + } + + public function addService(object $service, ?string $alias = null): void + { + $this->serviceLocator[$alias ?? get_class($service)] = $service; + } + + /** {@inheritDoc} */ + public function registerTagHandler(string $tagName, $handler): void + { + Assert::stringNotEmpty($tagName); + if (strpos($tagName, '\\') !== false && $tagName[0] !== '\\') { + throw new InvalidArgumentException( + 'A namespaced tag must have a leading backslash as it must be fully qualified' + ); + } + + if (is_object($handler)) { + Assert::isInstanceOf($handler, Factory::class); + $this->tagHandlerMappings[$tagName] = $handler; + + return; + } + + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); + $this->tagHandlerMappings[$tagName] = $handler; + } + + /** + * Extracts all components for a tag. + * + * @return string[] + */ + private function extractTagParts(string $tagLine): array + { + $matches = []; + if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) { + throw new InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + return array_slice($matches, 1); + } + + /** + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. + */ + private function createTag(string $body, string $name, TypeContext $context): Tag + { + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring( + $this->fetchParametersForHandlerFactoryMethod($handlerClassName), + $this->getServiceLocatorWithDynamicParameters($context, $name, $body) + ); + + if (array_key_exists('tagLine', $arguments)) { + $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + } + + try { + $callable = [$handlerClassName, 'create']; + Assert::isCallable($callable); + /** @phpstan-var callable(string): ?Tag $callable */ + $tag = call_user_func_array($callable, $arguments); + + return $tag ?? InvalidTag::create($body, $name); + } catch (InvalidArgumentException $e) { + return InvalidTag::create($body, $name)->withError($e); + } + } + + /** + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @return class-string|Tag|Factory + */ + private function findHandlerClassName(string $tagName, TypeContext $context) + { + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); + if (isset($this->annotationMappings[$tagName])) { + $handlerClassName = $this->annotationMappings[$tagName]; + } + } + + return $handlerClassName; + } + + /** + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. + * + * @param ReflectionParameter[] $parameters + * @param mixed[] $locator + * + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. + */ + private function getArgumentsForParametersFromWiring(array $parameters, array $locator): array + { + $arguments = []; + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $typeHint = null; + if ($type instanceof ReflectionNamedType) { + $typeHint = $type->getName(); + if ($typeHint === 'self') { + $declaringClass = $parameter->getDeclaringClass(); + if ($declaringClass !== null) { + $typeHint = $declaringClass->getName(); + } + } + } + + $parameterName = $parameter->getName(); + if (isset($locator[$typeHint ?? ''])) { + $arguments[$parameterName] = $locator[$typeHint ?? '']; + continue; + } + + if (isset($locator[$parameterName])) { + $arguments[$parameterName] = $locator[$parameterName]; + continue; + } + + $arguments[$parameterName] = null; + } + + return $arguments; + } + + /** + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. + * + * @param class-string|Tag|Factory $handler + * + * @return ReflectionParameter[] + */ + private function fetchParametersForHandlerFactoryMethod($handler): array + { + $handlerClassName = is_object($handler) ? get_class($handler) : $handler; + + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + } + + return $this->tagHandlerParameterCache[$handlerClassName]; + } + + /** + * Returns a copy of this class' Service Locator with added dynamic parameters, + * such as the tag's name, body and Context. + * + * @param TypeContext $context The Context (namespace and aliases) that may be + * passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be + * passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be + * passed onto the factory method of the Tag class. + * + * @return mixed[] + */ + private function getServiceLocatorWithDynamicParameters( + TypeContext $context, + string $tagName, + string $tagBody + ): array { + return array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + TypeContext::class => $context, + ] + ); + } + + /** + * Returns whether the given tag belongs to an annotation. + * + * @todo this method should be populated once we implement Annotation notation support. + */ + private function isAnnotation(string $tagContent): bool + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php new file mode 100644 index 0000000..7cf07b4 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php @@ -0,0 +1,31 @@ +|Factory $handler FQCN of handler. + * + * @throws InvalidArgumentException If the tag name is not a string. + * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but + * does not start with a backslash. + * @throws InvalidArgumentException If the handler is not a string. + * @throws InvalidArgumentException If the handler is not an existing class. + * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + */ + public function registerTagHandler(string $tagName, $handler): void; +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php new file mode 100644 index 0000000..e604ac8 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php @@ -0,0 +1,102 @@ +authorName = $authorName; + $this->authorEmail = $authorEmail; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName(): string + { + return $this->authorName; + } + + /** + * Returns the author's email. + * + * @return string The author's email. + */ + public function getEmail(): string + { + return $this->authorEmail; + } + + /** + * Returns this tag in string form. + */ + public function __toString(): string + { + if ($this->authorEmail) { + $authorEmail = '<' . $this->authorEmail . '>'; + } else { + $authorEmail = ''; + } + + $authorName = $this->authorName; + + return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); + } + + /** + * Attempts to create a new Author object based on the tag body. + */ + public static function create(string $body): ?self + { + $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; + } + + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + + return new static($authorName, $email); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php new file mode 100644 index 0000000..98b0d88 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php @@ -0,0 +1,53 @@ +name; + } + + public function getDescription(): ?Description + { + return $this->description; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php new file mode 100644 index 0000000..ba2384f --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php @@ -0,0 +1,99 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?FqsenResolver $resolver = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + Assert::notNull($resolver); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php new file mode 100644 index 0000000..8ae8322 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php @@ -0,0 +1,108 @@ +version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return new static( + null, + $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null + ); + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php new file mode 100644 index 0000000..06ecf8c --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php @@ -0,0 +1,197 @@ +filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + if ($content !== null) { + $this->content = trim($content); + } + + $this->isURI = $isURI; + } + + public function getContent(): string + { + if ($this->content === null || $this->content === '') { + $filePath = $this->filePath; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) + ? str_replace('%2F', '/', rawurlencode($this->filePath)) + : $this->filePath; + } + + return trim($filePath); + } + + return $this->content; + } + + public function getDescription(): ?string + { + return $this->content; + } + + public static function create(string $body): ?Tag + { + // File component: File path in quotes or File URI / Source information + if (!preg_match('/^\s*(?:(\"[^\"]+\")|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { + return null; + } + + $filePath = null; + $fileUri = null; + if (array_key_exists(1, $matches) && $matches[1] !== '') { + $filePath = $matches[1]; + } else { + $fileUri = array_key_exists(2, $matches) ? $matches[2] : ''; + } + + $startingLine = 1; + $lineCount = 0; + $description = null; + + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int) $contentMatches[1]; + if (isset($contentMatches[2])) { + $lineCount = (int) $contentMatches[2]; + } + + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } + } + + return new static( + $filePath ?? ($fileUri ?? ''), + $fileUri !== null, + $startingLine, + $lineCount, + $description + ); + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath(): string + { + return trim($this->filePath, '"'); + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + $filePath = $this->filePath; + $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; + $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; + $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; + $content = (string) $this->content; + + return $filePath + . ($startingLine !== '' + ? ($filePath !== '' ? ' ' : '') . $startingLine + : '') + . ($lineCount !== '' + ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount + : '') + . ($content !== '' + ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content + : ''); + } + + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + */ + private function isUriRelative(string $uri): bool + { + return strpos($uri, ':') === false; + } + + public function getStartingLine(): int + { + return $this->startingLine; + } + + public function getLineCount(): int + { + return $this->lineCount; + } + + public function getName(): string + { + return 'example'; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php new file mode 100644 index 0000000..583d791 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php @@ -0,0 +1,30 @@ +name = 'extends'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php new file mode 100644 index 0000000..770a09a --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -0,0 +1,130 @@ + true, 'lines' => true]); + $this->lexer = new Lexer($config); + $constParser = new ConstExprParser($config); + $this->parser = new PhpDocParser( + $config, + new TypeParser($config, $constParser), + $constParser + ); + + $this->factories = $factories; + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + try { + $tokens = $this->tokenizeLine($tagLine); + $ast = $this->parser->parseTag($tokens); + if (property_exists($ast->value, 'description') === true) { + $ast->value->setAttribute( + 'description', + rtrim($ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END), "\n") + ); + } + } catch (ParserException $e) { + return InvalidTag::create($tagLine, '')->withError($e); + } + + if ($context === null) { + $context = new TypeContext(''); + } + + try { + foreach ($this->factories as $factory) { + if ($factory->supports($ast, $context)) { + return $factory->create($ast, $context); + } + } + } catch (RuntimeException $e) { + return InvalidTag::create((string) $ast->value, 'method')->withError($e); + } catch (ParserException $e) { + return InvalidTag::create((string) $ast->value, $ast->name)->withError($e); + } + + return InvalidTag::create( + (string) $ast->value, + $ast->name + ); + } + + /** + * Solve the issue with the lexer not tokenizing the line correctly + * + * This method is a workaround for the lexer that includes newline tokens with spaces. For + * phpstan this isn't an issue, as it doesn't do a lot of things with the indentation of descriptions. + * But for us is important to keep the indentation of the descriptions, so we need to fix the lexer output. + */ + private function tokenizeLine(string $tagLine): TokenIterator + { + // Prefix continuation lines with "* ", which is consumed by the phpstan parser as TOKEN_PHPDOC_EOL. + $tagLine = str_replace("\n", "\n* ", $tagLine); + $tokens = $this->lexer->tokenize($tagLine . "\n"); + $fixed = []; + foreach ($tokens as $token) { + if ($token[Lexer::TYPE_OFFSET] === Lexer::TOKEN_PHPDOC_EOL) { + // Strip "* " prefix (and other horizontal whitespace) again so it doesn't and up in the + // description when we joinUntil() in create(). + $fixed[] = [ + Lexer::VALUE_OFFSET => trim($token[Lexer::VALUE_OFFSET], "* \t"), + Lexer::TYPE_OFFSET => $token[Lexer::TYPE_OFFSET], + Lexer::LINE_OFFSET => $token[Lexer::LINE_OFFSET] ?? 0, + ]; + + continue; + } + + $fixed[] = $token; + } + + return new TokenIterator($fixed); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php new file mode 100644 index 0000000..9a5528d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ExtendsTagValueNode && $node->name === '@extends'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ExtendsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Extends_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php new file mode 100644 index 0000000..190d3ff --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php @@ -0,0 +1,41 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ImplementsTagValueNode && $node->name === '@implements'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ImplementsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Implements_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php new file mode 100644 index 0000000..845a2da --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php @@ -0,0 +1,82 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, MethodTagValueNode::class); + + return new Method( + $tagValue->methodName, + array_map( + function (MethodTagValueParameterNode $param) use ($context) { + return new MethodParameter( + trim($param->parameterName, '$'), + $param->type === null ? new Mixed_() : $this->typeResolver->createType( + $param->type, + $context + ), + $param->isReference, + $param->isVariadic, + $param->defaultValue === null ? + MethodParameter::NO_DEFAULT_VALUE : + (string) $param->defaultValue + ); + }, + $tagValue->parameters + ), + $this->createReturnType($tagValue, $context), + $tagValue->isStatic, + $this->descriptionFactory->create($tagValue->description, $context), + false, + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof MethodTagValueNode; + } + + private function createReturnType(MethodTagValueNode $tagValue, Context $context): Type + { + if ($tagValue->returnType === null) { + return new Void_(); + } + + return $this->typeResolver->createType($tagValue->returnType, $context); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php new file mode 100644 index 0000000..d98237c --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php @@ -0,0 +1,100 @@ +{$method}($defaultValue); + } + + return ''; + } + + private function formatDouble(float $defaultValue): string + { + return var_export($defaultValue, true); + } + + /** + * @param mixed $defaultValue + */ + private function formatNull($defaultValue): string + { + return 'null'; + } + + private function formatInteger(int $defaultValue): string + { + return var_export($defaultValue, true); + } + + private function formatString(string $defaultValue): string + { + return var_export($defaultValue, true); + } + + private function formatBoolean(bool $defaultValue): string + { + return var_export($defaultValue, true); + } + + /** + * @param array<(array|int|float|bool|string|object|null)> $defaultValue + */ + private function formatArray(array $defaultValue): string + { + $formatedValue = '['; + + foreach ($defaultValue as $key => $value) { + $method = 'format' . ucfirst(gettype($value)); + if (!method_exists($this, $method)) { + continue; + } + + $formatedValue .= $this->{$method}($value); + + if ($key === array_key_last($defaultValue)) { + continue; + } + + $formatedValue .= ','; + } + + return $formatedValue . ']'; + } + + private function formatObject(object $defaultValue): string + { + return 'new ' . get_class($defaultValue) . '()'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MixinFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MixinFactory.php new file mode 100644 index 0000000..03e4421 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MixinFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, MixinTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Mixin( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof MixinTagValueNode; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php new file mode 100644 index 0000000..cf04a06 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -0,0 +1,16 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + + if ($tagValue instanceof InvalidTagValueNode) { + return InvalidTag::create($tagValue->value, 'param')->withError( + ParserException::from($tagValue->exception) + ); + } + + Assert::isInstanceOfAny( + $tagValue, + [ + ParamTagValueNode::class, + TypelessParamTagValueNode::class, + ] + ); + + if (($tagValue->type ?? null) instanceof OffsetAccessTypeNode) { + return InvalidTag::create( + (string) $tagValue, + 'param' + ); + } + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Param( + trim($tagValue->parameterName, '$'), + $this->typeResolver->createType($tagValue->type ?? new IdentifierTypeNode('mixed'), $context), + $tagValue->isVariadic, + $this->descriptionFactory->create($description, $context), + $tagValue->isReference + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ParamTagValueNode + || $node->value instanceof TypelessParamTagValueNode + || $node->name === '@param'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php new file mode 100644 index 0000000..b744ed0 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Property( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php new file mode 100644 index 0000000..b0898aa --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php @@ -0,0 +1,54 @@ +typeResolver = $typeResolver; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new PropertyRead( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-read'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php new file mode 100644 index 0000000..749b1ed --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new PropertyWrite( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-write'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php new file mode 100644 index 0000000..4a17dc2 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ReturnTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Return_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ReturnTagValueNode; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateCovariantFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateCovariantFactory.php new file mode 100644 index 0000000..329a997 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateCovariantFactory.php @@ -0,0 +1,53 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof TemplateTagValueNode && $node->name === '@template-covariant'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, TemplateTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new TemplateCovariant( + $this->typeResolver->createType(new IdentifierTypeNode($tagValue->name), $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php new file mode 100644 index 0000000..9e4b3c8 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php @@ -0,0 +1,56 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + + Assert::isInstanceOf($tagValue, TemplateTagValueNode::class); + $name = $tagValue->name; + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Template( + $name, + $this->typeResolver->createType($tagValue->bound, $context), + $this->typeResolver->createType($tagValue->default, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof TemplateTagValueNode && $node->name === '@template'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ThrowsFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ThrowsFactory.php new file mode 100644 index 0000000..e547adc --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ThrowsFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ThrowsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Throws( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ThrowsTagValueNode; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php new file mode 100644 index 0000000..479ceb2 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, VarTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Var_( + trim($tagValue->variableName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof VarTagValueNode; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php new file mode 100644 index 0000000..36b9983 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php @@ -0,0 +1,24 @@ +maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag): string + { + return '@' . $tag->getName() . + str_repeat( + ' ', + $this->maxLen - strlen($tag->getName()) + 1 + ) . + $tag; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php new file mode 100644 index 0000000..2afdfe5 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php @@ -0,0 +1,30 @@ +getName() . ' ' . $tag); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php new file mode 100644 index 0000000..1445b16 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php @@ -0,0 +1,89 @@ +validateTagName($name); + + $this->name = $name; + $this->description = $description; + } + + /** + * Creates a new tag that represents any unknown tag type. + * + * @return static + */ + public static function create( + string $body, + string $name = '', + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + + $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; + + return new static($name, $description); + } + + /** + * Returns the tag as a serialized string + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $description; + } + + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + */ + private function validateTagName(string $name): void + { + if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new InvalidArgumentException( + 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' + . 'hyphens and backslashes.' + ); + } + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php new file mode 100644 index 0000000..4c8ce5e --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php @@ -0,0 +1,30 @@ +name = 'implements'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php new file mode 100644 index 0000000..848f34d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php @@ -0,0 +1,150 @@ +name = $name; + $this->body = $body; + } + + public function getException(): ?Throwable + { + return $this->throwable; + } + + public function getName(): string + { + return $this->name; + } + + public static function create(string $body, string $name = ''): self + { + return new self($name, $body); + } + + public function withError(Throwable $exception): self + { + $this->flattenExceptionBacktrace($exception); + $tag = new self($this->name, $this->body); + $tag->throwable = $exception; + + return $tag; + } + + /** + * Removes all complex types from backtrace + * + * Not all objects are serializable. So we need to remove them from the + * stored exception to be sure that we do not break existing library usage. + */ + private function flattenExceptionBacktrace(Throwable $exception): void + { + $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); + if (PHP_VERSION_ID < 80100) { + $traceProperty->setAccessible(true); + } + + do { + $trace = $exception->getTrace(); + if (isset($trace[0]['args'])) { + $trace = array_map( + function (array $call): array { + $call['args'] = array_map([$this, 'flattenArguments'], $call['args'] ?? []); + + return $call; + }, + $trace + ); + } + + $traceProperty->setValue($exception, $trace); + $exception = $exception->getPrevious(); + } while ($exception !== null); + + if (PHP_VERSION_ID >= 80100) { + return; + } + + $traceProperty->setAccessible(false); + } + + /** + * @param mixed $value + * + * @return mixed + * + * @throws ReflectionException + */ + private function flattenArguments($value) + { + if ($value instanceof Closure) { + $closureReflection = new ReflectionFunction($value); + $value = sprintf( + '(Closure at %s:%s)', + $closureReflection->getFileName(), + $closureReflection->getStartLine() + ); + } elseif (is_object($value)) { + $value = sprintf('object(%s)', get_class($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (is_array($value)) { + $value = array_map([$this, 'flattenArguments'], $value); + } + + return $value; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } + + public function __toString(): string + { + return $this->body; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php new file mode 100644 index 0000000..8ce615d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php @@ -0,0 +1,76 @@ +link = $link; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + return new static($parts[0], $description); + } + + /** + * Gets the link + */ + public function getLink(): string + { + return $this->link; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $link = $this->link; + + return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php new file mode 100644 index 0000000..66d1167 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php @@ -0,0 +1,135 @@ +methodName = $methodName; + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; + $this->returnsReference = $returnsReference; + $this->parameters = $parameters; + } + + /** + * Retrieves the method name. + */ + public function getMethodName(): string + { + return $this->methodName; + } + + /** @return MethodParameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic(): bool + { + return $this->isStatic; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function returnsReference(): bool + { + return $this->returnsReference; + } + + public function __toString(): string + { + $arguments = []; + foreach ($this->parameters as $parameter) { + $arguments[] = (string) $parameter; + } + + $argumentStr = '(' . implode(', ', $arguments) . ')'; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $static = $this->isStatic ? 'static' : ''; + + $returnType = (string) $this->returnType; + + $methodName = $this->methodName; + + $reference = $this->returnsReference ? '&' : ''; + + return $static + . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') + . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $reference . $methodName : '') + . $argumentStr + . ($description !== '' ? ' ' . $description : ''); + } + + public static function create(string $body): void + { + throw new CannotCreateTag('Method tag cannot be created'); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php new file mode 100644 index 0000000..68c7ca9 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php @@ -0,0 +1,91 @@ +type = $type; + $this->isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->name = $name; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReference(): bool + { + return $this->isReference; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function getDefaultValue(): ?string + { + if ($this->defaultValue === self::NO_DEFAULT_VALUE) { + return null; + } + + return (new MethodParameterFactory())->format($this->defaultValue); + } + + public function __toString(): string + { + return $this->getType() . ' ' . + ($this->isReference() ? '&' : '') . + ($this->isVariadic() ? '...' : '') . + '$' . $this->getName() . + ( + $this->defaultValue !== self::NO_DEFAULT_VALUE ? + ' = ' . (new MethodParameterFactory())->format($this->defaultValue) : + '' + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php new file mode 100644 index 0000000..2b0909c --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php @@ -0,0 +1,30 @@ +name = 'mixin'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php new file mode 100644 index 0000000..1653365 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php @@ -0,0 +1,94 @@ +name = 'param'; + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + $this->isReference = $isReference; + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns whether this tag is variadic. + */ + public function isVariadic(): bool + { + return $this->isVariadic; + } + + /** + * Returns whether this tag is passed by reference. + */ + public function isReference(): bool + { + return $this->isReference; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $variableName = ''; + if ($this->variableName !== null && $this->variableName !== '') { + $variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : ''); + $variableName .= '$' . $this->variableName; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php new file mode 100644 index 0000000..b7bd958 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php @@ -0,0 +1,68 @@ +name = 'property'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php new file mode 100644 index 0000000..bcb0e0d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php @@ -0,0 +1,68 @@ +name = 'property-read'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php new file mode 100644 index 0000000..a5ee9e9 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php @@ -0,0 +1,68 @@ +name = 'property-write'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php new file mode 100644 index 0000000..e4e7e31 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php @@ -0,0 +1,37 @@ +fqsen = $fqsen; + } + + /** + * @return string string representation of the referenced fqsen + */ + public function __toString(): string + { + return (string) $this->fqsen; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php new file mode 100644 index 0000000..e7dea86 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php @@ -0,0 +1,22 @@ +uri = $uri; + } + + public function __toString(): string + { + return $this->uri; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php new file mode 100644 index 0000000..bc93a79 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php @@ -0,0 +1,30 @@ +name = 'return'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php new file mode 100644 index 0000000..f8242d4 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php @@ -0,0 +1,104 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('#\w://\w#', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the ref of this tag. + */ + public function getReference(): Reference + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php new file mode 100644 index 0000000..abfc750 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php @@ -0,0 +1,102 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php new file mode 100644 index 0000000..71eefcd --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php @@ -0,0 +1,115 @@ +startingLine = (int) $startingLine; + $this->lineCount = $lineCount !== null ? (int) $lineCount : null; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + + $startingLine = 1; + $lineCount = null; + $description = null; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int) $matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int) $matches[2]; + } + + $description = $matches[3]; + } + + return new static($startingLine, $lineCount, $descriptionFactory->create($description ?? '', $context)); + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine(): int + { + return $this->startingLine; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount(): ?int + { + return $this->lineCount; + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $startingLine = (string) $this->startingLine; + + $lineCount = $this->lineCount !== null ? ' ' . $this->lineCount : ''; + + return $startingLine + . $lineCount + . ($description !== '' + ? ' ' . $description + : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php new file mode 100644 index 0000000..36f43be --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php @@ -0,0 +1,50 @@ +type; + } + + final public static function create(string $body): Tag + { + throw new CannotCreateTag('Typed tag cannot be created'); + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = (string) $this->type; + + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php new file mode 100644 index 0000000..5009879 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php @@ -0,0 +1,85 @@ +name = 'template'; + $this->templateName = $templateName; + $this->bound = $bound; + $this->default = $default; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create(string $body): ?Tag + { + throw new CannotCreateTag('Template tag cannot be created'); + } + + public function getTemplateName(): string + { + return $this->templateName; + } + + public function getBound(): ?Type + { + return $this->bound; + } + + public function getDefault(): ?Type + { + return $this->default; + } + + public function __toString(): string + { + $bound = $this->bound !== null ? ' of ' . $this->bound : ''; + $default = $this->default !== null ? ' = ' . $this->default : ''; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $this->templateName . $bound . $default . ($description !== '' ? ' ' . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php new file mode 100644 index 0000000..4ce2a94 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php @@ -0,0 +1,30 @@ +name = 'template-covariant'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php new file mode 100644 index 0000000..8de4681 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php @@ -0,0 +1,29 @@ +name = 'template-extends'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php new file mode 100644 index 0000000..1fb073d --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php @@ -0,0 +1,29 @@ +name = 'template-implements'; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php new file mode 100644 index 0000000..675b6f0 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php @@ -0,0 +1,30 @@ +name = 'throws'; + $this->type = $type; + $this->description = $description; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php new file mode 100644 index 0000000..8a9849a --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php @@ -0,0 +1,98 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $resolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($resolver); + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php new file mode 100644 index 0000000..495d16f --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php @@ -0,0 +1,68 @@ +name = 'var'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php new file mode 100644 index 0000000..951c8e5 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php @@ -0,0 +1,105 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + $description = null; + if ($descriptionFactory !== null) { + $description = $descriptionFactory->create($matches[2] ?? '', $context); + } + + return new static( + $matches[1], + $description + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php new file mode 100644 index 0000000..bdd29d1 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php @@ -0,0 +1,284 @@ +descriptionFactory = $descriptionFactory; + $this->tagFactory = $tagFactory; + } + + /** + * Factory method for easy instantiation. + * + * @param array|Factory> $additionalTags + */ + public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface + { + $fqsenResolver = new FqsenResolver(); + $tagFactory = StandardTagFactory::createInstance($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + + $docBlockFactory = new self($descriptionFactory, $tagFactory); + foreach ($additionalTags as $tagName => $tagHandler) { + $docBlockFactory->registerTagHandler($tagName, $tagHandler); + } + + return $docBlockFactory; + } + + /** + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock + { + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + + throw new InvalidArgumentException($exceptionMessage); + } + + $docblock = $docblock->getDocComment(); + Assert::string($docblock); + } + + Assert::stringNotEmpty($docblock); + + if ($context === null) { + $context = new Types\Context(''); + } + + $parts = $this->splitDocBlock($this->stripDocComment($docblock)); + + [$templateMarker, $summary, $description, $tags] = $parts; + + return new DocBlock( + $summary, + $description ? $this->descriptionFactory->create($description, $context) : null, + $this->parseTagBlock($tags, $context), + $context, + $location, + $templateMarker === '#@+', + $templateMarker === '#@-' + ); + } + + /** + * @param class-string|Factory $handler + */ + public function registerTagHandler(string $tagName, $handler): void + { + $this->tagFactory->registerTagHandler($tagName, $handler); + } + + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + */ + private function stripDocComment(string $comment): string + { + $comment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]?(.*)?#u', '$1', $comment); + Assert::string($comment); + $comment = trim($comment); + + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) === '*/') { + $comment = trim(substr($comment, 0, -2)); + } + + return str_replace(["\r\n", "\r"], "\n", $comment); + } + + // phpcs:disable + + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + * + * @author Mike van Riel for extending the regex with template marker support. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + */ + private function splitDocBlock(string $comment): array + { + // phpcs:enable + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression + if (strpos($comment, '@') === 0) { + return ['', '', '', $comment]; + } + + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + Assert::string($comment); + /* + * Splits the docblock into a template marker, summary, description and tags section. + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.]* (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { + $matches[] = ''; + } + + return $matches; + } + + /** + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * @param Types\Context $context Context of the parsed Tag + * + * @return DocBlock\Tag[] + */ + private function parseTagBlock(string $tags, Types\Context $context): array + { + $tags = $this->filterTagBlock($tags); + if ($tags === null) { + return []; + } + + $result = []; + $lines = $this->splitTagBlockIntoTagLines($tags); + foreach ($lines as $key => $tagLine) { + $result[$key] = $this->tagFactory->create(trim($tagLine), $context); + } + + return $result; + } + + /** + * @return string[] + */ + private function splitTagBlockIntoTagLines(string $tags): array + { + $result = []; + foreach (explode("\n", $tags) as $tagLine) { + if ($tagLine !== '' && strpos($tagLine, '@') === 0) { + $result[] = $tagLine; + } else { + $result[count($result) - 1] .= "\n" . $tagLine; + } + } + + return $result; + } + + private function filterTagBlock(string $tags): ?string + { + $tags = trim($tags); + if (!$tags) { + return null; + } + + if ($tags[0] !== '@') { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. + + throw new LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + + // @codeCoverageIgnoreEnd + } + + return $tags; + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php new file mode 100644 index 0000000..cacc382 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php @@ -0,0 +1,23 @@ +> $additionalTags + */ + public static function createInstance(array $additionalTags = []): self; + + /** + * @param string|object $docblock + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock; +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/CannotCreateTag.php b/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/CannotCreateTag.php new file mode 100644 index 0000000..a66884a --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/CannotCreateTag.php @@ -0,0 +1,11 @@ +getMessage(), + 0, + $exception + ); + } +} diff --git a/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php b/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php new file mode 100644 index 0000000..b8b6da8 --- /dev/null +++ b/admin/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php @@ -0,0 +1,44 @@ + please note that if you want to pass partial class names that additional steps are necessary, see the + > chapter `Resolving partial classes and FQSENs` for more information. + +Where the FqsenResolver can resolve: + +- Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`) +- Function expressions (i.e. `@see \MyNamespace\myFunction()`) +- Class expressions (i.e. `@see \MyNamespace\MyClass`) +- Interface expressions (i.e. `@see \MyNamespace\MyInterface`) +- Trait expressions (i.e. `@see \MyNamespace\MyTrait`) +- Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`) +- Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`) +- Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`) + +## Resolving a type + +In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` and call its `resolve` method like this: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('string|integer'); +``` + +In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two +elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type +`\phpDocumentor\Reflection\Types\Integer`. + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +### Resolving nullable types + +Php 7.1 introduced nullable types e.g. `?string`. Type resolver will resolve the original type without the nullable notation `?` +just like it would do without the `?`. After that the type is wrapped in a `\phpDocumentor\Reflection\Types\Nullable` object. +The `Nullable` type has a method to fetch the actual type. + +## Resolving an FQSEN + +A Fully Qualified Structural Element Name is a reference to another element in your code bases and can be resolved using the `\phpDocumentor\Reflection\FqsenResolver` class' `resolve` method, like this: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()'); +``` + +In this example we resolve a Fully Qualified Structural Element Name (meaning that it includes the full namespace, class name and element name) and receive a Value Object of type `\phpDocumentor\Reflection\Fqsen`. + +The real power of this resolver is in its capability to expand partial element names into Fully Qualified Structural Element Names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +## Resolving partial Classes and Structural Element Names + +Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class names. + +For example, you have this file: + +```php +namespace My\Example; + +use phpDocumentor\Reflection\Types; + +class Classy +{ + /** + * @var Types\Context + * @see Classy::otherFunction() + */ + public function __construct($context) {} + + public function otherFunction(){} +} +``` + +Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag. + +For the resolvers to know how to expand partial names you have to provide a bit of _Context_ for them by instantiating a new class named `\phpDocumentor\Reflection\Types\Context` with the name of the namespace and the aliases that are in play. + +### Creating a Context + +You can do this by manually creating a Context like this: + +```php +$context = new \phpDocumentor\Reflection\Types\Context( + '\My\Example', + [ 'Types' => '\phpDocumentor\Reflection\Types'] +); +``` + +Or by using the `\phpDocumentor\Reflection\Types\ContextFactory` to instantiate a new context based on a Reflector object or by providing the namespace that you'd like to extract and the source code of the file in which the given type expression occurs. + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct')); +``` + +or + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php')); +``` + +### Using the Context + +After you have obtained a Context it is just a matter of passing it along with the `resolve` method of either Resolver class as second argument and the Resolvers will take this into account when resolving partial names. + +To obtain the resolved class name for the `@var` tag in the example above you can do: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('Types\Context', $context); +``` + +When you do this you will receive an object of class `\phpDocumentor\Reflection\Types\Object_` for which you can call the `getFqsen` method to receive a Value Object that represents the complete FQSEN. So that would be `phpDocumentor\Reflection\Types\Context`. + +> Why is the FQSEN wrapped in another object `Object_`? +> +> The resolve method of the TypeResolver only returns object with the interface `Type` and the FQSEN is a common type that does not represent a Type. Also: in some cases a type can represent an "Untyped Object", meaning that it is an object (signified by the `object` keyword) but does not refer to a specific element using an FQSEN. + +Another example is on how to resolve the FQSEN of a method as can be seen with the `@see` tag in the example above. To resolve that you can do the following: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$type = $fqsenResolver->resolve('Classy::otherFunction()', $context); +``` + +Because Classy is a Class in the current namespace its FQSEN will have the `My\Example` namespace and by calling the `resolve` method of the FQSEN Resolver you will receive an `Fqsen` object that refers to `\My\Example\Classy::otherFunction()`. diff --git a/admin/vendor/phpdocumentor/type-resolver/composer.json b/admin/vendor/phpdocumentor/type-resolver/composer.json new file mode 100644 index 0000000..631eca6 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/composer.json @@ -0,0 +1,51 @@ +{ + "name": "phpdocumentor/type-resolver", + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "doctrine/deprecations": "^1.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/extension-installer": "^1.4", + "phpbench/phpbench": "^1.2", + "psalm/phar": "^4" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": ["tests/unit", "tests/benchmark"] + } + }, + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "config": { + "platform": { + "php": "7.4.0" + }, + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/admin/vendor/phpdocumentor/type-resolver/docs/generics.rst b/admin/vendor/phpdocumentor/type-resolver/docs/generics.rst new file mode 100644 index 0000000..f306432 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/docs/generics.rst @@ -0,0 +1,29 @@ +======== +Generics +======== + +This project is capable of parsing generics notation as used by PHPStan. But it has some limitations, in regards to +PHPStan. The main difference is that PHPStan does scan your whole codebase to find out what types are used in generics, +while this library only parses the types as they are given to it. + +This means that if you use a generic type like. + +.. code:: php + + namespace MyApp; + + /** + * @template T of Item + */ + class Collection { + + /** + * @return T[] + */ + public function getItems() : array { + // ... + } + } + +The type resolver will not be able to determine what ``T`` is. In fact there is no difference between ``T`` and any other relative +used classname like ``Item``. The resolver will handle ``T`` as a normal class name. In this example it will resolve ``T`` to ``\MyApp\T``. diff --git a/admin/vendor/phpdocumentor/type-resolver/docs/getting-started.rst b/admin/vendor/phpdocumentor/type-resolver/docs/getting-started.rst new file mode 100644 index 0000000..1c8ea67 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/docs/getting-started.rst @@ -0,0 +1,32 @@ +=============== +Getting started +=============== + +On this page you will find a brief introduction on how to use the TypeResolver in your project. + +Installation +============ + +The TypeResolver is available on Packagist and can be installed using Composer: + +.. code:: bash + composer require phpdocumentor/type-resolver + + +General usage +=========== + +After you installed the TypeResolver you can use it in your project. This can be done by creating a new instance +of the :php:class:`\phpDocumentor\Reflection\TypeResolver` class and calling +:php:method:`\phpDocumentor\Reflection\TypeResolver::resolve()` with the type you want to resolve. + +.. code:: php + $typeResolver = new \phpDocumentor\Reflection\TypeResolver(); + $type = $typeResolver->resolve('string'); + echo get_class($type); // phpDocumentor\Reflection\Types\String_ + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; +but in order to do that we need an additional :php:class:`\phpDocumentor\Reflection\Types\Context` class that +will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +Read more about the Context class in the next section. diff --git a/admin/vendor/phpdocumentor/type-resolver/docs/index.rst b/admin/vendor/phpdocumentor/type-resolver/docs/index.rst new file mode 100644 index 0000000..1512983 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/docs/index.rst @@ -0,0 +1,19 @@ +============= +Type resolver +============= + +This project part of the phpDocumentor project. It is capable of creating an object structure of the type +specifications found in the PHPDoc blocks of a project. This can be useful for static analysis of a project +or other behavior that requires knowledge of the types used in a project like automatically build forms. + +This project aims to cover all types that are available in PHPDoc and PHP itself. And is open for extension by +third party developers. + +.. toctree:: + :maxdepth: 2 + :hidden: + + index + getting-started + generics + upgrade-v1-to-v2 diff --git a/admin/vendor/phpdocumentor/type-resolver/docs/upgrade-v1-to-v2.rst b/admin/vendor/phpdocumentor/type-resolver/docs/upgrade-v1-to-v2.rst new file mode 100644 index 0000000..4f63ad3 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/docs/upgrade-v1-to-v2.rst @@ -0,0 +1,40 @@ +==================== +Upgrade to Version 2 +==================== + +Version 2 of the Type Resolver introduces several breaking changes and new features. This guide will help you +upgrade your codebase to be compatible with the latest version. The usage of the TypeResolver remains the same. However, +some classes have been moved or replaced, and the minimum PHP version requirement has been raised. + +PHP Version +----------- + +Version 2 requires PHP 7.4 or higher. We have been supporting PHP 7.3 in version 1, but due to changing constraints +in our dependencies, we have had to raise the minimum PHP version. At the moment of writing this, PHP 7.3 is used by 2% +of all installations of this package according to Packagist. We believe this is a reasonable trade-off to ensure we +can continue to deliver new features and improvements. + +Moved classes +------------- + +- ``phpDocumentor\Reflection\Types\InterfaceString`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\InterfaceString` +- ``phpDocumentor\Reflection\Types\ClassString`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\ClassString` +- ``phpDocumentor\Reflection\Types\ArrayKey`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\ArrayKey` +- ``phpDocumentor\Reflection\Types\True_`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\True_` +- ``phpDocumentor\Reflection\Types\False_`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\False_` + +Replaced classes +----------------- + +- ``phpDocumentor\Reflection\Types\Collection`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\Generic` + +Since the introduction of generics in PHP this library was not capable of parsing them correctly. The old Collection +was blocking the use of generics. The new Generic type is a representation of generics like supported in the eco system. + +Changed implementations +----------------------- + +:php:class:`phpDocumentor\Reflection\PseudoTypes\InterfaceString`, :php:class:`phpDocumentor\Reflection\PseudoTypes\ClassString` and +:php:class:`phpDocumentor\Reflection\PseudoTypes\TraitString` are no longer returning a :php:class:`phpDocumentor\Reflection\Fqsen` since +support for generics these classes can have type arguments like any other generic. + diff --git a/admin/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml b/admin/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml new file mode 100644 index 0000000..6c98991 --- /dev/null +++ b/admin/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml @@ -0,0 +1,46 @@ + + + Type Resolver + + build/docs + + + latest + + + src/ + + api + + + php + + + template + template-extends + template-implements + extends + implements + + phpDocumentor + + + + docs + + guides + + + +