소재지 ₍₍◝(・'ω'・)◟⁾⁾ 🐟️?看XM(^_−)☆哈先看看刚看过卡卡国看过了回来冷藏柜好极过估计 PNG %k25u25%fgd5n!Abstracts/AbstractClientDiscoveryStrategy.php000064400000005557152213730340015534 0ustar00> The discovery candidates. */ public static function getCandidates($type) { if (ClientInterface::class === $type) { return [['class' => static function () { $psr17Factory = new Psr17Factory(); return static::createClient($psr17Factory); }]]; } $psr17Factories = ['WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface']; if (in_array($type, $psr17Factories, \true)) { return [['class' => Psr17Factory::class]]; } return []; } /** * Creates an instance of the HTTP client. * * Subclasses must implement this method to return their specific * PSR-18 HTTP client instance. The provided Psr17Factory implements * all PSR-17 interfaces (RequestFactory, ResponseFactory, StreamFactory, * etc.) and can be used to satisfy client constructor dependencies. * * @since 1.1.0 * * @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages. * @return ClientInterface The PSR-18 HTTP client. */ abstract protected static function createClient(Psr17Factory $psr17Factory): ClientInterface; } Abstracts/error_log000064400000000670152213730340010412 0ustar00[02-Jul-2026 04:45:46 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 Collections/HeadersCollection.php000064400000007411152213730340013125 0ustar00> The headers with original casing. */ private array $headers = []; /** * @var array Map of lowercase header names to actual header names. */ private array $headersMap = []; /** * Constructor. * * @since 0.1.0 * * @param array> $headers Initial headers. */ public function __construct(array $headers = []) { foreach ($headers as $name => $value) { $this->set($name, $value); } } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list|null The header value(s) or null if not found. */ public function get(string $name): ?array { $lowerName = strtolower($name); if (!isset($this->headersMap[$lowerName])) { return null; } $actualName = $this->headersMap[$lowerName]; return $this->headers[$actualName]; } /** * Gets all headers. * * @since 0.1.0 * * @return array> All headers with their original casing. */ public function getAll(): array { return $this->headers; } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getAsString(string $name): ?string { $values = $this->get($name); return $values !== null ? implode(', ', $values) : null; } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function has(string $name): bool { return isset($this->headersMap[strtolower($name)]); } /** * Sets a header value, replacing any existing value. * * @since 0.1.0 * * @param string $name The header name. * @param string|list $value The header value(s). * @return void */ private function set(string $name, $value): void { if (is_array($value)) { $normalizedValues = array_values($value); } else { // Split comma-separated string into array $normalizedValues = array_map('trim', explode(',', $value)); } $lowerName = strtolower($name); // If header exists with different casing, remove the old casing if (isset($this->headersMap[$lowerName])) { $oldName = $this->headersMap[$lowerName]; if ($oldName !== $name) { unset($this->headers[$oldName]); } } // Always use the new casing $this->headers[$name] = $normalizedValues; $this->headersMap[$lowerName] = $name; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $new = clone $this; $new->set($name, $value); return $new; } } Contracts/RequestAuthenticationInterface.php000064400000001155152213730340015370 0ustar00 */ class ApiKeyRequestAuthentication extends AbstractDataTransferObject implements RequestAuthenticationInterface { public const KEY_API_KEY = 'apiKey'; /** * @var string The API key used for authentication. */ protected string $apiKey; /** * Constructor. * * @since 0.1.0 * * @param string $apiKey The API key used for authentication. */ public function __construct(string $apiKey) { $this->apiKey = $apiKey; } /** * {@inheritDoc} * * @since 0.1.0 */ public function authenticateRequest(\WordPress\AiClient\Providers\Http\DTO\Request $request): \WordPress\AiClient\Providers\Http\DTO\Request { // Add the API key to the request headers. return $request->withHeader('Authorization', 'Bearer ' . $this->apiKey); } /** * Gets the API key. * * @since 0.1.0 * * @return string The API key. */ public function getApiKey(): string { return $this->apiKey; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 * * @return ApiKeyRequestAuthenticationArrayShape */ public function toArray(): array { return [self::KEY_API_KEY => $this->apiKey]; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_API_KEY]); return new self($array[self::KEY_API_KEY]); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_API_KEY => ['type' => 'string', 'title' => 'API Key', 'description' => 'The API key used for authentication.']], 'required' => [self::KEY_API_KEY]]; } } DTO/Request.php000064400000030047152213730340007337 0ustar00>, * body?: string|null, * options?: RequestOptionsArrayShape * } * * @extends AbstractDataTransferObject */ class Request extends AbstractDataTransferObject { public const KEY_METHOD = 'method'; public const KEY_URI = 'uri'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; public const KEY_OPTIONS = 'options'; /** * @var HttpMethodEnum The HTTP method. */ protected HttpMethodEnum $method; /** * @var string The request URI. */ protected string $uri; /** * @var HeadersCollection The request headers. */ protected HeadersCollection $headers; /** * @var array|null The request data (for query params or form data). */ protected ?array $data = null; /** * @var string|null The request body (raw string content). */ protected ?string $body = null; /** * @var RequestOptions|null Request transport options. */ protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null; /** * Constructor. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $uri The request URI. * @param array> $headers The request headers. * @param string|array|null $data The request data. * @param RequestOptions|null $options The request transport options. * * @throws InvalidArgumentException If the URI is empty. */ public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null) { if (empty($uri)) { throw new InvalidArgumentException('URI cannot be empty.'); } $this->method = $method; $this->uri = $uri; $this->headers = new HeadersCollection($headers); // Separate data and body based on type if (is_string($data)) { $this->body = $data; } elseif (is_array($data)) { $this->data = $data; } $this->options = $options; } /** * Creates a deep clone of this request. * * Clones the headers collection and request options to ensure * the cloned request is independent of the original. * The HTTP method enum is immutable and can be safely shared. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; // Clone request options if present (contains only primitives) if ($this->options !== null) { $this->options = clone $this->options; } // Note: $method is an immutable enum and can be safely shared } /** * Gets the HTTP method. * * @since 0.1.0 * * @return HttpMethodEnum The HTTP method. */ public function getMethod(): HttpMethodEnum { return $this->method; } /** * Gets the request URI. * * For GET requests with array data, appends the data as query parameters. * * @since 0.1.0 * * @return string The URI. */ public function getUri(): string { // If GET request with data, append as query parameters if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) { $separator = str_contains($this->uri, '?') ? '&' : '?'; return $this->uri . $separator . http_build_query($this->data); } return $this->uri; } /** * Gets the request headers. * * @since 0.1.0 * * @return array> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string, or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Gets the request body. * * For GET requests, returns null. * For POST/PUT/PATCH requests: * - If body is set, returns it as-is * - If data is set and Content-Type is JSON, returns JSON-encoded data * - If data is set and Content-Type is form, returns URL-encoded data * * @since 0.1.0 * * @return string|null The body. * @throws JsonException If the data cannot be encoded to JSON. */ public function getBody(): ?string { // GET requests don't have a body if (!$this->method->hasBody()) { return null; } // If body is set, return it as-is if ($this->body !== null) { return $this->body; } // If data is set, encode based on content type if ($this->data !== null) { $contentType = $this->getContentType(); // JSON encoding if ($contentType !== null && stripos($contentType, 'application/json') !== \false) { return json_encode($this->data, \JSON_THROW_ON_ERROR); } // Default to URL encoding for forms return http_build_query($this->data); } return null; } /** * Gets the Content-Type header value. * * @since 0.1.0 * * @return string|null The Content-Type header value or null if not set. */ private function getContentType(): ?string { $values = $this->getHeader('Content-Type'); return $values !== null ? $values[0] : null; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $newHeaders = $this->headers->withHeader($name, $value); $new = clone $this; $new->headers = $newHeaders; return $new; } /** * Returns a new instance with the specified data. * * @since 0.1.0 * * @param string|array $data The request data. * @return self A new instance with the data. */ public function withData($data): self { $new = clone $this; if (is_string($data)) { $new->body = $data; $new->data = null; } elseif (is_array($data)) { $new->data = $data; $new->body = null; } else { $new->data = null; $new->body = null; } return $new; } /** * Gets the request data array. * * @since 0.1.0 * * @return array|null The request data array. */ public function getData(): ?array { return $this->data; } /** * Gets the request options. * * @since 0.2.0 * * @return RequestOptions|null Request transport options when configured. */ public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions { return $this->options; } /** * Returns a new instance with the specified request options. * * @since 0.2.0 * * @param RequestOptions|null $options The request options to apply. * @return self A new instance with the options. */ public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self { $new = clone $this; $new->options = $options; return $new; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return RequestArrayShape */ public function toArray(): array { $array = [ self::KEY_METHOD => $this->method->value, self::KEY_URI => $this->getUri(), // Include query params if GET with data self::KEY_HEADERS => $this->headers->getAll(), ]; // Include body if present (getBody() handles the conversion) $body = $this->getBody(); if ($body !== null) { $array[self::KEY_BODY] = $body; } if ($this->options !== null) { $optionsArray = $this->options->toArray(); if (!empty($optionsArray)) { $array[self::KEY_OPTIONS] = $optionsArray; } } return $array; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]); return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null); } /** * Creates a Request instance from a PSR-7 RequestInterface. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request to convert. * @return self A new Request instance. * @throws InvalidArgumentException If the HTTP method is not supported. */ public static function fromPsrRequest(RequestInterface $psrRequest): self { $method = HttpMethodEnum::from($psrRequest->getMethod()); $uri = (string) $psrRequest->getUri(); // Convert PSR-7 headers to array format expected by our constructor /** @var array> $headers */ $headers = $psrRequest->getHeaders(); // Get body content $body = $psrRequest->getBody()->getContents(); $bodyOrData = !empty($body) ? $body : null; return new self($method, $uri, $headers, $bodyOrData); } } DTO/RequestOptions.php000064400000014523152213730340010714 0ustar00 */ class RequestOptions extends AbstractDataTransferObject { public const KEY_TIMEOUT = 'timeout'; public const KEY_CONNECT_TIMEOUT = 'connectTimeout'; public const KEY_MAX_REDIRECTS = 'maxRedirects'; /** * @var float|null Maximum duration in seconds to wait for the full response. */ protected ?float $timeout = null; /** * @var float|null Maximum duration in seconds to wait for the initial connection. */ protected ?float $connectTimeout = null; /** * @var int|null Maximum number of redirects to follow. 0 disables redirects, null is unspecified. */ protected ?int $maxRedirects = null; /** * Sets the request timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_TIMEOUT); $this->timeout = $timeout; } /** * Sets the connection timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Connection timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setConnectTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_CONNECT_TIMEOUT); $this->connectTimeout = $timeout; } /** * Sets the maximum number of redirects to follow. * * Set to 0 to disable redirects, null for unspecified, or a positive integer * to enable redirects with a maximum count. * * @since 0.2.0 * * @param int|null $maxRedirects Maximum redirects to follow, or 0 to disable, or null for unspecified. * @return void * * @throws InvalidArgumentException When redirect count is negative. */ public function setMaxRedirects(?int $maxRedirects): void { if ($maxRedirects !== null && $maxRedirects < 0) { throw new InvalidArgumentException('Request option "maxRedirects" must be greater than or equal to 0.'); } $this->maxRedirects = $maxRedirects; } /** * Gets the request timeout in seconds. * * @since 0.2.0 * * @return float|null Timeout in seconds. */ public function getTimeout(): ?float { return $this->timeout; } /** * Gets the connection timeout in seconds. * * @since 0.2.0 * * @return float|null Connection timeout in seconds. */ public function getConnectTimeout(): ?float { return $this->connectTimeout; } /** * Checks whether redirects are allowed. * * @since 0.2.0 * * @return bool|null True when redirects are allowed (maxRedirects > 0), * false when disabled (maxRedirects = 0), * null when unspecified (maxRedirects = null). */ public function allowsRedirects(): ?bool { if ($this->maxRedirects === null) { return null; } return $this->maxRedirects > 0; } /** * Gets the maximum number of redirects to follow. * * @since 0.2.0 * * @return int|null Maximum redirects or null when not specified. */ public function getMaxRedirects(): ?int { return $this->maxRedirects; } /** * {@inheritDoc} * * @since 0.2.0 * * @return RequestOptionsArrayShape */ public function toArray(): array { $data = []; if ($this->timeout !== null) { $data[self::KEY_TIMEOUT] = $this->timeout; } if ($this->connectTimeout !== null) { $data[self::KEY_CONNECT_TIMEOUT] = $this->connectTimeout; } if ($this->maxRedirects !== null) { $data[self::KEY_MAX_REDIRECTS] = $this->maxRedirects; } return $data; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function fromArray(array $array): self { $instance = new self(); if (isset($array[self::KEY_TIMEOUT])) { $instance->setTimeout((float) $array[self::KEY_TIMEOUT]); } if (isset($array[self::KEY_CONNECT_TIMEOUT])) { $instance->setConnectTimeout((float) $array[self::KEY_CONNECT_TIMEOUT]); } if (isset($array[self::KEY_MAX_REDIRECTS])) { $instance->setMaxRedirects((int) $array[self::KEY_MAX_REDIRECTS]); } return $instance; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the full response.'], self::KEY_CONNECT_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the initial connection.'], self::KEY_MAX_REDIRECTS => ['type' => ['integer', 'null'], 'minimum' => 0, 'description' => 'Maximum redirects to follow. 0 disables, null is unspecified.']], 'additionalProperties' => \false]; } /** * Validates timeout values. * * @since 0.2.0 * * @param float|null $value Timeout to validate. * @param string $fieldName Field name for the error message. * * @throws InvalidArgumentException When timeout is negative. */ private function validateTimeout(?float $value, string $fieldName): void { if ($value !== null && $value < 0) { throw new InvalidArgumentException(sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName)); } } } DTO/Response.php000064400000013734152213730340007511 0ustar00>, * body?: string|null * } * * @extends AbstractDataTransferObject */ class Response extends AbstractDataTransferObject { public const KEY_STATUS_CODE = 'statusCode'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; /** * @var int The HTTP status code. */ protected int $statusCode; /** * @var HeadersCollection The response headers. */ protected HeadersCollection $headers; /** * @var string|null The response body. */ protected ?string $body; /** * Constructor. * * @since 0.1.0 * * @param int $statusCode The HTTP status code. * @param array> $headers The response headers. * @param string|null $body The response body. * * @throws InvalidArgumentException If the status code is invalid. */ public function __construct(int $statusCode, array $headers, ?string $body = null) { if ($statusCode < 100 || $statusCode >= 600) { throw new InvalidArgumentException('Invalid HTTP status code: ' . $statusCode); } $this->statusCode = $statusCode; $this->headers = new HeadersCollection($headers); $this->body = $body; } /** * Creates a deep clone of this response. * * Clones the headers collection to ensure the cloned * response is independent of the original. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; } /** * Gets the HTTP status code. * * @since 0.1.0 * * @return int The status code. */ public function getStatusCode(): int { return $this->statusCode; } /** * Gets the response headers. * * @since 0.1.0 * * @return array> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Gets the response body. * * @since 0.1.0 * * @return string|null The body. */ public function getBody(): ?string { return $this->body; } /** * Checks if the response has a header. * * @since 0.1.0 * * @param string $name The header name. * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Checks if the response indicates success. * * @since 0.1.0 * * @return bool True if status code is 2xx, false otherwise. */ public function isSuccessful(): bool { return $this->statusCode >= 200 && $this->statusCode < 300; } /** * Gets the response data as an array. * * Attempts to decode the body as JSON. Returns null if the body * is empty or not valid JSON. * * @since 0.1.0 * * @return array|null The decoded data or null. */ public function getData(): ?array { if ($this->body === null || $this->body === '') { return null; } $data = json_decode($this->body, \true); if (json_last_error() !== \JSON_ERROR_NONE) { return null; } /** @var array|null $data */ return is_array($data) ? $data : null; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_STATUS_CODE => ['type' => 'integer', 'minimum' => 100, 'maximum' => 599, 'description' => 'The HTTP status code.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The response headers.'], self::KEY_BODY => ['type' => ['string', 'null'], 'description' => 'The response body.']], 'required' => [self::KEY_STATUS_CODE, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ResponseArrayShape */ public function toArray(): array { $data = [self::KEY_STATUS_CODE => $this->statusCode, self::KEY_HEADERS => $this->headers->getAll()]; if ($this->body !== null) { $data[self::KEY_BODY] = $this->body; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_STATUS_CODE, self::KEY_HEADERS]); return new self($array[self::KEY_STATUS_CODE], $array[self::KEY_HEADERS], $array[self::KEY_BODY] ?? null); } } DTO/error_log000064400000002710152213730340007107 0ustar00[02-Jul-2026 03:46:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [02-Jul-2026 03:46:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [02-Jul-2026 03:47:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [02-Jul-2026 04:45:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 Enums/RequestAuthenticationMethod.php000064400000002344152213730340014040 0ustar00 The implementation class. * * @phpstan-ignore missingType.generics */ public function getImplementationClass(): string { // At the moment, this is the only supported method. // Once more methods are available, add conditionals here for each method. return ApiKeyRequestAuthentication::class; } } Enums/HttpMethodEnum.php000064400000004750152213730340011257 0ustar00value, [self::GET, self::HEAD, self::OPTIONS, self::TRACE, self::PUT, self::DELETE], \true); } /** * Checks if this method typically has a request body. * * @since 0.1.0 * * @return bool True if the method typically has a body, false otherwise. */ public function hasBody(): bool { return in_array($this->value, [self::POST, self::PUT, self::PATCH], \true); } } Enums/error_log000064400000001352152213730340007551 0ustar00[02-Jul-2026 03:46:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [02-Jul-2026 04:45:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 Exception/ServerException.php000064400000003425152213730340012344 0ustar00getStatusCode(); $statusTexts = [500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 507 => 'Insufficient Storage', 529 => 'Overloaded']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Server error (%d): Request was rejected due to server-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $response->getStatusCode()); } } Exception/ClientException.php000064400000004655152213730340012322 0ustar00request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a ClientException from a client error response (4xx). * * This method extracts error details from common API response formats * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP response that failed. * @return self */ public static function fromClientErrorResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Client error (%d): Request was rejected due to client-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $statusCode); } } Exception/RedirectException.php000064400000003465152213730340012643 0ustar00getStatusCode(); $statusTexts = [300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Redirect error (%d): Request needs to be retried at a different location', $statusCode); } // Try to extract the redirect location from headers $locationValues = $response->getHeader('Location'); if ($locationValues !== null && !empty($locationValues)) { $location = $locationValues[0]; $errorMessage .= ' - Location: ' . $location; } return new self($errorMessage, $statusCode); } } Exception/NetworkException.php000064400000003512152213730340012524 0ustar00request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a NetworkException from a PSR-18 network exception. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request that failed. * @param \Throwable $networkException The PSR-18 network exception. * @return self */ public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self { $request = Request::fromPsrRequest($psrRequest); $message = sprintf('Network error occurred while sending request to %s: %s', $request->getUri(), $networkException->getMessage()); $exception = new self($message, 0, $networkException); $exception->request = $request; return $exception; } } Exception/ResponseException.php000064400000003041152213730340012666 0ustar00requestAuthentication = $requestAuthentication; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getRequestAuthentication(): RequestAuthenticationInterface { if ($this->requestAuthentication === null) { throw new RuntimeException('RequestAuthenticationInterface instance not set. ' . 'Make sure you use the AiClient class for all requests.'); } return $this->requestAuthentication; } } Traits/WithHttpTransporterTrait.php000064400000002106152213730340013545 0ustar00httpTransporter = $httpTransporter; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getHttpTransporter(): HttpTransporterInterface { if ($this->httpTransporter === null) { throw new RuntimeException('HttpTransporterInterface instance not set. Make sure you use the AiClient class for all requests.'); } return $this->httpTransporter; } } Util/ErrorMessageExtractor.php000064400000003545152213730340012473 0ustar00isSuccessful()) { return; } $statusCode = $response->getStatusCode(); // 3xx Redirect Responses if ($statusCode >= 300 && $statusCode < 400) { throw RedirectException::fromRedirectResponse($response); } // 4xx Client Errors if ($statusCode >= 400 && $statusCode < 500) { throw ClientException::fromClientErrorResponse($response); } // 5xx Server Errors if ($statusCode >= 500 && $statusCode < 600) { throw ServerException::fromServerErrorResponse($response); } throw new \RuntimeException(sprintf('Response returned invalid status code: %s', $response->getStatusCode())); } } HttpTransporter.php000064400000025234152213730340010446 0ustar00client = $client ?: Psr18ClientDiscovery::find(); $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory(); $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); } /** * {@inheritDoc} * * @since 0.1.0 * @since 0.2.0 Added optional RequestOptions parameter and ClientWithOptions support. */ public function send(Request $request, ?RequestOptions $options = null): Response { $psr7Request = $this->convertToPsr7Request($request); // Merge request options with parameter options, with parameter options taking precedence $mergedOptions = $this->mergeOptions($request->getOptions(), $options); try { $hasOptions = $mergedOptions !== null; if ($hasOptions && $this->client instanceof ClientWithOptionsInterface) { $psr7Response = $this->client->sendRequestWithOptions($psr7Request, $mergedOptions); } elseif ($hasOptions && $this->isGuzzleClient($this->client)) { $psr7Response = $this->sendWithGuzzle($psr7Request, $mergedOptions); } else { $psr7Response = $this->client->sendRequest($psr7Request); } } catch (\WordPress\AiClientDependencies\Psr\Http\Client\NetworkExceptionInterface $e) { throw NetworkException::fromPsr18NetworkException($psr7Request, $e); } catch (\WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface $e) { // Handle other PSR-18 client exceptions that are not network-related throw new RuntimeException(sprintf('HTTP client error occurred while sending request to %s: %s', $request->getUri(), $e->getMessage()), 0, $e); } return $this->convertFromPsr7Response($psr7Response); } /** * Merges request options with parameter options taking precedence. * * @since 0.2.0 * * @param RequestOptions|null $requestOptions Options from the Request object. * @param RequestOptions|null $parameterOptions Options passed as method parameter. * @return RequestOptions|null Merged options, or null if both are null. */ private function mergeOptions(?RequestOptions $requestOptions, ?RequestOptions $parameterOptions): ?RequestOptions { // If no options at all, return null if ($requestOptions === null && $parameterOptions === null) { return null; } // If only one set of options exists, return it if ($requestOptions === null) { return $parameterOptions; } if ($parameterOptions === null) { return $requestOptions; } // Both exist, merge them with parameter options taking precedence $merged = new RequestOptions(); // Start with request options (lower precedence) if ($requestOptions->getTimeout() !== null) { $merged->setTimeout($requestOptions->getTimeout()); } if ($requestOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($requestOptions->getConnectTimeout()); } if ($requestOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($requestOptions->getMaxRedirects()); } // Override with parameter options (higher precedence) if ($parameterOptions->getTimeout() !== null) { $merged->setTimeout($parameterOptions->getTimeout()); } if ($parameterOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($parameterOptions->getConnectTimeout()); } if ($parameterOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($parameterOptions->getMaxRedirects()); } return $merged; } /** * Determines if the underlying client matches the Guzzle client shape. * * @since 0.2.0 * * @param ClientInterface $client The HTTP client instance. * @return bool True when the client exposes Guzzle's send signature. */ private function isGuzzleClient(ClientInterface $client): bool { $reflection = new \ReflectionObject($client); if (!is_callable([$client, 'send'])) { return \false; } if (!$reflection->hasMethod('send')) { return \false; } $method = $reflection->getMethod('send'); if (!$method->isPublic() || $method->isStatic()) { return \false; } $parameters = $method->getParameters(); if (count($parameters) < 2) { return \false; } $firstParameter = $parameters[0]->getType(); if (!$firstParameter instanceof \ReflectionNamedType || $firstParameter->isBuiltin()) { return \false; } if (!is_a($firstParameter->getName(), RequestInterface::class, \true)) { return \false; } $secondParameter = $parameters[1]; $secondType = $secondParameter->getType(); if (!$secondType instanceof \ReflectionNamedType || $secondType->getName() !== 'array') { return \false; } return \true; } /** * Sends a request using a Guzzle-compatible client. * * @since 0.2.0 * * @param RequestInterface $request The PSR-7 request to send. * @param RequestOptions $options The request options. * @return ResponseInterface The PSR-7 response received. */ private function sendWithGuzzle(RequestInterface $request, RequestOptions $options): ResponseInterface { $guzzleOptions = $this->buildGuzzleOptions($options); /** @var callable $callable */ $callable = [$this->client, 'send']; /** @var ResponseInterface $response */ $response = $callable($request, $guzzleOptions); return $response; } /** * Converts request options to a Guzzle-compatible options array. * * @since 0.2.0 * * @param RequestOptions $options The request options. * @return array Guzzle-compatible options. */ private function buildGuzzleOptions(RequestOptions $options): array { $guzzleOptions = []; $timeout = $options->getTimeout(); if ($timeout !== null) { $guzzleOptions['timeout'] = $timeout; } $connectTimeout = $options->getConnectTimeout(); if ($connectTimeout !== null) { $guzzleOptions['connect_timeout'] = $connectTimeout; } $allowRedirects = $options->allowsRedirects(); if ($allowRedirects !== null) { if ($allowRedirects) { $redirectOptions = []; $maxRedirects = $options->getMaxRedirects(); if ($maxRedirects !== null) { $redirectOptions['max'] = $maxRedirects; } $guzzleOptions['allow_redirects'] = !empty($redirectOptions) ? $redirectOptions : \true; } else { $guzzleOptions['allow_redirects'] = \false; } } return $guzzleOptions; } /** * Converts a custom Request to a PSR-7 request. * * @since 0.1.0 * * @param Request $request The custom request. * @return RequestInterface The PSR-7 request. */ private function convertToPsr7Request(Request $request): RequestInterface { $psr7Request = $this->requestFactory->createRequest($request->getMethod()->value, $request->getUri()); // Add headers foreach ($request->getHeaders() as $name => $values) { foreach ($values as $value) { $psr7Request = $psr7Request->withAddedHeader($name, $value); } } // Add body if present $body = $request->getBody(); if ($body !== null) { $stream = $this->streamFactory->createStream($body); $psr7Request = $psr7Request->withBody($stream); } return $psr7Request; } /** * Converts a PSR-7 response to a custom Response. * * @since 0.1.0 * * @param ResponseInterface $psr7Response The PSR-7 response. * @return Response The custom response. */ private function convertFromPsr7Response(ResponseInterface $psr7Response): Response { $body = (string) $psr7Response->getBody(); // PSR-7 always returns headers as arrays, but HeadersCollection handles this return new Response( $psr7Response->getStatusCode(), $psr7Response->getHeaders(), // @phpstan-ignore-line $body === '' ? null : $body ); } } HttpTransporterFactory.php000064400000002026152213730340011770 0ustar00code = (int) $data->status_code; } parent::__construct($reason, $data); } } Client/RequestExceptionInterface.php000064400000001207152214025470013625 0ustar00getHeaders() as $name => $values) { * echo $name . ": " . implode(", ", $values); * } * * // Emit headers iteratively: * foreach ($message->getHeaders() as $name => $values) { * foreach ($values as $value) { * header(sprintf('%s: %s', $name, $value), false); * } * } * * While header names are not case-sensitive, getHeaders() will preserve the * exact case in which headers were originally specified. * * @return string[][] Returns an associative array of the message's headers. Each * key MUST be a header name, and each value MUST be an array of strings * for that header. */ public function getHeaders(): array; /** * Checks if a header exists by the given case-insensitive name. * * @param string $name Case-insensitive header field name. * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function hasHeader(string $name): bool; /** * Retrieves a message header value by the given case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $name Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function getHeader(string $name): array; /** * Retrieves a comma-separated string of the values for a single header. * * This method returns all of the header values of the given * case-insensitive header name as a string concatenated together using * a comma. * * NOTE: Not all header values may be appropriately represented using * comma concatenation. For such headers, use getHeader() instead * and supply your own delimiter when concatenating. * * If the header does not appear in the message, this method MUST return * an empty string. * * @param string $name Case-insensitive header field name. * @return string A string of values as provided for the given header * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ public function getHeaderLine(string $name): string; /** * Return an instance with the provided value replacing the specified header. * * While header names are case-insensitive, the casing of the header will * be preserved by this function, and returned from getHeaders(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new and/or updated header and value. * * @param string $name Case-insensitive header field name. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withHeader(string $name, $value): MessageInterface; /** * Return an instance with the specified header appended with the given value. * * Existing values for the specified header will be maintained. The new * value(s) will be appended to the existing list. If the header did not * exist previously, it will be added. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new header and/or value. * * @param string $name Case-insensitive header field name to add. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withAddedHeader(string $name, $value): MessageInterface; /** * Return an instance without the specified header. * * Header resolution MUST be done without case-sensitivity. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the named header. * * @param string $name Case-insensitive header field name to remove. * @return static */ public function withoutHeader(string $name): MessageInterface; /** * Gets the body of the message. * * @return StreamInterface Returns the body as a stream. */ public function getBody(): StreamInterface; /** * Return an instance with the specified message body. * * The body MUST be a StreamInterface object. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. * * @param StreamInterface $body Body. * @return static * @throws \InvalidArgumentException When the body is not valid. */ public function withBody(StreamInterface $body): MessageInterface; } Message/RequestInterface.php000064400000011521152214025470012114 0ustar00getQuery()` * or from the `QUERY_STRING` server param. * * @return array */ public function getQueryParams(): array; /** * Return an instance with the specified query string arguments. * * These values SHOULD remain immutable over the course of the incoming * request. They MAY be injected during instantiation, such as from PHP's * $_GET superglobal, or MAY be derived from some other value such as the * URI. In cases where the arguments are parsed from the URI, the data * MUST be compatible with what PHP's parse_str() would return for * purposes of how duplicate query parameters are handled, and how nested * sets are handled. * * Setting query string arguments MUST NOT change the URI stored by the * request, nor the values in the server params. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated query string arguments. * * @param array $query Array of query string arguments, typically from * $_GET. * @return static */ public function withQueryParams(array $query): ServerRequestInterface; /** * Retrieve normalized file upload data. * * This method returns upload metadata in a normalized tree, with each leaf * an instance of Psr\Http\Message\UploadedFileInterface. * * These values MAY be prepared from $_FILES or the message body during * instantiation, or MAY be injected via withUploadedFiles(). * * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ public function getUploadedFiles(): array; /** * Create a new instance with the specified uploaded files. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param array $uploadedFiles An array tree of UploadedFileInterface instances. * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; /** * Retrieve any parameters provided in the request body. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, this method MUST * return the contents of $_POST. * * Otherwise, this method may return any results of deserializing * the request body content; as parsing returns structured content, the * potential types MUST be arrays or objects only. A null value indicates * the absence of body content. * * @return null|array|object The deserialized body parameters, if any. * These will typically be an array or object. */ public function getParsedBody(); /** * Return an instance with the specified body parameters. * * These MAY be injected during instantiation. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, use this method * ONLY to inject the contents of $_POST. * * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of * deserializing the request body content. Deserialization/parsing returns * structured data, and, as such, this method ONLY accepts arrays or objects, * or a null value if nothing was available to parse. * * As an example, if content negotiation determines that the request data * is a JSON payload, this method could be used to create a request * instance with the deserialized parameters. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param null|array|object $data The deserialized body data. This will * typically be in an array or object. * @return static * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ public function withParsedBody($data): ServerRequestInterface; /** * Retrieve attributes derived from the request. * * The request "attributes" may be used to allow injection of any * parameters derived from the request: e.g., the results of path * match operations; the results of decrypting cookies; the results of * deserializing non-form-encoded message bodies; etc. Attributes * will be application and request specific, and CAN be mutable. * * @return array Attributes derived from the request. */ public function getAttributes(): array; /** * Retrieve a single derived request attribute. * * Retrieves a single derived request attribute as described in * getAttributes(). If the attribute has not been previously set, returns * the default value as provided. * * This method obviates the need for a hasAttribute() method, as it allows * specifying a default value to return if the attribute is not found. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $default Default value to return if the attribute does not exist. * @return mixed */ public function getAttribute(string $name, $default = null); /** * Return an instance with the specified derived request attribute. * * This method allows setting a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated attribute. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $value The value of the attribute. * @return static */ public function withAttribute(string $name, $value): ServerRequestInterface; /** * Return an instance that removes the specified derived request attribute. * * This method allows removing a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the attribute. * * @see getAttributes() * @param string $name The attribute name. * @return static */ public function withoutAttribute(string $name): ServerRequestInterface; } Message/ServerRequestFactoryInterface.php000064400000001676152214025470014645 0ustar00 * [user-info@]host[:port] * * * If the port component is not set or is the standard port for the current * scheme, it SHOULD NOT be included. * * @see https://tools.ietf.org/html/rfc3986#section-3.2 * @return string The URI authority, in "[user-info@]host[:port]" format. */ public function getAuthority(): string; /** * Retrieve the user information component of the URI. * * If no user information is present, this method MUST return an empty * string. * * If a user is present in the URI, this will return that value; * additionally, if the password is also present, it will be appended to the * user value, with a colon (":") separating the values. * * The trailing "@" character is not part of the user information and MUST * NOT be added. * * @return string The URI user information, in "username[:password]" format. */ public function getUserInfo(): string; /** * Retrieve the host component of the URI. * * If no host is present, this method MUST return an empty string. * * The value returned MUST be normalized to lowercase, per RFC 3986 * Section 3.2.2. * * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 * @return string The URI host. */ public function getHost(): string; /** * Retrieve the port component of the URI. * * If a port is present, and it is non-standard for the current scheme, * this method MUST return it as an integer. If the port is the standard port * used with the current scheme, this method SHOULD return null. * * If no port is present, and no scheme is present, this method MUST return * a null value. * * If no port is present, but a scheme is present, this method MAY return * the standard port for that scheme, but SHOULD return null. * * @return null|int The URI port. */ public function getPort(): ?int; /** * Retrieve the path component of the URI. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * Normally, the empty path "" and absolute path "/" are considered equal as * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically * do this normalization because in contexts with a trimmed base path, e.g. * the front controller, this difference becomes significant. It's the task * of the user to handle both "" and "/". * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.3. * * As an example, if the value should include a slash ("/") not intended as * delimiter between path segments, that value MUST be passed in encoded * form (e.g., "%2F") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.3 * @return string The URI path. */ public function getPath(): string; /** * Retrieve the query string of the URI. * * If no query string is present, this method MUST return an empty string. * * The leading "?" character is not part of the query and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.4. * * As an example, if a value in a key/value pair of the query string should * include an ampersand ("&") not intended as a delimiter between values, * that value MUST be passed in encoded form (e.g., "%26") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.4 * @return string The URI query string. */ public function getQuery(): string; /** * Retrieve the fragment component of the URI. * * If no fragment is present, this method MUST return an empty string. * * The leading "#" character is not part of the fragment and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.5. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.5 * @return string The URI fragment. */ public function getFragment(): string; /** * Return an instance with the specified scheme. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified scheme. * * Implementations MUST support the schemes "http" and "https" case * insensitively, and MAY accommodate other schemes if required. * * An empty scheme is equivalent to removing the scheme. * * @param string $scheme The scheme to use with the new instance. * @return static A new instance with the specified scheme. * @throws \InvalidArgumentException for invalid or unsupported schemes. */ public function withScheme(string $scheme): UriInterface; /** * Return an instance with the specified user information. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified user information. * * Password is optional, but the user information MUST include the * user; an empty string for the user is equivalent to removing user * information. * * @param string $user The user name to use for authority. * @param null|string $password The password associated with $user. * @return static A new instance with the specified user information. */ public function withUserInfo(string $user, ?string $password = null): UriInterface; /** * Return an instance with the specified host. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified host. * * An empty host value is equivalent to removing the host. * * @param string $host The hostname to use with the new instance. * @return static A new instance with the specified host. * @throws \InvalidArgumentException for invalid hostnames. */ public function withHost(string $host): UriInterface; /** * Return an instance with the specified port. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified port. * * Implementations MUST raise an exception for ports outside the * established TCP and UDP port ranges. * * A null value provided for the port is equivalent to removing the port * information. * * @param null|int $port The port to use with the new instance; a null value * removes the port information. * @return static A new instance with the specified port. * @throws \InvalidArgumentException for invalid ports. */ public function withPort(?int $port): UriInterface; /** * Return an instance with the specified path. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified path. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * If the path is intended to be domain-relative rather than path relative then * it must begin with a slash ("/"). Paths not starting with a slash ("/") * are assumed to be relative to some base path known to the application or * consumer. * * Users can provide both encoded and decoded path characters. * Implementations ensure the correct encoding as outlined in getPath(). * * @param string $path The path to use with the new instance. * @return static A new instance with the specified path. * @throws \InvalidArgumentException for invalid paths. */ public function withPath(string $path): UriInterface; /** * Return an instance with the specified query string. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified query string. * * Users can provide both encoded and decoded query characters. * Implementations ensure the correct encoding as outlined in getQuery(). * * An empty query string value is equivalent to removing the query string. * * @param string $query The query string to use with the new instance. * @return static A new instance with the specified query string. * @throws \InvalidArgumentException for invalid query strings. */ public function withQuery(string $query): UriInterface; /** * Return an instance with the specified URI fragment. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified URI fragment. * * Users can provide both encoded and decoded fragment characters. * Implementations ensure the correct encoding as outlined in getFragment(). * * An empty fragment value is equivalent to removing the fragment. * * @param string $fragment The fragment to use with the new instance. * @return static A new instance with the specified fragment. */ public function withFragment(string $fragment): UriInterface; /** * Return the string representation as a URI reference. * * Depending on which components of the URI are present, the resulting * string is either a full URI or relative reference according to RFC 3986, * Section 4.1. The method concatenates the various components of the URI, * using the appropriate delimiters: * * - If a scheme is present, it MUST be suffixed by ":". * - If an authority is present, it MUST be prefixed by "//". * - The path can be concatenated without delimiters. But there are two * cases where the path has to be adjusted to make the URI reference * valid as PHP does not allow to throw an exception in __toString(): * - If the path is rootless and an authority is present, the path MUST * be prefixed by "/". * - If the path is starting with more than one "/" and no authority is * present, the starting slashes MUST be reduced to one. * - If a query is present, it MUST be prefixed by "?". * - If a fragment is present, it MUST be prefixed by "#". * * @see http://tools.ietf.org/html/rfc3986#section-4.1 * @return string */ public function __toString(): string; } Message/StreamFactoryInterface.php000064400000002647152214025470013260 0ustar00 */ final class PuliUnavailableException extends StrategyUnavailableException { } Discovery/Exception/NotFoundException.php000064400000000631152214131030014565 0ustar00 */ /* final */ class NotFoundException extends \RuntimeException implements Exception { } Discovery/Exception/StrategyUnavailableException.php000064400000000643152214131030017002 0ustar00 */ class StrategyUnavailableException extends \RuntimeException implements Exception { } Discovery/Exception/DiscoveryFailedException.php000064400000002331152214131030016104 0ustar00 */ 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; } } Discovery/Exception/ClassInstantiationFailedException.php000064400000000524152214131030017751 0ustar00 */ final class ClassInstantiationFailedException extends \RuntimeException implements Exception { } Discovery/Exception/NoCandidateFoundException.php000064400000002203152214131030016173 0ustar00 */ 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); } } Discovery/Strategy/DiscoveryStrategy.php000064400000001242152214131030014507 0ustar00 */ 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); } Discovery/Strategy/PuliBetaStrategy.php000064400000004515152214131030014253 0ustar00 * @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; } } Discovery/Strategy/CommonClassesStrategy.php000064400000020522152214131030015310 0ustar00 * * 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' => [\WordPress\AiClientDependencies\Buzz\Client\FileGetContents::class, \WordPress\AiClientDependencies\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' => [\WordPress\AiClientDependencies\Buzz\Client\FileGetContents::class, \WordPress\AiClientDependencies\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 \WordPress\AiClientDependencies\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; } } Discovery/Strategy/CommonPsr17ClassesStrategy.php000064400000010055152214131030016145 0ustar00 * * 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', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\RequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\RequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\RequestFactory', 'Laminas\Diactoros\RequestFactory', 'Slim\Psr7\Factory\RequestFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\RequestFactory'], ResponseFactoryInterface::class => ['Phalcon\Http\Message\ResponseFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\ResponseFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\ResponseFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\ResponseFactory', 'Laminas\Diactoros\ResponseFactory', 'Slim\Psr7\Factory\ResponseFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\ResponseFactory'], ServerRequestFactoryInterface::class => ['Phalcon\Http\Message\ServerRequestFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\ServerRequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\ServerRequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\ServerRequestFactory', 'Laminas\Diactoros\ServerRequestFactory', 'Slim\Psr7\Factory\ServerRequestFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\ServerRequestFactory'], StreamFactoryInterface::class => ['Phalcon\Http\Message\StreamFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\StreamFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\StreamFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\StreamFactory', 'Laminas\Diactoros\StreamFactory', 'Slim\Psr7\Factory\StreamFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\StreamFactory'], UploadedFileFactoryInterface::class => ['Phalcon\Http\Message\UploadedFileFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\UploadedFileFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\UploadedFileFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\UploadedFileFactory', 'Laminas\Diactoros\UploadedFileFactory', 'Slim\Psr7\Factory\UploadedFileFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\UploadedFileFactory'], UriFactoryInterface::class => ['Phalcon\Http\Message\UriFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\UriFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\UriFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\UriFactory', 'Laminas\Diactoros\UriFactory', 'Slim\Psr7\Factory\UriFactory', 'WordPress\AiClientDependencies\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; } } Discovery/Exception.php000064400000000353152214131030011153 0ustar00 */ interface Exception extends \Throwable { } Discovery/Psr18ClientDiscovery.php000064400000002016152214131030013157 0ustar00 */ 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); } } Discovery/Psr17FactoryDiscovery.php000064400000007761152214131030013363 0ustar00 */ 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(); } } Discovery/ClassDiscovery.php000064400000015652152214131030012162 0ustar00 * @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; } } } Discovery/error_log000064400000001436152214131030010424 0ustar00[02-Jul-2026 05:52:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/toreyh/public_html/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php:18 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php on line 18 [02-Jul-2026 06:55:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/toreyh/public_html/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php:13 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php on line 13