diff --git a/.editorconfig b/.editorconfig index b5df0fc..c890705 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,6 @@ insert_final_newline = true trim_trailing_whitespace = true indent_style = tab -[*.{yml,yaml}] +[*.{yml,yaml,neon.dist}] indent_style = space indent_size = 2 diff --git a/composer.json b/composer.json index aa838d2..354d61a 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "require": { "php": ">=7.4", "ext-json": "*", + "nyholm/psr7": "^1.5", "wordpress/php-ai-client": "^0.2" }, "require-dev": { @@ -39,7 +40,7 @@ "phpstan/phpstan": "^1.10 | ^2.1", "slevomat/coding-standard": "^8.0", "squizlabs/php_codesniffer": "^3.7", - "szepeviktor/phpstan-wordpress": "^1.3", + "szepeviktor/phpstan-wordpress": "^2.0", "wp-coding-standards/wpcs": "^3.0" }, "config": { @@ -59,6 +60,6 @@ ], "phpcs": "phpcs", "phpcbf": "phpcbf", - "phpstan": "phpstan analyze --memory-limit=1024M" + "phpstan": "phpstan analyze --memory-limit=512M" } } diff --git a/composer.lock b/composer.lock index 3257540..0ceb96b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,86 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5b8fdfe3093f519c707f0e4fbd115da6", + "content-hash": "14a5a5c47621282c11b2c64e09c4baa3", "packages": [ + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, { "name": "php-http/discovery", "version": "1.20.0", @@ -630,16 +708,16 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.8.2", + "version": "v6.8.3", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8" + "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8", - "reference": "9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/abeb5a8b58fda7ac21f15ee596f302f2959a7114", + "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114", "shasum": "" }, "conflict": { @@ -675,9 +753,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.2" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.3" }, - "time": "2025-07-16T06:41:00+00:00" + "time": "2025-09-30T20:58:47+00:00" }, { "name": "phpcompatibility/php-compatibility", @@ -894,16 +972,16 @@ }, { "name": "phpcsstandards/phpcsextra", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "882b8c947ada27eb002870fe77fee9ce0a454cdb" + "reference": "8e89a01c7b8fed84a12a2a7f5a23a44cdbe4f62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/882b8c947ada27eb002870fe77fee9ce0a454cdb", - "reference": "882b8c947ada27eb002870fe77fee9ce0a454cdb", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/8e89a01c7b8fed84a12a2a7f5a23a44cdbe4f62e", + "reference": "8e89a01c7b8fed84a12a2a7f5a23a44cdbe4f62e", "shasum": "" }, "require": { @@ -972,7 +1050,7 @@ "type": "thanks_dev" } ], - "time": "2025-09-05T06:54:52+00:00" + "time": "2025-10-28T17:00:02+00:00" }, { "name": "phpcsstandards/phpcsutils", @@ -1116,15 +1194,15 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.32", + "version": "2.1.31", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", - "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1165,7 +1243,7 @@ "type": "github" } ], - "time": "2025-09-30T10:16:31+00:00" + "time": "2025-10-10T14:14:11+00:00" }, { "name": "sirbrillig/phpcs-variable-analysis", @@ -1290,16 +1368,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.4", + "version": "3.13.5", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119" + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119", - "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { @@ -1316,11 +1394,6 @@ "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -1370,114 +1443,34 @@ "type": "thanks_dev" } ], - "time": "2025-09-05T05:47:09+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-11-04T16:30:35+00:00" }, { "name": "szepeviktor/phpstan-wordpress", - "version": "v1.3.5", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/szepeviktor/phpstan-wordpress.git", - "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7" + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/7f8cfe992faa96b6a33bbd75c7bace98864161e7", - "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/aa722f037b2d034828cd6c55ebe9e5c74961927e", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", - "phpstan/phpstan": "^1.10.31", - "symfony/polyfill-php73": "^1.12.0" + "php": "^7.4 || ^8.0", + "php-stubs/wordpress-stubs": "^6.6.2", + "phpstan/phpstan": "^2.0" }, "require-dev": { "composer/composer": "^2.1.14", + "composer/semver": "^3.4", "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.1", - "phpstan/phpstan-strict-rules": "^1.2", - "phpunit/phpunit": "^8.0 || ^9.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.0", "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" }, @@ -1511,9 +1504,9 @@ ], "support": { "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", - "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.5" + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.3" }, - "time": "2024-06-28T22:27:19+00:00" + "time": "2025-09-14T02:58:22+00:00" }, { "name": "wp-coding-standards/wpcs", diff --git a/includes/API_Credentials/API_Credentials_Manager.php b/includes/API_Credentials/API_Credentials_Manager.php index bfb8ad1..4ca97ba 100644 --- a/includes/API_Credentials/API_Credentials_Manager.php +++ b/includes/API_Credentials/API_Credentials_Manager.php @@ -76,6 +76,8 @@ function () { * is registered in. * * @since n.e.x.t + * + * @throws RuntimeException If the collected provider metadata is in an invalid format. */ private function collect_providers(): void { /** @@ -95,13 +97,22 @@ private function collect_providers(): void { foreach ( $provider_ids as $provider_id ) { // If the provider was already found via another client class, just add this client class name to the list. if ( isset( $wp_ai_client_providers_metadata[ $provider_id ] ) ) { + if ( ! is_array( $wp_ai_client_providers_metadata[ $provider_id ]['ai_client_classnames'] ) ) { + throw new RuntimeException( 'Invalid format for collected provider AI client class names.' ); + } $wp_ai_client_providers_metadata[ $provider_id ]['ai_client_classnames'][ AiClient::class ] = true; continue; } // Otherwise, get the provider metadata and add it to the global. $provider_class_name = $registry->getProviderClassName( $provider_id ); - $provider_metadata = $provider_class_name::metadata(); + + /** + * The provider metadata. + * + * @var ProviderMetadata + */ + $provider_metadata = $provider_class_name::metadata(); $wp_ai_client_providers_metadata[ $provider_id ] = array_merge( $provider_metadata->toArray(), @@ -191,6 +202,10 @@ private function register_settings(): void { $credentials = array_intersect_key( $credentials, $providers_metadata_keyed_by_ids ); foreach ( $credentials as $provider_id => $api_key ) { + if ( ! is_string( $api_key ) ) { + unset( $credentials[ $provider_id ] ); + continue; + } $credentials[ $provider_id ] = sanitize_text_field( $api_key ); } return $credentials; @@ -218,7 +233,7 @@ private function pass_credentials_to_client(): void { // Set available API keys for all registered providers. foreach ( $credentials as $provider_id => $api_key ) { - if ( '' === $api_key ) { + if ( ! is_string( $api_key ) || '' === $api_key ) { continue; } @@ -248,8 +263,16 @@ private function add_admin_screen(): void { // Bail if the screen was already added (e.g. by another instance of this package). if ( - isset( $_wp_submenu_nopriv[ $parent_slug ][ $screen_slug ] ) || - isset( $_parent_pages[ $screen_slug ] ) + ( + is_array( $_wp_submenu_nopriv ) && + isset( $_wp_submenu_nopriv[ $parent_slug ] ) && + is_array( $_wp_submenu_nopriv[ $parent_slug ] ) && + isset( $_wp_submenu_nopriv[ $parent_slug ][ $screen_slug ] ) + ) || + ( + is_array( $_parent_pages ) && + isset( $_parent_pages[ $screen_slug ] ) + ) ) { return; } diff --git a/includes/API_Credentials/API_Credentials_Settings_Screen.php b/includes/API_Credentials/API_Credentials_Settings_Screen.php index 4552151..47aa536 100644 --- a/includes/API_Credentials/API_Credentials_Settings_Screen.php +++ b/includes/API_Credentials/API_Credentials_Settings_Screen.php @@ -201,7 +201,13 @@ public function render_field( array $args ): void { $option = get_option( $parts[0] ); $subkey = trim( $parts[1], ']' ); if ( is_array( $option ) && isset( $option[ $subkey ] ) ) { - $value = $option[ $subkey ]; + if ( is_string( $option[ $subkey ] ) ) { + $value = $option[ $subkey ]; + } elseif ( is_numeric( $option[ $subkey ] ) ) { + $value = (string) $option[ $subkey ]; + } else { + $value = ''; + } } else { $value = ''; } diff --git a/includes/HTTP/WP_AI_Client_Discovery_Strategy.php b/includes/HTTP/WP_AI_Client_Discovery_Strategy.php new file mode 100644 index 0000000..ca03e9b --- /dev/null +++ b/includes/HTTP/WP_AI_Client_Discovery_Strategy.php @@ -0,0 +1,91 @@ +> + */ + public static function getCandidates( $type ) { + // PSR-18 HTTP Client. + if ( ClientInterface::class === $type ) { + return array( + array( + 'class' => static function () { + return self::createWordPressClient(); + }, + ), + ); + } + + // PSR-17 factories - Nyholm's Psr17Factory implements all of them. + $psr17_factories = array( + 'Psr\Http\Message\RequestFactoryInterface', + 'Psr\Http\Message\ResponseFactoryInterface', + 'Psr\Http\Message\ServerRequestFactoryInterface', + 'Psr\Http\Message\StreamFactoryInterface', + 'Psr\Http\Message\UploadedFileFactoryInterface', + 'Psr\Http\Message\UriFactoryInterface', + ); + + if ( in_array( $type, $psr17_factories, true ) ) { + return array( + array( + 'class' => Psr17Factory::class, + ), + ); + } + + return array(); + } + + /** + * Create an instance of the WordPress HTTP client. + * + * @return WordPress_HTTP_Client + */ + private static function createWordPressClient() { + $psr17_factory = new Psr17Factory(); + return new WordPress_HTTP_Client( + $psr17_factory, // Response factory. + $psr17_factory // Stream factory. + ); + } +} diff --git a/includes/HTTP/WordPress_HTTP_Client.php b/includes/HTTP/WordPress_HTTP_Client.php new file mode 100644 index 0000000..d12b318 --- /dev/null +++ b/includes/HTTP/WordPress_HTTP_Client.php @@ -0,0 +1,239 @@ +response_factory = $response_factory; + $this->stream_factory = $stream_factory; + } + + /** + * Sends a PSR-7 request and returns a PSR-7 response. + * + * @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(); + + /** Ignoring PHPStan for WordPress-specific array structure. @phpstan-ignore-next-line */ + $response = \wp_remote_request( $url, $args ); + + if ( \is_wp_error( $response ) ) { + $message = sprintf( + 'Network error occurred while sending %s request to %s: %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 n.e.x.t + * + * @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(); + + /** Ignoring PHPStan for WordPress-specific array structure. @phpstan-ignore-next-line */ + $response = \wp_remote_request( $url, $args ); + + if ( \is_wp_error( $response ) ) { + $message = sprintf( + 'Network error occurred while sending request to %s: %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 ); + } + + /** + * Prepare WordPress HTTP API arguments from PSR-7 request. + * + * @param RequestInterface $request The PSR-7 request. + * @param RequestOptions|null $options Optional transport options for the request. + * + * @return array 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, + ); + + // Apply options if provided. + if ( null !== $options ) { + // Set timeout if specified. + if ( null !== $options->getTimeout() ) { + $args['timeout'] = $options->getTimeout(); + } + + // Set redirection if specified. + if ( null !== $options->getMaxRedirects() ) { + $args['redirection'] = $options->getMaxRedirects(); + } + } + + return $args; + } + + /** + * Prepare headers for WordPress HTTP API. + * + * @param RequestInterface $request The PSR-7 request. + * + * @return array Headers array for WordPress HTTP API. + */ + private function prepare_headers( RequestInterface $request ): array { + $headers = array(); + + foreach ( $request->getHeaders() as $name => $values ) { + // Skip pseudo headers used for streaming. + if ( strpos( $name, 'X-Stream' ) === 0 ) { + continue; + } + + // WordPress expects headers as name => value pairs. + $headers[ (string) $name ] = implode( ', ', $values ); + } + + return $headers; + } + + /** + * Prepare request body for WordPress HTTP API. + * + * @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; + } + + // Rewind the stream to ensure we read from the beginning. + if ( $body->isSeekable() ) { + $body->rewind(); + } + + return (string) $body; + } + + /** + * Create PSR-7 response from WordPress HTTP response. + * + * @param array{headers: \Traversable>|array>, body: string, response: array{code: int|string, message: string}} $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 ); + + // Create the PSR-7 response. + $response = $this->response_factory->createResponse( (int) $status_code, $reason_phrase ); + + // Add headers to response. + if ( $headers instanceof \WP_HTTP_Requests_Response ) { + $headers = $headers->get_headers(); + } + + /** + * Headers from WordPress response. + * + * @var \Traversable>|array> $headers + */ + if ( is_array( $headers ) || $headers instanceof \Traversable ) { + foreach ( $headers as $name => $value ) { + // PSR-7 expects string name and string|array value. + $response = $response->withHeader( $name, $value ); + } + } + + // Set the response body. + if ( ! empty( $body ) ) { + $stream = $this->stream_factory->createStream( $body ); + $response = $response->withBody( $stream ); + } + + return $response; + } +} diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cd90f7a..d01edfc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,9 @@ parameters: - level: max - paths: - - includes - scanFiles: - - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php - treatPhpDocTypesAsCertain: false + level: max + paths: + - includes + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php + treatPhpDocTypesAsCertain: false +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon diff --git a/plugin.php b/plugin.php index c2cf866..be37c5b 100644 --- a/plugin.php +++ b/plugin.php @@ -24,6 +24,10 @@ add_action( 'init', static function () { + // Wire up the WordPress HTTP client with the PHP AI Client SDK. + WordPress\AI_Client\HTTP\WP_AI_Client_Discovery_Strategy::init(); + + // Initialize the API credentials manager and settings screen. $api_credentials_manager = new WordPress\AI_Client\API_Credentials\API_Credentials_Manager(); $api_credentials_manager->initialize(); }