lol backups and old site first commit

This commit is contained in:
2026-09-06 00:14:06 +02:00
parent 1e8a86873d
commit 56e7e28142
4021 changed files with 1866999 additions and 0 deletions
@@ -0,0 +1,92 @@
<?php
declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;
use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
/**
* Class for HTTP request authentication using an API key.
*
* @since 0.1.0
*
* @phpstan-type ApiKeyRequestAuthenticationArrayShape array{
* apiKey: string
* }
*
* @extends AbstractDataTransferObject<ApiKeyRequestAuthenticationArrayShape>
*/
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]];
}
}
@@ -0,0 +1,377 @@
<?php
declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;
use JsonException;
use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
/**
* Represents an HTTP request.
*
* This class encapsulates HTTP request data that can be converted
* to PSR-7 requests by the HTTP transporter.
*
* @since 0.1.0
*
* @phpstan-import-type RequestOptionsArrayShape from RequestOptions
* @phpstan-type RequestArrayShape array{
* method: string,
* uri: string,
* headers: array<string, list<string>>,
* body?: string|null,
* options?: RequestOptionsArrayShape
* }
*
* @extends AbstractDataTransferObject<RequestArrayShape>
*/
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<string, mixed>|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<string, string|list<string>> $headers The request headers.
* @param string|array<string, mixed>|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<string, list<string>> 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<string>|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<string> $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<string, mixed> $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<string, mixed>|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<string, list<string>> $headers */
$headers = $psrRequest->getHeaders();
// Get body content
$body = $psrRequest->getBody()->getContents();
$bodyOrData = !empty($body) ? $body : null;
return new self($method, $uri, $headers, $bodyOrData);
}
}
@@ -0,0 +1,204 @@
<?php
declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;
use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
* Represents optional HTTP transport configuration for a single request.
*
* Provides mutable setters for working with timeouts and redirect handling.
*
* @since 0.2.0
*
* @phpstan-type RequestOptionsArrayShape array{
* timeout?: float|null,
* connectTimeout?: float|null,
* maxRedirects?: int|null
* }
*
* @extends AbstractDataTransferObject<RequestOptionsArrayShape>
*/
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));
}
}
}
@@ -0,0 +1,211 @@
<?php
declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;
use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
/**
* Represents an HTTP response.
*
* This class encapsulates HTTP response data that has been converted
* from PSR-7 responses by the HTTP transporter.
*
* @since 0.1.0
*
* @phpstan-type ResponseArrayShape array{
* statusCode: int,
* headers: array<string, list<string>>,
* body?: string|null
* }
*
* @extends AbstractDataTransferObject<ResponseArrayShape>
*/
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<string, string|list<string>> $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<string, list<string>> 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<string>|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<string, mixed>|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<string, mixed>|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);
}
}