From 7ff688830456cc90e995c8f90bc429c8063495ef Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 4 Mar 2026 02:31:40 +0200 Subject: [PATCH 01/16] fix: add `wp_supports_ai()` and related constant + filter --- src/wp-includes/ai-client.php | 21 ++++++++++ .../class-wp-ai-client-prompt-builder.php | 5 +++ src/wp-includes/connectors.php | 6 ++- .../tests/ai-client/wpAiClientPrompt.php | 19 +++++++++ .../phpunit/tests/ai-client/wpSupportsAI.php | 40 +++++++++++++++++++ .../wpConnectorsGetProviderSettings.php | 12 ++++++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/tests/ai-client/wpSupportsAI.php diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 88a1fdf323f52..6951fcdbfeac6 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -9,6 +9,27 @@ use WordPress\AiClient\AiClient; +/** + * Returns whether AI features are supported in the current environment. + * + * @since 7.0.0 + */ +function wp_supports_ai(): bool { + // Constant check gives a hard short-circuit for environments that cannot be overridden with a filter, such as wp-config.php settings or hosting provider configurations. + if ( defined( 'WP_DISABLE_AI' ) && WP_DISABLE_AI ) { + return false; + } + + /** + * Filters whether the current request should use AI. + * + * @since 7.0.0 + * + * @param bool $is_enabled Whether the current request should use AI. Default true. + */ + return (bool) apply_filters( 'wp_supports_ai', true ); +} + /** * Creates a new AI prompt builder using the default provider registry. * diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index 3ed311915d158..47ef9bed367bb 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -166,6 +166,11 @@ class WP_AI_Client_Prompt_Builder { */ public function __construct( ProviderRegistry $registry, $prompt = null ) { try { + if ( ! wp_supports_ai() ) { + // The catch block will convert this to a WP_Error. + throw new \RuntimeException( __( 'AI features are not supported in the current environment.' ) ); + } + $this->builder = new PromptBuilder( $registry, $prompt ); } catch ( Exception $e ) { $this->builder = new PromptBuilder( $registry ); diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 85a92e31f98df..45427aa3483ca 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -17,7 +17,7 @@ * @access private */ function _wp_connectors_add_settings_menu_item(): void { - if ( ! class_exists( '\WordPress\AiClient\AiClient' ) || ! function_exists( 'wp_connectors_wp_admin_render_page' ) ) { + if ( ! wp_supports_ai() || ! class_exists( '\WordPress\AiClient\AiClient' ) || ! function_exists( 'wp_connectors_wp_admin_render_page' ) ) { return; } @@ -117,6 +117,10 @@ function _wp_connectors_get_real_api_key( string $option_name, callable $mask_ca * @return array Provider settings keyed by setting name. */ function _wp_connectors_get_provider_settings(): array { + if ( ! wp_supports_ai() ) { + return array(); + } + $providers = array( 'google' => array( 'name' => 'Google', diff --git a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php index 287cd49116b71..73a571d282a40 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php @@ -30,4 +30,23 @@ public function test_returns_independent_instances() { $this->assertNotSame( $builder1, $builder2 ); } + + /** + * Tests that returns a WP_AI_Client_Prompt_Builder instance even when AI is not supported, but that the builder contains an error. + */ + public function test_returns_error_builder_when_ai_not_supported() { + // Temporarily disable AI support for this test. + add_filter( 'wp_supports_ai', '__return_false' ); + $builder = wp_ai_client_prompt(); + $this->assertInstanceOf( WP_AI_Client_Prompt_Builder::class, $builder ); + + // Check the $error prop is a real WP_Error with the expected message. + $reflection = new ReflectionClass( $builder ); + $error_prop = $reflection->getProperty( 'error' ); + $error_prop->setAccessible( true ); + $error = $error_prop->getValue( $builder ); + + $this->assertInstanceOf( WP_Error::class, $error ); + $this->assertSame( 'AI features are not supported in the current environment.', $error->get_error_message() ); + } } diff --git a/tests/phpunit/tests/ai-client/wpSupportsAI.php b/tests/phpunit/tests/ai-client/wpSupportsAI.php new file mode 100644 index 0000000000000..503c06e9031a5 --- /dev/null +++ b/tests/phpunit/tests/ai-client/wpSupportsAI.php @@ -0,0 +1,40 @@ +assertTrue( wp_supports_ai() ); + } + + /** + * Tests that the wp_supports_ai filter can disable/enable AI features. + */ + public function test_filter_can_disable_ai_features() { + add_filter( 'wp_supports_ai', '__return_false' ); + $this->assertFalse( wp_supports_ai() ); + + // Try a later filter to re-enable AI and confirm that it works. + add_filter( 'wp_supports_ai', '__return_true' ); + $this->assertTrue( wp_supports_ai() ); + } +} diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetProviderSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetProviderSettings.php index 8d49391d34d38..9b252ac01baa5 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetProviderSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetProviderSettings.php @@ -43,4 +43,16 @@ public function test_provider_values_match_expected() { $this->assertSame( 'openai', $settings['connectors_ai_openai_api_key']['provider'] ); $this->assertSame( 'anthropic', $settings['connectors_ai_anthropic_api_key']['provider'] ); } + + /** + * Tests providers return an empty array when AI is not supported + */ + public function test_returns_empty_array_when_ai_not_supported() { + // Temporarily disable AI support for this test. + add_filter( 'wp_supports_ai', '__return_false' ); + + $settings = _wp_connectors_get_provider_settings(); + $this->assertIsArray( $settings ); + $this->assertEmpty( $settings ); + } } From 3aa0aae0b8d9758bc52f71980d8816875d175b30 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 4 Mar 2026 22:08:08 +0200 Subject: [PATCH 02/16] fix: resolve merge conflicts --- src/wp-includes/connectors.php | 16 ++++++++++++++-- .../wpConnectorsGetConnectorSettings.php | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 0546aee814977..3a2caca81e173 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -115,12 +115,24 @@ function _wp_connectors_get_real_api_key( string $option_name, callable $mask_ca * } * } */ -function _wp_connectors_get_provider_settings(): array { +function _wp_connectors_get_connector_settings(): array { if ( ! wp_supports_ai() ) { return array(); } - $providers = array( + $connectors = array( + 'anthropic' => array( + 'name' => 'Anthropic', + 'description' => __( 'Text generation with Claude.' ), + 'type' => 'ai_provider', + 'plugin' => array( + 'slug' => 'ai-provider-for-anthropic', + ), + 'authentication' => array( + 'method' => 'api_key', + 'credentials_url' => 'https://platform.claude.com/settings/keys', + ), + ), 'google' => array( 'name' => 'Google', 'description' => __( 'Text and image generation with Gemini and Imagen.' ), diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index 9f8da29895eff..0e7418680a5f8 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -119,13 +119,13 @@ public function test_includes_registered_provider_from_registry() { /** - * Tests providers return an empty array when AI is not supported + * Tests connectors return an empty array when AI is not supported */ public function test_returns_empty_array_when_ai_not_supported() { // Temporarily disable AI support for this test. add_filter( 'wp_supports_ai', '__return_false' ); - $settings = _wp_connectors_get_provider_settings(); + $settings = _wp_connectors_get_connector_settings(); $this->assertIsArray( $settings ); $this->assertEmpty( $settings ); } From 941cc8ba8c357a9868acb7751c76a464c29d022d Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 4 Mar 2026 22:08:28 +0200 Subject: [PATCH 03/16] dev: rename const to WP_AI_SUPPORT --- src/wp-includes/ai-client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 6951fcdbfeac6..620aa02f6eed8 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -16,7 +16,7 @@ */ function wp_supports_ai(): bool { // Constant check gives a hard short-circuit for environments that cannot be overridden with a filter, such as wp-config.php settings or hosting provider configurations. - if ( defined( 'WP_DISABLE_AI' ) && WP_DISABLE_AI ) { + if ( defined( 'WP_AI_SUPPORT' ) && ! WP_AI_SUPPORT ) { return false; } From 78b010bb7b37ab2681bdace1ed9e686a97bf4ff9 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 4 Mar 2026 22:56:13 +0200 Subject: [PATCH 04/16] tests: gate `ReflectionProperty::setAccessible()` --- tests/phpunit/tests/ai-client/wpAiClientPrompt.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php index 73a571d282a40..b73bcced29df8 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php @@ -43,7 +43,9 @@ public function test_returns_error_builder_when_ai_not_supported() { // Check the $error prop is a real WP_Error with the expected message. $reflection = new ReflectionClass( $builder ); $error_prop = $reflection->getProperty( 'error' ); - $error_prop->setAccessible( true ); + if ( PHP_VERSION_ID < 80100 ) { + $error_prop->setAccessible( true ); + } $error = $error_prop->getValue( $builder ); $this->assertInstanceOf( WP_Error::class, $error ); From a5cef8e955493ac375b717dc8166c5ee036b2dc0 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Sat, 7 Mar 2026 19:05:00 +0200 Subject: [PATCH 05/16] Apply suggestions from code review Co-authored-by: Weston Ruter --- src/wp-includes/ai-client.php | 2 ++ tests/phpunit/tests/ai-client/wpSupportsAI.php | 4 ++-- .../tests/connectors/wpConnectorsGetConnectorSettings.php | 3 --- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 620aa02f6eed8..4d9bc7c62892b 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -13,6 +13,8 @@ * Returns whether AI features are supported in the current environment. * * @since 7.0.0 + * + * @return bool Whether AI features are supported. */ function wp_supports_ai(): bool { // Constant check gives a hard short-circuit for environments that cannot be overridden with a filter, such as wp-config.php settings or hosting provider configurations. diff --git a/tests/phpunit/tests/ai-client/wpSupportsAI.php b/tests/phpunit/tests/ai-client/wpSupportsAI.php index 503c06e9031a5..598432b96369d 100644 --- a/tests/phpunit/tests/ai-client/wpSupportsAI.php +++ b/tests/phpunit/tests/ai-client/wpSupportsAI.php @@ -8,8 +8,8 @@ class Tests_WP_Supports_AI extends WP_UnitTestCase { /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public function tear_down() { // Remove the WP_DISABLE_AI constant if it was defined during tests. remove_all_filters( 'wp_supports_ai' ); diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index 0e7418680a5f8..6ca13d18c5d85 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -115,9 +115,6 @@ public function test_includes_registered_provider_from_registry() { $this->assertNull( $mock['authentication']['credentials_url'] ); $this->assertSame( 'connectors_ai_mock_connectors_test_api_key', $mock['authentication']['setting_name'] ); } - - - /** * Tests connectors return an empty array when AI is not supported */ From 98b881fffb644dd836078153fdc6a331521d7680 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Sat, 7 Mar 2026 19:56:59 +0200 Subject: [PATCH 06/16] chore: phpcbf --- src/wp-includes/ai-client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 4d9bc7c62892b..ad8bb52ff9c71 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -13,7 +13,7 @@ * Returns whether AI features are supported in the current environment. * * @since 7.0.0 - * + * * @return bool Whether AI features are supported. */ function wp_supports_ai(): bool { From 3ee6aaa54d18918a1e4f4b727cb33d0250eac27e Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 11 Mar 2026 21:12:55 +0200 Subject: [PATCH 07/16] chore: feedback --- tests/phpunit/tests/ai-client/wpSupportsAI.php | 10 ---------- .../connectors/wpConnectorsGetConnectorSettings.php | 1 + 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/phpunit/tests/ai-client/wpSupportsAI.php b/tests/phpunit/tests/ai-client/wpSupportsAI.php index 598432b96369d..d1a46a17109d6 100644 --- a/tests/phpunit/tests/ai-client/wpSupportsAI.php +++ b/tests/phpunit/tests/ai-client/wpSupportsAI.php @@ -7,16 +7,6 @@ */ class Tests_WP_Supports_AI extends WP_UnitTestCase { - /** - * {@inheritDoc} - */ - public function tear_down() { - // Remove the WP_DISABLE_AI constant if it was defined during tests. - remove_all_filters( 'wp_supports_ai' ); - - parent::tear_down(); - } - /** * Test that wp_supports_ai() defaults to true. * diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index 6ca13d18c5d85..f30ec29bdc140 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -115,6 +115,7 @@ public function test_includes_registered_provider_from_registry() { $this->assertNull( $mock['authentication']['credentials_url'] ); $this->assertSame( 'connectors_ai_mock_connectors_test_api_key', $mock['authentication']['setting_name'] ); } + /** * Tests connectors return an empty array when AI is not supported */ From a2d738f7dd637f7191ee354f94a8c249dfa059ac Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:10:08 -0700 Subject: [PATCH 08/16] Reuse Prompt type from PromptBuilder in WP_AI_Client_Prompt_Builder constructor --- .../class-wp-ai-client-prompt-builder.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index a06fffbab67cf..d2417c0fa4fe4 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -41,6 +41,8 @@ * * @since 7.0.0 * + * @phpstan-import-type Prompt from PromptBuilder + * * @method self with_text(string $text) Adds text to the current message. * @method self with_file($file, ?string $mimeType = null) Adds a file to the current message. * @method self with_function_response(FunctionResponse $functionResponse) Adds a function response to the current message. @@ -165,14 +167,14 @@ class WP_AI_Client_Prompt_Builder { * * @since 7.0.0 * - * @param ProviderRegistry $registry The provider registry for finding suitable models. - * @param string|MessagePart|Message|array|list|list|null $prompt Optional. Initial prompt content. - * A string for simple text prompts, - * a MessagePart or Message object for - * structured content, an array for a - * message array shape, or a list of - * parts or messages for multi-turn - * conversations. Default null. + * @param ProviderRegistry $registry The provider registry for finding suitable models. + * @param Prompt $prompt Optional. Initial prompt content. + * A string for simple text prompts, + * a MessagePart or Message object for + * structured content, an array for a + * message array shape, or a list of + * parts or messages for multi-turn + * conversations. Default null. */ public function __construct( ProviderRegistry $registry, $prompt = null ) { try { From 95a827a5541dd92d7a770c6b6416f6062c8b0c05 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:14:02 -0700 Subject: [PATCH 09/16] Fix PHPStan error about non-callable being returned > phpstan: Method WP_AI_Client_Prompt_Builder::get_builder_callable() should return callable(): mixed but returns array{WordPress\AiClient\Builders\PromptBuilder, string}. --- .../ai-client/class-wp-ai-client-prompt-builder.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index d2417c0fa4fe4..5f2531b341070 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -392,7 +392,8 @@ private static function is_generating_method( string $name ): bool { protected function get_builder_callable( string $name ): callable { $camel_case_name = $this->snake_to_camel_case( $name ); - if ( ! is_callable( array( $this->builder, $camel_case_name ) ) ) { + $method = array( $this->builder, $camel_case_name ); + if ( ! is_callable( $method ) ) { throw new BadMethodCallException( sprintf( /* translators: 1: Method name. 2: Class name. */ @@ -403,7 +404,7 @@ protected function get_builder_callable( string $name ): callable { ); } - return array( $this->builder, $camel_case_name ); + return $method; } /** From b3f04fdf0ba7af21a8b15c0ad74fbe25331e28eb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:20:05 -0700 Subject: [PATCH 10/16] Add type hint for _wp_connectors_register_default_ai_providers() --- src/wp-includes/connectors.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index b9eb3e302c2c7..d7848cb014a98 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -164,7 +164,7 @@ function _wp_connectors_init(): void { * * @param WP_Connector_Registry $registry The connector registry instance. */ -function _wp_connectors_register_default_ai_providers( $registry ): void { +function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $registry ): void { // Built-in connectors. $defaults = array( 'anthropic' => array( From f7f754d5504f67976288dea528b28d742bcf7be3 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:22:26 -0700 Subject: [PATCH 11/16] Remove blank line --- src/wp-includes/connectors.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index d7848cb014a98..d1d21b86b5563 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -265,7 +265,6 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re } } - /** * Masks an API key, showing only the last 4 characters. * From 5a06aea0cfb143c2e17de69faaa04e9f005ad784 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:23:12 -0700 Subject: [PATCH 12/16] Add void return types --- tests/phpunit/tests/ai-client/wpAiClientPrompt.php | 2 +- tests/phpunit/tests/ai-client/wpSupportsAI.php | 4 ++-- .../tests/connectors/wpConnectorsGetConnectorSettings.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php index b73bcced29df8..6fcfeca19de6b 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php @@ -34,7 +34,7 @@ public function test_returns_independent_instances() { /** * Tests that returns a WP_AI_Client_Prompt_Builder instance even when AI is not supported, but that the builder contains an error. */ - public function test_returns_error_builder_when_ai_not_supported() { + public function test_returns_error_builder_when_ai_not_supported(): void { // Temporarily disable AI support for this test. add_filter( 'wp_supports_ai', '__return_false' ); $builder = wp_ai_client_prompt(); diff --git a/tests/phpunit/tests/ai-client/wpSupportsAI.php b/tests/phpunit/tests/ai-client/wpSupportsAI.php index d1a46a17109d6..83346f1082d93 100644 --- a/tests/phpunit/tests/ai-client/wpSupportsAI.php +++ b/tests/phpunit/tests/ai-client/wpSupportsAI.php @@ -12,14 +12,14 @@ class Tests_WP_Supports_AI extends WP_UnitTestCase { * * @ticket 64591 */ - public function test_defaults_to_true() { + public function test_defaults_to_true(): void { $this->assertTrue( wp_supports_ai() ); } /** * Tests that the wp_supports_ai filter can disable/enable AI features. */ - public function test_filter_can_disable_ai_features() { + public function test_filter_can_disable_ai_features(): void { add_filter( 'wp_supports_ai', '__return_false' ); $this->assertFalse( wp_supports_ai() ); diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index c30979722a17e..cd9a36d21b34b 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -119,7 +119,7 @@ public function test_includes_registered_provider_from_registry() { /** * Tests connectors return an empty array when AI is not supported */ - public function test_returns_empty_array_when_ai_not_supported() { + public function test_returns_empty_array_when_ai_not_supported(): void { // Temporarily disable AI support for this test. add_filter( 'wp_supports_ai', '__return_false' ); From 2b05524bf9a913f03045938b988dec97d54f4697 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Mar 2026 13:23:26 -0700 Subject: [PATCH 13/16] Remove needless assertion since _wp_connectors_get_connector_settings() always returns array --- .../tests/connectors/wpConnectorsGetConnectorSettings.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index cd9a36d21b34b..4034acef41a6e 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -124,8 +124,7 @@ public function test_returns_empty_array_when_ai_not_supported(): void { add_filter( 'wp_supports_ai', '__return_false' ); $settings = _wp_connectors_get_connector_settings(); - $this->assertIsArray( $settings ); - $this->assertEmpty( $settings ); + $this->assertSame( array(), $settings ); } /** From d0b4a03d75d40dcddc4cf45e9e835f1fe1de07a0 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Thu, 12 Mar 2026 21:00:24 +0200 Subject: [PATCH 14/16] chore: lint after merging --- .../tests/connectors/wpConnectorsGetConnectorSettings.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index 0949404b4acfe..a752716a282c8 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -63,11 +63,9 @@ public function test_each_connector_has_required_fields(): void { $this->assertContains( $connector_data['authentication']['method'], array( 'api_key', 'none' ), "Connector '{$connector_id}' has unexpected authentication method." ); } } - - /** - * Tests connectors return an empty array when AI is not supported + * Tests connectors return an empty array when AI is not supported. */ public function test_returns_empty_array_when_ai_not_supported(): void { // Temporarily disable AI support for this test. From 007b5720aed08ec30bfe59f5d0992583776b4f03 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Fri, 13 Mar 2026 01:12:13 +0200 Subject: [PATCH 15/16] chore: fix test and cleanup --- src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php | 2 +- src/wp-includes/class-wp-connector-registry.php | 2 +- tests/phpunit/tests/ai-client/wpAiClientPrompt.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index 5f2531b341070..f3bfe87a00687 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -180,7 +180,7 @@ public function __construct( ProviderRegistry $registry, $prompt = null ) { try { if ( ! wp_supports_ai() ) { // The catch block will convert this to a WP_Error. - throw new \RuntimeException( __( 'AI features are not supported in the current environment.' ) ); + throw new \RuntimeException( __( 'AI features are not supported in this environment.' ) ); } $this->builder = new PromptBuilder( $registry, $prompt ); diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 27a6165e18920..6b0382a5c6680 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -144,7 +144,7 @@ public function register( string $id, array $args ): ?array { return null; } - if ( 'ai_provider' === $args['type'] & ! wp_supports_ai() ) { + if ( 'ai_provider' === $args['type'] && ! wp_supports_ai() ) { // No need for a `doing_it_wrong` as AI support is disabled intentionally. return null; } diff --git a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php index 6fcfeca19de6b..af1ec8dd42883 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php @@ -49,6 +49,6 @@ public function test_returns_error_builder_when_ai_not_supported(): void { $error = $error_prop->getValue( $builder ); $this->assertInstanceOf( WP_Error::class, $error ); - $this->assertSame( 'AI features are not supported in the current environment.', $error->get_error_message() ); + $this->assertSame( 'AI features are not supported in this environment.', $error->get_error_message() ); } } From eda33234845074cd55ba7a6c0d869ef6d247a570 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Fri, 13 Mar 2026 01:43:32 +0200 Subject: [PATCH 16/16] fix: check for support in __call() --- .../class-wp-ai-client-prompt-builder.php | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index f3bfe87a00687..e74da9d6f5f21 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -297,15 +297,20 @@ public function __call( string $name, array $arguments ) { // Check if the prompt should be prevented for is_supported* and generate_*/convert_text_to_speech* methods. if ( self::is_support_check_method( $name ) || self::is_generating_method( $name ) ) { - /** - * Filters whether to prevent the prompt from being executed. - * - * @since 7.0.0 - * - * @param bool $prevent Whether to prevent the prompt. Default false. - * @param WP_AI_Client_Prompt_Builder $builder A clone of the prompt builder instance (read-only). - */ - $prevent = (bool) apply_filters( 'wp_ai_client_prevent_prompt', false, clone $this ); + // If AI is not supported, then there's no need to apply the filter as the prompt will be prevented anyway. + $is_ai_disabled = ! wp_supports_ai(); + $prevent = $is_ai_disabled; + if ( ! $prevent ) { + /** + * Filters whether to prevent the prompt from being executed. + * + * @since 7.0.0 + * + * @param bool $prevent Whether to prevent the prompt. Default false. + * @param WP_AI_Client_Prompt_Builder $builder A clone of the prompt builder instance (read-only). + */ + $prevent = (bool) apply_filters( 'wp_ai_client_prevent_prompt', false, clone $this ); + } if ( $prevent ) { // For is_supported* methods, return false. @@ -313,10 +318,14 @@ public function __call( string $name, array $arguments ) { return false; } + $error_message = $is_ai_disabled + ? __( 'AI features are not supported in this environment.' ) + : __( 'Prompt execution was prevented by a filter.' ); + // For generate_* and convert_text_to_speech* methods, create a WP_Error. $this->error = new WP_Error( 'prompt_prevented', - __( 'Prompt execution was prevented by a filter.' ), + $error_message, array( 'exception_class' => 'WP_AI_Client_Prompt_Prevented', )