From eee44dc392dd0f69294a97f89121aa74d41dfacd Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Mon, 13 Apr 2026 23:12:54 +0530 Subject: [PATCH 1/4] Connectors: Add is_active callback support to plugin registration Backport of WordPress/gutenberg#76994 --- .../class-wp-connector-registry.php | 19 ++++++++++++++++++- src/wp-includes/connectors.php | 18 ++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 9fe51be96aa8e..62baac907b9fd 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -40,7 +40,8 @@ * env_var_name?: non-empty-string * }, * plugin?: array{ - * file: non-empty-string + * file: non-empty-string, + * is_active?: callable(): bool * } * } */ @@ -111,6 +112,8 @@ final class WP_Connector_Registry { * * @type string $file The plugin's main file path relative to the plugins * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). + * @type callable $is_active Optional callback to determine whether the plugin + * is active. Receives no arguments and must return bool. * } * } * @return array|null The registered connector data on success, null on failure. @@ -245,6 +248,20 @@ public function register( string $id, array $args ): ?array { if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) && ! empty( $args['plugin']['file'] ) ) { $connector['plugin'] = array( 'file' => $args['plugin']['file'] ); + + if ( isset( $args['plugin']['is_active'] ) ) { + if ( ! is_callable( $args['plugin']['is_active'] ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Connector ID. */ + sprintf( __( 'Connector "%s" plugin is_active must be callable.' ), esc_html( $id ) ), + '7.0.0' + ); + return null; + } + + $connector['plugin']['is_active'] = $args['plugin']['is_active']; + } } $this->registered_connectors[ $id ] = $connector; diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 63e018074fd58..6cfb8e615bc43 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -674,8 +674,22 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array { if ( ! empty( $connector_data['plugin']['file'] ) ) { $file = $connector_data['plugin']['file']; - $is_installed = file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) ); - $is_activated = $is_installed && is_plugin_active( $file ); + $is_installed = false; + $is_activated = false; + + if ( ! empty( $connector_data['plugin']['is_active'] ) && is_callable( $connector_data['plugin']['is_active'] ) ) { + $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); + } + + if ( ! $is_activated ) { + $is_activated = is_plugin_active( $file ); + } + + if ( $is_activated ) { + $is_installed = true; + } else { + $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $file ); + } $connector_out['plugin'] = array( 'file' => $file, From 7bec3b0c1aebed91cf4217548ac53b00d6e8d6c2 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Tue, 21 Apr 2026 15:24:58 +0530 Subject: [PATCH 2/4] refactor(connectors): simplify plugin status checks --- src/wp-includes/class-wp-connector-registry.php | 4 ++-- src/wp-includes/connectors.php | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 62baac907b9fd..8cb1ebb4fa6cb 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -110,8 +110,8 @@ final class WP_Connector_Registry { * @type array $plugin { * Optional. Plugin data for install/activate UI. * - * @type string $file The plugin's main file path relative to the plugins - * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). + * @type string $file The plugin's main file path relative to the plugins + * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). * @type callable $is_active Optional callback to determine whether the plugin * is active. Receives no arguments and must return bool. * } diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 6cfb8e615bc43..c5d46c7637faa 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -685,11 +685,7 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array { $is_activated = is_plugin_active( $file ); } - if ( $is_activated ) { - $is_installed = true; - } else { - $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $file ); - } + $is_installed = $is_activated || file_exists( WP_PLUGIN_DIR . '/' . $file ); $connector_out['plugin'] = array( 'file' => $file, From 9f5ba77c10abeb0f781a37421ee0980423fd91c7 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Tue, 21 Apr 2026 15:33:22 +0530 Subject: [PATCH 3/4] docs(connectors): Align the plugin docblock indentation --- src/wp-includes/class-wp-connector-registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 8cb1ebb4fa6cb..c35832ca54a48 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -110,7 +110,7 @@ final class WP_Connector_Registry { * @type array $plugin { * Optional. Plugin data for install/activate UI. * - * @type string $file The plugin's main file path relative to the plugins + * @type string $file The plugin's main file path relative to the plugins * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). * @type callable $is_active Optional callback to determine whether the plugin * is active. Receives no arguments and must return bool. From 598847db3f2bc9b9f1b0bb5606ab573e0c998926 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Mon, 27 Apr 2026 17:18:57 +0530 Subject: [PATCH 4/4] Connector Registry: Add default `is_active` callback and enhance tests for plugin registration --- .../class-wp-connector-registry.php | 3 ++ src/wp-includes/connectors.php | 16 +----- .../tests/connectors/wpConnectorRegistry.php | 50 ++++++++++++++++++- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index c35832ca54a48..5191f449942dd 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -114,6 +114,7 @@ final class WP_Connector_Registry { * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). * @type callable $is_active Optional callback to determine whether the plugin * is active. Receives no arguments and must return bool. + * Defaults to `__return_true`. * } * } * @return array|null The registered connector data on success, null on failure. @@ -261,6 +262,8 @@ public function register( string $id, array $args ): ?array { } $connector['plugin']['is_active'] = $args['plugin']['is_active']; + } else { + $connector['plugin']['is_active'] = '__return_true'; } } diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index c5d46c7637faa..00807caa68062 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -638,10 +638,6 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void { function _wp_connectors_get_connector_script_module_data( array $data ): array { $registry = AiClient::defaultRegistry(); - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - $connectors = array(); foreach ( wp_get_connectors() as $connector_id => $connector_data ) { $auth = $connector_data['authentication']; @@ -674,17 +670,7 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array { if ( ! empty( $connector_data['plugin']['file'] ) ) { $file = $connector_data['plugin']['file']; - $is_installed = false; - $is_activated = false; - - if ( ! empty( $connector_data['plugin']['is_active'] ) && is_callable( $connector_data['plugin']['is_active'] ) ) { - $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); - } - - if ( ! $is_activated ) { - $is_activated = is_plugin_active( $file ); - } - + $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); $is_installed = $is_activated || file_exists( WP_PLUGIN_DIR . '/' . $file ); $connector_out['plugin'] = array( diff --git a/tests/phpunit/tests/connectors/wpConnectorRegistry.php b/tests/phpunit/tests/connectors/wpConnectorRegistry.php index d1a46dc0981fe..0a2ce90d30712 100644 --- a/tests/phpunit/tests/connectors/wpConnectorRegistry.php +++ b/tests/phpunit/tests/connectors/wpConnectorRegistry.php @@ -299,7 +299,55 @@ public function test_register_includes_plugin_data() { $result = $this->registry->register( 'with-plugin', $args ); $this->assertArrayHasKey( 'plugin', $result ); - $this->assertSame( array( 'file' => 'my-plugin/my-plugin.php' ), $result['plugin'] ); + $this->assertSame( 'my-plugin/my-plugin.php', $result['plugin']['file'] ); + } + + /** + * @ticket 65020 + */ + public function test_register_stores_plugin_is_active_callback() { + $args = self::$default_args; + $args['plugin'] = array( + 'file' => 'my-plugin/my-plugin.php', + 'is_active' => '__return_true', + ); + + $result = $this->registry->register( 'with-callback', $args ); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'is_active', $result['plugin'] ); + $this->assertIsCallable( $result['plugin']['is_active'] ); + } + + /** + * @ticket 65020 + */ + public function test_register_rejects_non_callable_plugin_is_active() { + $this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' ); + + $args = self::$default_args; + $args['plugin'] = array( + 'file' => 'my-plugin/my-plugin.php', + 'is_active' => 'not_a_real_function_name', + ); + + $result = $this->registry->register( 'bad-callback', $args ); + + $this->assertNull( $result ); + } + + /** + * @ticket 65020 + */ + public function test_register_defaults_plugin_is_active_to_return_true() { + $args = self::$default_args; + $args['plugin'] = array( 'file' => 'my-plugin/my-plugin.php' ); + + $result = $this->registry->register( 'default-callback', $args ); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'is_active', $result['plugin'] ); + $this->assertSame( '__return_true', $result['plugin']['is_active'] ); } /**