From 7ff688830456cc90e995c8f90bc429c8063495ef Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Wed, 4 Mar 2026 02:31:40 +0200 Subject: [PATCH 01/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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', ) From e8387df4a41622e4f16efa94080268409e826bc8 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Tue, 17 Mar 2026 00:36:58 +0200 Subject: [PATCH 17/23] chore: post merge cleanup --- src/wp-includes/connectors.php | 63 +++++++++------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index af0cb9708951b..985a7b0720977 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -341,24 +341,26 @@ function _wp_connectors_init(): void { /** * Fires when the connector registry is ready for plugins to register connectors. * - * Default connectors have already been registered at this point and cannot be - * unhooked. Use `$registry->register()` within this action to add new connectors. + * Built-in connectors and any AI providers auto-discovered from the WP AI Client + * registry have already been registered at this point and cannot be unhooked. + * + * AI provider plugins that register with the WP AI Client do not need to use + * this action — their connectors are created automatically. This action is + * primarily for registering non-AI-provider connectors or overriding metadata + * on existing connectors. + * + * Use `$registry->register()` within this action to add new connectors. + * To override an existing connector, unregister it first, then re-register + * with updated data. * - * Example usage: + * Example — overriding metadata on an auto-discovered connector: * * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) { - * $registry->register( - * 'my_custom_ai', - * array( - * 'name' => __( 'My Custom AI', 'my-plugin' ), - * 'description' => __( 'Custom AI provider integration.', 'my-plugin' ), - * 'type' => 'ai_provider', - * 'authentication' => array( - * 'method' => 'api_key', - * 'credentials_url' => 'https://example.com/api-keys', - * ), - * ) - * ); + * if ( $registry->is_registered( 'openai' ) ) { + * $connector = $registry->unregister( 'openai' ); + * $connector['description'] = __( 'Custom description for OpenAI.', 'my-plugin' ); + * $registry->register( 'openai', $connector ); + * } * } ); * * @since 7.0.0 @@ -475,37 +477,6 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re foreach ( $defaults as $id => $args ) { $registry->register( $id, $args ); } - - /** - * Fires when the connector registry is ready for plugins to register connectors. - * - * Built-in connectors and any AI providers auto-discovered from the WP AI Client - * registry have already been registered at this point and cannot be unhooked. - * - * AI provider plugins that register with the WP AI Client do not need to use - * this action — their connectors are created automatically. This action is - * primarily for registering non-AI-provider connectors or overriding metadata - * on existing connectors. - * - * Use `$registry->register()` within this action to add new connectors. - * To override an existing connector, unregister it first, then re-register - * with updated data. - * - * Example — overriding metadata on an auto-discovered connector: - * - * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) { - * if ( $registry->is_registered( 'openai' ) ) { - * $connector = $registry->unregister( 'openai' ); - * $connector['description'] = __( 'Custom description for OpenAI.', 'my-plugin' ); - * $registry->register( 'openai', $connector ); - * } - * } ); - * - * @since 7.0.0 - * - * @param WP_Connector_Registry $registry Connector registry instance. - */ - do_action( 'wp_connectors_init', $registry ); } /** From 97737057b7b3962bc6a4fcc14483e30fc7ecc33c Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Tue, 17 Mar 2026 01:36:10 +0200 Subject: [PATCH 18/23] tests: test `Registry->get_all_registered()` instead of downstream --- .../phpunit/tests/connectors/wpConnectorRegistry.php | 12 ++++++++++++ .../connectors/wpConnectorsGetConnectorSettings.php | 11 ----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/phpunit/tests/connectors/wpConnectorRegistry.php b/tests/phpunit/tests/connectors/wpConnectorRegistry.php index 161739b7a8ab8..c92e1c6833f86 100644 --- a/tests/phpunit/tests/connectors/wpConnectorRegistry.php +++ b/tests/phpunit/tests/connectors/wpConnectorRegistry.php @@ -381,4 +381,16 @@ public function test_get_instance_returns_same_instance() { $this->assertSame( $instance1, $instance2 ); } + + /** + * Test registration skips AI connectors when AI is not supported. + */ + public function test_register_skips_when_ai_not_supported() { + add_filter( 'wp_supports_ai', '__return_false' ); + + $this->registry->register( 'first', self::$default_args ); + + $all = $this->registry->get_all_registered(); + $this->assertCount( 0, $all ); + } } diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php index a752716a282c8..8cb7a5c5d2d90 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php @@ -64,17 +64,6 @@ public function test_each_connector_has_required_fields(): void { } } - /** - * 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. - add_filter( 'wp_supports_ai', '__return_false' ); - - $settings = wp_get_connectors(); - $this->assertSame( array(), $settings ); - } - /** * @ticket 64730 */ From 069e6d4b9ef7b0c169782e9c068f7aee8c48e067 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Tue, 17 Mar 2026 01:53:45 +0200 Subject: [PATCH 19/23] chore: revert `__call()` check in favor of constructor --- .../class-wp-ai-client-prompt-builder.php | 29 +++++++------------ .../ai-client/wpAiClientPromptBuilder.php | 14 ++++++++- 2 files changed, 23 insertions(+), 20 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 e74da9d6f5f21..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 @@ -297,20 +297,15 @@ 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 ) ) { - // 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 ); - } + /** + * 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. @@ -318,14 +313,10 @@ 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', - $error_message, + __( 'Prompt execution was prevented by a filter.' ), array( 'exception_class' => 'WP_AI_Client_Prompt_Prevented', ) diff --git a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php index ea4814212d335..b9b1e1a90f1d0 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php @@ -2395,6 +2395,19 @@ public function test_using_ability_method_chaining() { $this->assertEquals( 500, $config->getMaxTokens() ); } + /** + * Tests that is_supported returns false when prevent prompt filter returns true. + * + * @ticket 64591 + */ + public function test_is_supported_returns_false_when_ai_not_supported() { + add_filter( 'wp_supports_ai', '__return_false' ); + + $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(), 'Test prompt' ); + + $this->assertFalse( $builder->is_supported() ); + } + /** * Tests that is_supported returns false when prevent prompt filter returns true. * @@ -2407,7 +2420,6 @@ public function test_is_supported_returns_false_when_filter_prevents_prompt() { $this->assertFalse( $builder->is_supported() ); } - /** * Tests that generate_result returns WP_Error when prevent prompt filter returns true. * From 4f43aadc5d189a83866b25fbfce3497d7a112f92 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Thu, 19 Mar 2026 01:30:53 +0200 Subject: [PATCH 20/23] chore: move support check to __call() --- .../class-wp-ai-client-prompt-builder.php | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 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..d91bcef392197 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 @@ -178,11 +178,6 @@ 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 this environment.' ) ); - } - $this->builder = new PromptBuilder( $registry, $prompt ); } catch ( Exception $e ) { $this->builder = new PromptBuilder( $registry ); @@ -297,15 +292,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 +313,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', ) From 0d1ffb9a907ca920bf14cf15512280e80fecc748 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Thu, 19 Mar 2026 01:50:52 +0200 Subject: [PATCH 21/23] tests: remove unnecessary test --- .../tests/ai-client/wpAiClientPrompt.php | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php index af1ec8dd42883..287cd49116b71 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPrompt.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPrompt.php @@ -30,25 +30,4 @@ 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(): void { - // 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' ); - if ( PHP_VERSION_ID < 80100 ) { - $error_prop->setAccessible( true ); - } - $error = $error_prop->getValue( $builder ); - - $this->assertInstanceOf( WP_Error::class, $error ); - $this->assertSame( 'AI features are not supported in this environment.', $error->get_error_message() ); - } } From 9126d8b3bf9ba4cfc2f39333f3d08cb79d9b838d Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Thu, 19 Mar 2026 01:51:40 +0200 Subject: [PATCH 22/23] dev: allow filter to override constant --- src/wp-includes/ai-client.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index ad8bb52ff9c71..818e1dbaedcde 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -17,19 +17,20 @@ * @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. - if ( defined( 'WP_AI_SUPPORT' ) && ! WP_AI_SUPPORT ) { - return false; - } + $is_enabled = defined( 'WP_AI_SUPPORT' ) ? WP_AI_SUPPORT : true; /** * Filters whether the current request should use AI. * + * This allows plugins and 3rd-party code to disable AI features on a per-request basis, or to even override explicit + * preferences defined by the site owner. + * * @since 7.0.0 * - * @param bool $is_enabled Whether the current request should use AI. Default true. + * @param bool $is_enabled Whether the current request should use AI. Default to WP_AI_SUPPORT constant, or true if + * the constant is not defined. */ - return (bool) apply_filters( 'wp_supports_ai', true ); + return (bool) apply_filters( 'wp_supports_ai', $is_enabled ); } /** From f0c1632e19951a45073bc581294bcca0c2e569fc Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Thu, 19 Mar 2026 02:24:00 +0200 Subject: [PATCH 23/23] fix: prevent filter from overriding WP_AI_SUPPORT preference --- src/wp-includes/ai-client.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 818e1dbaedcde..5ca4801fb9f5d 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -17,20 +17,19 @@ * @return bool Whether AI features are supported. */ function wp_supports_ai(): bool { - $is_enabled = defined( 'WP_AI_SUPPORT' ) ? WP_AI_SUPPORT : true; + // Constant ensures 3rd-party code cannot override the explicit preferences defined by the environment. + if ( defined( 'WP_AI_SUPPORT' ) && ! WP_AI_SUPPORT ) { + return false; + } /** * Filters whether the current request should use AI. * - * This allows plugins and 3rd-party code to disable AI features on a per-request basis, or to even override explicit - * preferences defined by the site owner. - * * @since 7.0.0 * - * @param bool $is_enabled Whether the current request should use AI. Default to WP_AI_SUPPORT constant, or true if - * the constant is not defined. + * @param bool $is_enabled Whether the current request should use AI. Default true. */ - return (bool) apply_filters( 'wp_supports_ai', $is_enabled ); + return (bool) apply_filters( 'wp_supports_ai', true ); } /**