PK     O\n    )  class-wp-ai-client-discovery-strategy.phpnu [        <?php
/**
 * WP AI Client: WP_AI_Client_Discovery_Strategy class
 *
 * @package WordPress
 * @subpackage AI
 * @since 7.0.0
 */

use WordPress\AiClient\Providers\Http\Abstracts\AbstractClientDiscoveryStrategy;
use WordPress\AiClientDependencies\Nyholm\Psr7\Factory\Psr17Factory;
use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface;

/**
 * Discovery strategy for WordPress HTTP client.
 *
 * Registers the WordPress HTTP client adapter with the HTTPlug discovery system
 * so the AI Client SDK can find and use it automatically.
 *
 * @since 7.0.0
 * @internal Intended only to register WordPress's HTTP client so that the PHP AI Client SDK can use it.
 * @access private
 */
class WP_AI_Client_Discovery_Strategy extends AbstractClientDiscoveryStrategy {

	/**
	 * Creates an instance of the WordPress HTTP client.
	 *
	 * @since 7.0.0
	 *
	 * @param Psr17Factory $psr17_factory The PSR-17 factory for creating HTTP messages.
	 * @return ClientInterface The PSR-18 HTTP client.
	 */
	protected static function createClient( Psr17Factory $psr17_factory ): ClientInterface {
		return new WP_AI_Client_HTTP_Client( $psr17_factory, $psr17_factory );
	}
}
PK     O\x/  /  "  class-wp-ai-client-http-client.phpnu [        <?php
/**
 * WP AI Client: WP_AI_Client_HTTP_Client class
 *
 * @package WordPress
 * @subpackage AI
 * @since 7.0.0
 */

use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface;
use WordPress\AiClient\Providers\Http\Contracts\ClientWithOptionsInterface;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Http\Exception\NetworkException;

/**
 * PSR-18 HTTP Client adapter using WordPress HTTP API.
 *
 * Allows WordPress HTTP functions to be used as a PSR-18 compliant HTTP client
 * for the AI Client SDK.
 *
 * @since 7.0.0
 * @internal Intended only to wire up the PHP AI Client SDK to WordPress's HTTP client.
 * @access private
 */
class WP_AI_Client_HTTP_Client implements ClientInterface, ClientWithOptionsInterface {

	/**
	 * Response factory instance.
	 *
	 * @since 7.0.0
	 */
	private ResponseFactoryInterface $response_factory;

	/**
	 * Stream factory instance.
	 *
	 * @since 7.0.0
	 */
	private StreamFactoryInterface $stream_factory;

	/**
	 * Constructor.
	 *
	 * @since 7.0.0
	 *
	 * @param ResponseFactoryInterface $response_factory PSR-17 Response factory.
	 * @param StreamFactoryInterface   $stream_factory   PSR-17 Stream factory.
	 */
	public function __construct( ResponseFactoryInterface $response_factory, StreamFactoryInterface $stream_factory ) {
		$this->response_factory = $response_factory;
		$this->stream_factory   = $stream_factory;
	}

	/**
	 * Sends a PSR-7 request and returns a PSR-7 response.
	 *
	 * @since 7.0.0
	 *
	 * @param RequestInterface $request The PSR-7 request.
	 * @return ResponseInterface The PSR-7 response.
	 *
	 * @throws NetworkException If the WordPress HTTP request fails.
	 */
	public function sendRequest( RequestInterface $request ): ResponseInterface {
		$args = $this->prepare_wp_args( $request );
		$url  = (string) $request->getUri();

		$response = wp_safe_remote_request( $url, $args );

		if ( is_wp_error( $response ) ) {
			$message = sprintf(
				/* translators: 1: HTTP method (e.g. GET, POST). 2: Request URL. 3: Error message. */
				__( 'Network error occurred while sending %1$s request to %2$s: %3$s' ),
				$request->getMethod(),
				$url,
				$response->get_error_message()
			);
			throw new NetworkException( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		return $this->create_psr_response( $response );
	}

	/**
	 * Sends a PSR-7 request with transport options and returns a PSR-7 response.
	 *
	 * @since 7.0.0
	 *
	 * @param RequestInterface $request The PSR-7 request.
	 * @param RequestOptions   $options Transport options for the request.
	 * @return ResponseInterface The PSR-7 response.
	 *
	 * @throws NetworkException If the WordPress HTTP request fails.
	 */
	public function sendRequestWithOptions( RequestInterface $request, RequestOptions $options ): ResponseInterface {
		$args = $this->prepare_wp_args( $request, $options );
		$url  = (string) $request->getUri();

		$response = wp_safe_remote_request( $url, $args );

		if ( is_wp_error( $response ) ) {
			$message = sprintf(
				/* translators: 1: Request URL. 2: Error message. */
				__( 'Network error occurred while sending request to %1$s: %2$s' ),
				$url,
				$response->get_error_message()
			);

			throw new NetworkException(
				$message, // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
				$response->get_error_code() ? (int) $response->get_error_code() : 0
			);
		}

		return $this->create_psr_response( $response );
	}

	/**
	 * Prepares WordPress HTTP API arguments from a PSR-7 request.
	 *
	 * @since 7.0.0
	 *
	 * @param RequestInterface    $request The PSR-7 request.
	 * @param RequestOptions|null $options Optional transport options for the request.
	 * @return array<string, mixed> WordPress HTTP API arguments.
	 */
	private function prepare_wp_args( RequestInterface $request, ?RequestOptions $options = null ): array {
		$args = array(
			'method'      => $request->getMethod(),
			'headers'     => $this->prepare_headers( $request ),
			'body'        => $this->prepare_body( $request ),
			'httpversion' => $request->getProtocolVersion(),
			'blocking'    => true,
		);

		if ( null !== $options ) {
			if ( null !== $options->getTimeout() ) {
				$args['timeout'] = $options->getTimeout();
			}

			if ( null !== $options->getMaxRedirects() ) {
				$args['redirection'] = $options->getMaxRedirects();
			}
		}

		return $args;
	}

	/**
	 * Prepares headers for WordPress HTTP API.
	 *
	 * @since 7.0.0
	 *
	 * @param RequestInterface $request The PSR-7 request.
	 * @return array<string, string> Headers array for WordPress HTTP API.
	 */
	private function prepare_headers( RequestInterface $request ): array {
		$headers = array();

		foreach ( $request->getHeaders() as $name => $values ) {
			$headers[ (string) $name ] = implode( ', ', $values );
		}

		return $headers;
	}

	/**
	 * Prepares request body for WordPress HTTP API.
	 *
	 * @since 7.0.0
	 *
	 * @param RequestInterface $request The PSR-7 request.
	 * @return string|null The request body.
	 */
	private function prepare_body( RequestInterface $request ): ?string {
		$body = $request->getBody();

		if ( $body->getSize() === 0 ) {
			return null;
		}

		if ( $body->isSeekable() ) {
			$body->rewind();
		}

		return (string) $body;
	}

	/**
	 * Creates a PSR-7 response from a WordPress HTTP response.
	 *
	 * @since 7.0.0
	 *
	 * @param array<string, mixed> $wp_response WordPress HTTP API response array.
	 * @return ResponseInterface PSR-7 response.
	 */
	private function create_psr_response( array $wp_response ): ResponseInterface {
		$status_code   = wp_remote_retrieve_response_code( $wp_response );
		$reason_phrase = wp_remote_retrieve_response_message( $wp_response );
		$headers       = wp_remote_retrieve_headers( $wp_response );
		$body          = wp_remote_retrieve_body( $wp_response );

		$response = $this->response_factory->createResponse( (int) $status_code, $reason_phrase );

		if ( $headers instanceof WP_HTTP_Requests_Response ) {
			$headers = $headers->get_headers();
		}

		if ( is_array( $headers ) || $headers instanceof Traversable ) {
			foreach ( $headers as $name => $value ) {
				$response = $response->withHeader( $name, $value );
			}
		}

		if ( ! empty( $body ) ) {
			$stream   = $this->stream_factory->createStream( $body );
			$response = $response->withBody( $stream );
		}

		return $response;
	}
}
PK     O\	  	  '  class-wp-ai-client-event-dispatcher.phpnu [        <?php
/**
 * WP AI Client: WP_AI_Client_Event_Dispatcher class
 *
 * @package WordPress
 * @subpackage AI
 * @since 7.0.0
 */

use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface;

/**
 * WordPress-specific PSR-14 event dispatcher for the AI Client.
 *
 * Bridges PSR-14 events to WordPress action hooks, enabling plugins to hook
 * into AI client lifecycle events.
 *
 * @since 7.0.0
 * @internal Intended only to wire up the PHP AI Client SDK to WordPress's hook system.
 * @access private
 */
class WP_AI_Client_Event_Dispatcher implements EventDispatcherInterface {

	/**
	 * Dispatches an event to WordPress action hooks.
	 *
	 * Converts the event class name to a WordPress action hook name and fires it.
	 * For example, BeforeGenerateResultEvent becomes wp_ai_client_before_generate_result.
	 *
	 * @since 7.0.0
	 *
	 * @param object $event The event object to dispatch.
	 * @return object The same event object, potentially modified by listeners.
	 */
	public function dispatch( object $event ): object {
		$event_name = $this->get_hook_name_portion_for_event( $event );

		/**
		 * Fires when an AI client event is dispatched.
		 *
		 * The dynamic portion of the hook name, `$event_name`, refers to the
		 * snake_case version of the event class name, without the `_event` suffix.
		 *
		 * For example, an event class named `BeforeGenerateResultEvent` will fire the
		 * `wp_ai_client_before_generate_result` action hook.
		 *
		 * In practice, the available action hook names are:
		 *
		 * - wp_ai_client_before_generate_result
		 * - wp_ai_client_after_generate_result
		 *
		 * @since 7.0.0
		 *
		 * @param object $event The event object.
		 */
		do_action( "wp_ai_client_{$event_name}", $event );

		return $event;
	}

	/**
	 * Converts an event object class name to a WordPress action hook name portion.
	 *
	 * @since 7.0.0
	 *
	 * @param object $event The event object.
	 * @return string The hook name portion derived from the event class name.
	 */
	private function get_hook_name_portion_for_event( object $event ): string {
		$class_name = get_class( $event );
		$pos        = strrpos( $class_name, '\\' );
		$short_name = false !== $pos ? substr( $class_name, $pos + 1 ) : $class_name;

		// Convert PascalCase to snake_case.
		$snake_case = strtolower( (string) preg_replace( '/([a-z])([A-Z])/', '$1_$2', $short_name ) );

		// Strip '_event' suffix if present.
		if ( str_ends_with( $snake_case, '_event' ) ) {
			$snake_case = (string) substr( $snake_case, 0, -6 );
		}

		return $snake_case;
	}
}
PK     O\tM{      class-wp-ai-client-cache.phpnu [        <?php
/**
 * WP AI Client: WP_AI_Client_Cache class
 *
 * @package WordPress
 * @subpackage AI
 * @since 7.0.0
 */

use WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface;

/**
 * WordPress-specific PSR-16 cache adapter for the AI Client.
 *
 * Bridges PSR-16 cache operations to WordPress object cache functions,
 * enabling the AI client to leverage WordPress caching infrastructure.
 *
 * @since 7.0.0
 * @internal Intended only to wire up the PHP AI Client SDK to WordPress's caching system.
 * @access private
 */
class WP_AI_Client_Cache implements CacheInterface {

	/**
	 * Cache group used for all cache operations.
	 *
	 * @since 7.0.0
	 * @var string
	 */
	private const CACHE_GROUP = 'wp_ai_client';

	/**
	 * Fetches a value from the cache.
	 *
	 * @since 7.0.0
	 *
	 * @param string $key           The unique key of this item in the cache.
	 * @param mixed  $default_value Default value to return if the key does not exist.
	 * @return mixed The value of the item from the cache, or $default_value in case of cache miss.
	 */
	public function get( $key, $default_value = null ) {
		$found = false;
		$value = wp_cache_get( $key, self::CACHE_GROUP, false, $found );

		if ( ! $found ) {
			return $default_value;
		}

		return $value;
	}

	/**
	 * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
	 *
	 * @since 7.0.0
	 *
	 * @param string                $key   The key of the item to store.
	 * @param mixed                 $value The value of the item to store, must be serializable.
	 * @param null|int|DateInterval $ttl   Optional. The TTL value of this item.
	 * @return bool True on success and false on failure.
	 */
	public function set( $key, $value, $ttl = null ): bool {
		$expire = $this->ttl_to_seconds( $ttl );

		return wp_cache_set( $key, $value, self::CACHE_GROUP, $expire );
	}

	/**
	 * Delete an item from the cache by its unique key.
	 *
	 * @since 7.0.0
	 *
	 * @param string $key The unique cache key of the item to delete.
	 * @return bool True if the item was successfully removed. False if there was an error.
	 */
	public function delete( $key ): bool {
		return wp_cache_delete( $key, self::CACHE_GROUP );
	}

	/**
	 * Wipes clean the entire cache's keys.
	 *
	 * This method only clears the cache group used by this adapter. If the underlying
	 * cache implementation does not support group flushing, this method returns false.
	 *
	 * @since 7.0.0
	 *
	 * @return bool True on success and false on failure.
	 */
	public function clear(): bool {
		if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) {
			return false;
		}

		return wp_cache_flush_group( self::CACHE_GROUP );
	}

	/**
	 * Obtains multiple cache items by their unique keys.
	 *
	 * @since 7.0.0
	 *
	 * @param iterable<string> $keys          A list of keys that can be obtained in a single operation.
	 * @param mixed            $default_value Default value to return for keys that do not exist.
	 * @return array<string, mixed> A list of key => value pairs.
	 */
	public function getMultiple( $keys, $default_value = null ): array {
		/**
		 * Keys array.
		 *
		 * @var array<string> $keys_array
		 */
		$keys_array = $this->iterable_to_array( $keys );
		$values     = wp_cache_get_multiple( $keys_array, self::CACHE_GROUP );
		$result     = array();

		foreach ( $keys_array as $key ) {
			if ( false === $values[ $key ] ) {
				// Could be a stored false or a cache miss — disambiguate via get().
				$result[ $key ] = $this->get( $key, $default_value );
			} else {
				$result[ $key ] = $values[ $key ];
			}
		}

		return $result;
	}

	/**
	 * Persists a set of key => value pairs in the cache, with an optional TTL.
	 *
	 * @since 7.0.0
	 *
	 * @param iterable<string, mixed> $values A list of key => value pairs for a multiple-set operation.
	 * @param null|int|DateInterval   $ttl    Optional. The TTL value of this item.
	 * @return bool True on success and false on failure.
	 */
	public function setMultiple( $values, $ttl = null ): bool {
		$values_array = $this->iterable_to_array( $values );
		$expire       = $this->ttl_to_seconds( $ttl );
		$results      = wp_cache_set_multiple( $values_array, self::CACHE_GROUP, $expire );

		// Return true only if all operations succeeded.
		return ! in_array( false, $results, true );
	}

	/**
	 * Deletes multiple cache items in a single operation.
	 *
	 * @since 7.0.0
	 *
	 * @param iterable<string> $keys A list of string-based keys to be deleted.
	 * @return bool True if the items were successfully removed. False if there was an error.
	 */
	public function deleteMultiple( $keys ): bool {
		$keys_array = $this->iterable_to_array( $keys );
		$results    = wp_cache_delete_multiple( $keys_array, self::CACHE_GROUP );

		// Return true only if all operations succeeded.
		return ! in_array( false, $results, true );
	}

	/**
	 * Determines whether an item is present in the cache.
	 *
	 * @since 7.0.0
	 *
	 * @param string $key The cache item key.
	 * @return bool True if the item exists in the cache, false otherwise.
	 */
	public function has( $key ): bool {
		$found = false;
		wp_cache_get( $key, self::CACHE_GROUP, false, $found );

		return (bool) $found;
	}

	/**
	 * Converts a PSR-16 TTL value to seconds for WordPress cache functions.
	 *
	 * @since 7.0.0
	 *
	 * @param null|int|DateInterval $ttl The TTL value.
	 * @return int The TTL in seconds, or 0 for no expiration.
	 */
	private function ttl_to_seconds( $ttl ): int {
		if ( null === $ttl ) {
			return 0;
		}

		if ( $ttl instanceof DateInterval ) {
			$now = new DateTime();
			$end = ( clone $now )->add( $ttl );

			return $end->getTimestamp() - $now->getTimestamp();
		}

		return max( 0, (int) $ttl );
	}

	/**
	 * Converts an iterable to an array.
	 *
	 * @since 7.0.0
	 *
	 * @param iterable<mixed> $items The iterable to convert.
	 * @return array<mixed> The array.
	 */
	private function iterable_to_array( $items ): array {
		if ( is_array( $items ) ) {
			return $items;
		}

		return iterator_to_array( $items );
	}
}
PK       O\n    )                class-wp-ai-client-discovery-strategy.phpnu [        PK       O\x/  /  "              class-wp-ai-client-http-client.phpnu [        PK       O\	  	  '            i  class-wp-ai-client-event-dispatcher.phpnu [        PK       O\tM{                )  class-wp-ai-client-cache.phpnu [        PK        A 