From 7e9634dac78aa6401d6ffbc88e093dfe9da7a7ea Mon Sep 17 00:00:00 2001 From: Todd Wright Date: Wed, 25 Feb 2026 21:32:46 +1100 Subject: [PATCH 1/6] Image Studio: Gate activation behind AI feature availability --- .../plugins/image-studio/image-studio.php | 29 +++- .../image-studio/Image_Studio_Test.php | 132 +++++++++++++++++- .../image-studio/mocks/class-big-sky.php | 18 +++ 3 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php diff --git a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php index 8430c2985d8..6911a56eacd 100644 --- a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php +++ b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php @@ -25,16 +25,31 @@ /** * Check if Image Studio is enabled. * - * Returns true if either the unified chat experience or the - * jetpack_image_studio_enabled filter is active. + * Requires AI features (Big Sky or AI Assistant) plus at least one of: + * - The unified chat experience (agents_manager_use_unified_experience). + * - The jetpack_image_studio_enabled filter. * * @return bool */ function is_image_studio_enabled() { + if ( ! has_ai_features() ) { + return false; + } + return apply_filters( 'agents_manager_use_unified_experience', false ) || apply_filters( 'jetpack_image_studio_enabled', false ); } +/** + * Check whether AI features are available (Big Sky or AI Assistant). + * + * @return bool + */ +function has_ai_features() { + return ( class_exists( 'Big_Sky' ) && \Big_Sky::$enabled ) + || \Jetpack_Gutenberg::is_available( 'ai-assistant-plugin' ); +} + /** * Check if the current screen is a block editor (Post Editor or Site Editor). * @@ -91,7 +106,9 @@ function register_plugin() { \Jetpack_Gutenberg::set_extension_available( FEATURE_NAME ); } -add_action( 'jetpack_register_gutenberg_extensions', __NAMESPACE__ . '\register_plugin' ); +// Priority 11 ensures this runs after the AI Assistant plugin registers at priority 10, +// so that is_available( 'ai-assistant-plugin' ) returns the correct value. +add_action( 'jetpack_register_gutenberg_extensions', __NAMESPACE__ . '\register_plugin', 11 ); /** * Fetch and cache the remote asset manifest. @@ -332,7 +349,7 @@ function disable_jetpack_ai_image_extensions() { /** * Enable the agents manager unified experience on self-hosted sites - * when jetpack_image_studio_enabled is true. + * when Image Studio is enabled with AI capabilities. * * This ensures the agents manager loads and can host the headless agent * even when the unified chat experience is not otherwise enabled. @@ -341,6 +358,10 @@ function disable_jetpack_ai_image_extensions() { * @return bool */ function enable_agents_manager_for_image_studio( $use_unified_experience ) { + if ( ! has_ai_features() ) { + return false; + } + if ( $use_unified_experience ) { return true; } diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php index e144f848b85..b5f5e473292 100644 --- a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php +++ b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php @@ -7,8 +7,11 @@ use Automattic\Jetpack\Extensions\ImageStudio; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/image-studio/image-studio.php'; +require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/ai-assistant-plugin/ai-assistant-plugin.php'; /** * Image Studio extension tests. @@ -92,9 +95,20 @@ private function reset_availability() { /** * Enable Image Studio via jetpack_image_studio_enabled filter. + * + * Also enables the AI assistant extension, since is_image_studio_enabled() + * now requires AI features (Big Sky or AI Assistant) to return true. */ private function enable_image_studio() { add_filter( 'jetpack_image_studio_enabled', '__return_true' ); + $this->enable_jp_ai_assistant(); + } + + /** + * Enable the AI assistant plugin extension. + */ + private function enable_jp_ai_assistant() { + \Jetpack_Gutenberg::set_extension_available( 'ai-assistant-plugin' ); } /** @@ -228,7 +242,7 @@ function () use ( $status_code, $body, $content_type ) { // ------------------------------------------------------------------------- /** - * Test is_image_studio_enabled returns true when jetpack_image_studio_enabled is true. + * Enabled when jetpack_image_studio_enabled filter is true and AI features exist. */ public function test_is_enabled_via_jetpack_filter() { $this->enable_image_studio(); @@ -236,22 +250,23 @@ public function test_is_enabled_via_jetpack_filter() { } /** - * Test is_image_studio_enabled returns true when unified experience is true. + * Enabled when unified experience is true and AI features exist. */ public function test_is_enabled_via_unified_experience() { $this->enable_unified_experience(); + $this->enable_jp_ai_assistant(); $this->assertTrue( ImageStudio\is_image_studio_enabled() ); } /** - * Test is_image_studio_enabled returns false when both filters are false. + * Not enabled when neither filter is set. */ public function test_is_not_enabled_when_both_filters_false() { $this->assertFalse( ImageStudio\is_image_studio_enabled() ); } /** - * Test is_image_studio_enabled returns true when both filters are true. + * Enabled when both filters are true. */ public function test_is_enabled_when_both_filters_true() { $this->enable_image_studio(); @@ -259,6 +274,56 @@ public function test_is_enabled_when_both_filters_true() { $this->assertTrue( ImageStudio\is_image_studio_enabled() ); } + /** + * AI features alone aren't enough; a filter must also be set. + */ + public function test_is_not_enabled_via_ai_assistant_without_filter() { + $this->enable_jp_ai_assistant(); + $this->assertFalse( ImageStudio\is_image_studio_enabled() ); + } + + /** + * Enabled via filter when AI Assistant is available. + */ + public function test_is_enabled_via_filter_with_ai_assistant() { + add_filter( 'jetpack_image_studio_enabled', '__return_true' ); + $this->enable_jp_ai_assistant(); + $this->assertTrue( ImageStudio\is_image_studio_enabled() ); + } + + /** + * Unified experience alone isn't enough; AI features must also exist. + */ + public function test_is_not_enabled_via_unified_experience_without_ai_features() { + $this->reset_availability(); + $this->enable_unified_experience(); + $this->assertFalse( ImageStudio\is_image_studio_enabled() ); + } + + /** + * Enabled via filter when Big Sky is active. + * + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + #[PreserveGlobalState( false )] + #[RunInSeparateProcess] + public function test_is_enabled_via_filter_with_big_sky() { + require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/image-studio/image-studio.php'; + require_once __DIR__ . '/mocks/class-big-sky.php'; + + add_filter( 'jetpack_image_studio_enabled', '__return_true' ); + $this->assertTrue( ImageStudio\is_image_studio_enabled() ); + } + + /** + * Filter alone isn't enough; AI features must also exist. + */ + public function test_is_not_enabled_via_filter_without_ai_features() { + add_filter( 'jetpack_image_studio_enabled', '__return_true' ); + $this->assertFalse( ImageStudio\is_image_studio_enabled() ); + } + // ------------------------------------------------------------------------- // is_block_editor() tests // ------------------------------------------------------------------------- @@ -385,6 +450,7 @@ public function test_register_plugin_sets_available_when_enabled() { */ public function test_register_plugin_sets_available_when_unified_experience() { $this->enable_unified_experience(); + $this->enable_jp_ai_assistant(); ImageStudio\register_plugin(); $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); } @@ -412,6 +478,7 @@ public function test_register_plugin_available_regardless_of_screen() { $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); $this->reset_availability(); + $this->enable_jp_ai_assistant(); // Media Library - still registers. $this->set_media_library_screen(); @@ -419,6 +486,7 @@ public function test_register_plugin_available_regardless_of_screen() { $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); $this->reset_availability(); + $this->enable_jp_ai_assistant(); // Dashboard - still registers. set_current_screen( 'dashboard' ); @@ -874,6 +942,7 @@ public function test_ai_extensions_disabled_when_enabled() { */ public function test_ai_extensions_disabled_when_unified_experience() { $this->enable_unified_experience(); + $this->enable_jp_ai_assistant(); $this->make_ai_extensions_available(); $this->set_block_editor_screen(); @@ -1281,6 +1350,8 @@ public function test_enable_agents_manager_returns_false_when_image_studio_disab * when agents_manager_use_unified_experience is already true. */ public function test_enable_agents_manager_preserves_existing_true() { + $this->enable_jp_ai_assistant(); + // Even without image studio enabled, if already true, stay true. $result = ImageStudio\enable_agents_manager_for_image_studio( true ); @@ -1460,4 +1531,57 @@ public function test_js_url_has_min_css_urls_do_not() { public function test_asset_transient_constant() { $this->assertEquals( 'jetpack_image_studio_asset', ImageStudio\ASSET_TRANSIENT ); } + + // ------------------------------------------------------------------------- + // Hook priority tests + // ------------------------------------------------------------------------- + // These test priorities directly rather than behaviour because the + // ordering is important but can't be verified end-to-end in these tests + // ------------------------------------------------------------------------- + + /** + * Test that register_plugin runs after the AI Assistant plugin's register_plugin. + * + * Image Studio checks is_available( 'ai-assistant-plugin' ), so it must + * register after the AI assistant has set itself available. + */ + public function test_register_plugin_priority_after_ai_assistant() { + $hook = 'jetpack_register_gutenberg_extensions'; + + $jp_ai_priority = has_action( + $hook, + 'Automattic\Jetpack\Extensions\AiAssistantPlugin\register_plugin' + ); + $image_studio_priority = has_action( + $hook, + 'Automattic\Jetpack\Extensions\ImageStudio\register_plugin' + ); + + $this->assertNotFalse( $jp_ai_priority, 'AI Assistant register_plugin should be hooked.' ); + $this->assertNotFalse( $image_studio_priority, 'Image Studio register_plugin should be hooked.' ); + $this->assertGreaterThan( $jp_ai_priority, $image_studio_priority ); + } + + /** + * Test that disable_jetpack_ai_image_extensions runs after the AI Assistant + * plugin's register_plugin. + * + * AI extensions must be registered before they can be disabled. + */ + public function test_disable_ai_extensions_priority_after_ai_assistant() { + $hook = 'jetpack_register_gutenberg_extensions'; + + $jp_ai_priority = has_action( + $hook, + 'Automattic\Jetpack\Extensions\AiAssistantPlugin\register_plugin' + ); + $disable_priority = has_action( + $hook, + 'Automattic\Jetpack\Extensions\ImageStudio\disable_jetpack_ai_image_extensions' + ); + + $this->assertNotFalse( $jp_ai_priority, 'AI Assistant register_plugin should be hooked.' ); + $this->assertNotFalse( $disable_priority, 'disable_jetpack_ai_image_extensions should be hooked.' ); + $this->assertGreaterThan( $jp_ai_priority, $disable_priority ); + } } diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php new file mode 100644 index 00000000000..c68ca7cd872 --- /dev/null +++ b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php @@ -0,0 +1,18 @@ + Date: Wed, 25 Feb 2026 21:39:38 +1100 Subject: [PATCH 2/6] Add changelog entries. --- .../forno-217-update-image-studio-activation-conditions | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/forno-217-update-image-studio-activation-conditions diff --git a/projects/plugins/jetpack/changelog/forno-217-update-image-studio-activation-conditions b/projects/plugins/jetpack/changelog/forno-217-update-image-studio-activation-conditions new file mode 100644 index 00000000000..4eab38422ec --- /dev/null +++ b/projects/plugins/jetpack/changelog/forno-217-update-image-studio-activation-conditions @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Image Studio: Only enable when AI features are available From 9942d573f4019dc256c779d5087d4a0c25cf8149 Mon Sep 17 00:00:00 2001 From: Todd Wright Date: Thu, 26 Feb 2026 17:16:06 +1100 Subject: [PATCH 3/6] Image Studio: Use wpcom_site_has_feature for Big Sky detection --- .../agents-manager/class-agents-manager.php | 7 ++-- .../plugins/image-studio/image-studio.php | 12 ++++-- .../image-studio/Image_Studio_Test.php | 37 ++++++++++--------- .../image-studio/mocks/class-big-sky.php | 18 --------- 4 files changed, 32 insertions(+), 42 deletions(-) delete mode 100644 projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php diff --git a/projects/packages/jetpack-mu-wpcom/src/features/agents-manager/class-agents-manager.php b/projects/packages/jetpack-mu-wpcom/src/features/agents-manager/class-agents-manager.php index da079b10a22..9fe4f107b34 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/agents-manager/class-agents-manager.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/agents-manager/class-agents-manager.php @@ -566,9 +566,10 @@ public function register_rest_api() { /** * Determine if user should see unified experience. * + * @param bool $use_unified_experience Whether to use unified experience. * @return bool */ - public function should_use_unified_experience() { + public function should_use_unified_experience( $use_unified_experience = false ) { // Early return for non-proxied/dev mode requests. // This feature is currently only available to Automattic employees testing via proxy. if ( ! self::is_dev_mode() ) { @@ -598,9 +599,9 @@ public function should_use_unified_experience() { return true; } - // False, for now. + // Default to false, for now. // In the future: users with a big sky site (similar to https://github.a8c.com/Automattic/wpcom/pull/196449/files), a big-sky free trial or a paid plan. - return false; + return $use_unified_experience; } /** diff --git a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php index 6911a56eacd..cb39e41aa04 100644 --- a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php +++ b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php @@ -41,13 +41,19 @@ function is_image_studio_enabled() { } /** - * Check whether AI features are available (Big Sky or AI Assistant). + * Check whether AI features are available. + * + * On wpcom / atomic, checks the Big Sky feature flag. On self-hosted, checks + * whether the AI Assistant extension is available. * * @return bool */ function has_ai_features() { - return ( class_exists( 'Big_Sky' ) && \Big_Sky::$enabled ) - || \Jetpack_Gutenberg::is_available( 'ai-assistant-plugin' ); + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) { + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'big-sky' ); + } else { + return \Jetpack_Gutenberg::is_available( 'ai-assistant-plugin' ); + } } /** diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php index b5f5e473292..6749544927b 100644 --- a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php +++ b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php @@ -7,8 +7,6 @@ use Automattic\Jetpack\Extensions\ImageStudio; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\PreserveGlobalState; -use PHPUnit\Framework\Attributes\RunInSeparateProcess; require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/image-studio/image-studio.php'; require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/ai-assistant-plugin/ai-assistant-plugin.php'; @@ -237,6 +235,25 @@ function () use ( $status_code, $body, $content_type ) { ); } + // ------------------------------------------------------------------------- + // has_ai_features() tests + // ------------------------------------------------------------------------- + + /** + * No AI features when AI Assistant isn't available. + */ + public function test_has_ai_features_false_without_ai_assistant() { + $this->assertFalse( ImageStudio\has_ai_features() ); + } + + /** + * AI features available when AI Assistant is registered. + */ + public function test_has_ai_features_true_with_ai_assistant() { + $this->enable_jp_ai_assistant(); + $this->assertTrue( ImageStudio\has_ai_features() ); + } + // ------------------------------------------------------------------------- // is_image_studio_enabled() tests // ------------------------------------------------------------------------- @@ -300,22 +317,6 @@ public function test_is_not_enabled_via_unified_experience_without_ai_features() $this->assertFalse( ImageStudio\is_image_studio_enabled() ); } - /** - * Enabled via filter when Big Sky is active. - * - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - #[PreserveGlobalState( false )] - #[RunInSeparateProcess] - public function test_is_enabled_via_filter_with_big_sky() { - require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/image-studio/image-studio.php'; - require_once __DIR__ . '/mocks/class-big-sky.php'; - - add_filter( 'jetpack_image_studio_enabled', '__return_true' ); - $this->assertTrue( ImageStudio\is_image_studio_enabled() ); - } - /** * Filter alone isn't enough; AI features must also exist. */ diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php deleted file mode 100644 index c68ca7cd872..00000000000 --- a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/mocks/class-big-sky.php +++ /dev/null @@ -1,18 +0,0 @@ - Date: Thu, 26 Feb 2026 20:33:21 +1100 Subject: [PATCH 4/6] Refactor Image Studio enablement conditions --- .../plugins/image-studio/image-studio.php | 27 ++++-- .../image-studio/Image_Studio_Test.php | 89 +++++++------------ 2 files changed, 50 insertions(+), 66 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php index cb39e41aa04..530405cfe02 100644 --- a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php +++ b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php @@ -43,17 +43,28 @@ function is_image_studio_enabled() { /** * Check whether AI features are available. * - * On wpcom / atomic, checks the Big Sky feature flag. On self-hosted, checks - * whether the AI Assistant extension is available. + * - wpcom simple: always available. + * - Atomic: requires Big Sky or AI Assistant feature flags. + * - Self-hosted: requires a connected owner with AI not disabled + * (same conditions the AI Assistant plugin uses to register). * * @return bool */ function has_ai_features() { - if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) { - return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'big-sky' ); - } else { - return \Jetpack_Gutenberg::is_available( 'ai-assistant-plugin' ); + $host = new \Automattic\Jetpack\Status\Host(); + + if ( $host->is_wpcom_simple() ) { + return true; } + + if ( $host->is_woa_site() ) { + return function_exists( 'wpcom_site_has_feature' ) + && ( wpcom_site_has_feature( 'big-sky' ) || wpcom_site_has_feature( 'ai-assistant' ) ); + } + + return ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->has_connected_owner() + && ! ( new \Automattic\Jetpack\Status() )->is_offline_mode() + && apply_filters( 'jetpack_ai_enabled', true ); } /** @@ -112,9 +123,7 @@ function register_plugin() { \Jetpack_Gutenberg::set_extension_available( FEATURE_NAME ); } -// Priority 11 ensures this runs after the AI Assistant plugin registers at priority 10, -// so that is_available( 'ai-assistant-plugin' ) returns the correct value. -add_action( 'jetpack_register_gutenberg_extensions', __NAMESPACE__ . '\register_plugin', 11 ); +add_action( 'jetpack_register_gutenberg_extensions', __NAMESPACE__ . '\register_plugin' ); /** * Fetch and cache the remote asset manifest. diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php index 6749544927b..3b72b1d574b 100644 --- a/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php +++ b/projects/plugins/jetpack/tests/php/extensions/plugins/image-studio/Image_Studio_Test.php @@ -60,6 +60,7 @@ public function set_up() { $GLOBALS['wp_scripts'] = new WP_Scripts(); $GLOBALS['wp_styles'] = new WP_Styles(); $this->reset_availability(); + $this->simulate_connected_owner(); unset( $_GET['enable_image_studio'] ); $this->saved_screen = $GLOBALS['current_screen'] ?? null; } @@ -74,6 +75,8 @@ public function tear_down() { remove_all_filters( 'agents_manager_agent_providers' ); remove_all_filters( 'pre_http_request' ); remove_all_filters( 'locale' ); + remove_all_filters( 'jetpack_ai_enabled' ); + ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->reset_connection_status(); unset( $_GET['enable_image_studio'] ); $GLOBALS['current_screen'] = $this->saved_screen; $GLOBALS['wp_scripts'] = $this->saved_wp_scripts; @@ -92,21 +95,30 @@ private function reset_availability() { } /** - * Enable Image Studio via jetpack_image_studio_enabled filter. + * Simulate a connected Jetpack owner so has_ai_features() returns true. * - * Also enables the AI assistant extension, since is_image_studio_enabled() - * now requires AI features (Big Sky or AI Assistant) to return true. + * Called in set_up() so every test starts with AI features available. + * Tests that need AI features off should use disable_ai_features() instead. + */ + private function simulate_connected_owner() { + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + \Jetpack_Options::update_option( 'master_user', $user_id ); + \Jetpack_Options::update_option( 'user_tokens', array( $user_id => 'token.secret.' . $user_id ) ); + ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->reset_connection_status(); + } + + /** + * Enable Image Studio via jetpack_image_studio_enabled filter. */ private function enable_image_studio() { add_filter( 'jetpack_image_studio_enabled', '__return_true' ); - $this->enable_jp_ai_assistant(); } /** - * Enable the AI assistant plugin extension. + * Disable AI features via the jetpack_ai_enabled kill switch. */ - private function enable_jp_ai_assistant() { - \Jetpack_Gutenberg::set_extension_available( 'ai-assistant-plugin' ); + private function disable_ai_features() { + add_filter( 'jetpack_ai_enabled', '__return_false' ); } /** @@ -240,18 +252,18 @@ function () use ( $status_code, $body, $content_type ) { // ------------------------------------------------------------------------- /** - * No AI features when AI Assistant isn't available. + * AI features available by default in the test environment. */ - public function test_has_ai_features_false_without_ai_assistant() { - $this->assertFalse( ImageStudio\has_ai_features() ); + public function test_has_ai_features_true_by_default() { + $this->assertTrue( ImageStudio\has_ai_features() ); } /** - * AI features available when AI Assistant is registered. + * AI features disabled via jetpack_ai_enabled kill switch. */ - public function test_has_ai_features_true_with_ai_assistant() { - $this->enable_jp_ai_assistant(); - $this->assertTrue( ImageStudio\has_ai_features() ); + public function test_has_ai_features_false_when_ai_disabled() { + $this->disable_ai_features(); + $this->assertFalse( ImageStudio\has_ai_features() ); } // ------------------------------------------------------------------------- @@ -271,7 +283,6 @@ public function test_is_enabled_via_jetpack_filter() { */ public function test_is_enabled_via_unified_experience() { $this->enable_unified_experience(); - $this->enable_jp_ai_assistant(); $this->assertTrue( ImageStudio\is_image_studio_enabled() ); } @@ -294,17 +305,15 @@ public function test_is_enabled_when_both_filters_true() { /** * AI features alone aren't enough; a filter must also be set. */ - public function test_is_not_enabled_via_ai_assistant_without_filter() { - $this->enable_jp_ai_assistant(); + public function test_is_not_enabled_with_ai_features_but_no_filter() { $this->assertFalse( ImageStudio\is_image_studio_enabled() ); } /** - * Enabled via filter when AI Assistant is available. + * Enabled via filter with AI features available. */ - public function test_is_enabled_via_filter_with_ai_assistant() { + public function test_is_enabled_via_filter_with_ai_features() { add_filter( 'jetpack_image_studio_enabled', '__return_true' ); - $this->enable_jp_ai_assistant(); $this->assertTrue( ImageStudio\is_image_studio_enabled() ); } @@ -312,8 +321,8 @@ public function test_is_enabled_via_filter_with_ai_assistant() { * Unified experience alone isn't enough; AI features must also exist. */ public function test_is_not_enabled_via_unified_experience_without_ai_features() { - $this->reset_availability(); $this->enable_unified_experience(); + $this->disable_ai_features(); $this->assertFalse( ImageStudio\is_image_studio_enabled() ); } @@ -322,6 +331,7 @@ public function test_is_not_enabled_via_unified_experience_without_ai_features() */ public function test_is_not_enabled_via_filter_without_ai_features() { add_filter( 'jetpack_image_studio_enabled', '__return_true' ); + $this->disable_ai_features(); $this->assertFalse( ImageStudio\is_image_studio_enabled() ); } @@ -451,7 +461,6 @@ public function test_register_plugin_sets_available_when_enabled() { */ public function test_register_plugin_sets_available_when_unified_experience() { $this->enable_unified_experience(); - $this->enable_jp_ai_assistant(); ImageStudio\register_plugin(); $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); } @@ -479,7 +488,6 @@ public function test_register_plugin_available_regardless_of_screen() { $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); $this->reset_availability(); - $this->enable_jp_ai_assistant(); // Media Library - still registers. $this->set_media_library_screen(); @@ -487,7 +495,6 @@ public function test_register_plugin_available_regardless_of_screen() { $this->assertTrue( \Jetpack_Gutenberg::is_available( ImageStudio\FEATURE_NAME ) ); $this->reset_availability(); - $this->enable_jp_ai_assistant(); // Dashboard - still registers. set_current_screen( 'dashboard' ); @@ -943,7 +950,6 @@ public function test_ai_extensions_disabled_when_enabled() { */ public function test_ai_extensions_disabled_when_unified_experience() { $this->enable_unified_experience(); - $this->enable_jp_ai_assistant(); $this->make_ai_extensions_available(); $this->set_block_editor_screen(); @@ -1351,8 +1357,6 @@ public function test_enable_agents_manager_returns_false_when_image_studio_disab * when agents_manager_use_unified_experience is already true. */ public function test_enable_agents_manager_preserves_existing_true() { - $this->enable_jp_ai_assistant(); - // Even without image studio enabled, if already true, stay true. $result = ImageStudio\enable_agents_manager_for_image_studio( true ); @@ -1536,38 +1540,9 @@ public function test_asset_transient_constant() { // ------------------------------------------------------------------------- // Hook priority tests // ------------------------------------------------------------------------- - // These test priorities directly rather than behaviour because the - // ordering is important but can't be verified end-to-end in these tests - // ------------------------------------------------------------------------- - - /** - * Test that register_plugin runs after the AI Assistant plugin's register_plugin. - * - * Image Studio checks is_available( 'ai-assistant-plugin' ), so it must - * register after the AI assistant has set itself available. - */ - public function test_register_plugin_priority_after_ai_assistant() { - $hook = 'jetpack_register_gutenberg_extensions'; - - $jp_ai_priority = has_action( - $hook, - 'Automattic\Jetpack\Extensions\AiAssistantPlugin\register_plugin' - ); - $image_studio_priority = has_action( - $hook, - 'Automattic\Jetpack\Extensions\ImageStudio\register_plugin' - ); - - $this->assertNotFalse( $jp_ai_priority, 'AI Assistant register_plugin should be hooked.' ); - $this->assertNotFalse( $image_studio_priority, 'Image Studio register_plugin should be hooked.' ); - $this->assertGreaterThan( $jp_ai_priority, $image_studio_priority ); - } /** - * Test that disable_jetpack_ai_image_extensions runs after the AI Assistant - * plugin's register_plugin. - * - * AI extensions must be registered before they can be disabled. + * Disable_jetpack_ai_image_extensions must run after AI extensions register. */ public function test_disable_ai_extensions_priority_after_ai_assistant() { $hook = 'jetpack_register_gutenberg_extensions'; From 9b474ce2094d1cf4712f8d84e55dc55994265d7f Mon Sep 17 00:00:00 2001 From: Todd Wright Date: Thu, 26 Feb 2026 20:40:20 +1100 Subject: [PATCH 5/6] Add jetpack-mu-wpcom changelog --- .../forno-217-update-image-studio-activation-conditions | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/forno-217-update-image-studio-activation-conditions diff --git a/projects/packages/jetpack-mu-wpcom/changelog/forno-217-update-image-studio-activation-conditions b/projects/packages/jetpack-mu-wpcom/changelog/forno-217-update-image-studio-activation-conditions new file mode 100644 index 00000000000..76bc6f5b11a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/forno-217-update-image-studio-activation-conditions @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Agents Manager: Allow plugins to enable the agents manager unified experience via filter. From eace4596c83d365bf3dbfd04592f2fcccb9d3cf0 Mon Sep 17 00:00:00 2001 From: Todd Wright Date: Thu, 26 Feb 2026 22:06:29 +1100 Subject: [PATCH 6/6] Fix woa but and filter override bug --- .../plugins/image-studio/image-studio.php | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php index 530405cfe02..03c269bad03 100644 --- a/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php +++ b/projects/plugins/jetpack/extensions/plugins/image-studio/image-studio.php @@ -7,6 +7,10 @@ namespace Automattic\Jetpack\Extensions\ImageStudio; +use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Status; +use Automattic\Jetpack\Status\Host; + if ( ! defined( 'ABSPATH' ) ) { exit( 0 ); } @@ -51,19 +55,14 @@ function is_image_studio_enabled() { * @return bool */ function has_ai_features() { - $host = new \Automattic\Jetpack\Status\Host(); + $host = new Host(); if ( $host->is_wpcom_simple() ) { return true; } - if ( $host->is_woa_site() ) { - return function_exists( 'wpcom_site_has_feature' ) - && ( wpcom_site_has_feature( 'big-sky' ) || wpcom_site_has_feature( 'ai-assistant' ) ); - } - - return ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->has_connected_owner() - && ! ( new \Automattic\Jetpack\Status() )->is_offline_mode() + return ( new Connection_Manager( 'jetpack' ) )->has_connected_owner() + && ! ( new Status() )->is_offline_mode() && apply_filters( 'jetpack_ai_enabled', true ); } @@ -373,15 +372,11 @@ function disable_jetpack_ai_image_extensions() { * @return bool */ function enable_agents_manager_for_image_studio( $use_unified_experience ) { - if ( ! has_ai_features() ) { - return false; - } - if ( $use_unified_experience ) { return true; } - return (bool) apply_filters( 'jetpack_image_studio_enabled', false ); + return has_ai_features() && (bool) apply_filters( 'jetpack_image_studio_enabled', false ); } add_filter( 'agents_manager_use_unified_experience', __NAMESPACE__ . '\enable_agents_manager_for_image_studio' );