diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 9fe51be96aa8e..fbf35ad73e21d 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 * } * } */ @@ -109,8 +110,12 @@ 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 Optional. 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. + * Defaults to `__return_true`. * } * } * @return array|null The registered connector data on success, null on failure. @@ -243,8 +248,30 @@ 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'] ); + $connector['plugin'] = array(); + + if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) { + if ( ! empty( $args['plugin']['file'] ) ) { + $connector['plugin']['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']; + } + } + + if ( ! isset( $connector['plugin']['is_active'] ) ) { + $connector['plugin']['is_active'] = '__return_true'; } $this->registered_connectors[ $id ] = $connector; diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 63e018074fd58..9e7db2cef8f68 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -367,6 +367,17 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re } } } + + if ( ! isset( $args['plugin']['is_active'] ) ) { + $args['plugin']['is_active'] = static function () use ( $ai_registry, $id ): bool { + try { + return $ai_registry->hasProvider( $id ); + } catch ( Exception $e ) { + return false; + } + }; + } + $registry->register( $id, $args ); } } @@ -638,10 +649,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,8 +681,8 @@ 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_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); + $is_installed = $is_activated || file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) ); $connector_out['plugin'] = array( 'file' => $file, diff --git a/tests/phpunit/tests/connectors/wpConnectorRegistry.php b/tests/phpunit/tests/connectors/wpConnectorRegistry.php index d1a46dc0981fe..47e12eb7fd6fd 100644 --- a/tests/phpunit/tests/connectors/wpConnectorRegistry.php +++ b/tests/phpunit/tests/connectors/wpConnectorRegistry.php @@ -299,16 +299,66 @@ 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'] ); } /** * @ticket 64791 */ - public function test_register_omits_plugin_when_not_provided() { + public function test_register_defaults_plugin_when_not_provided() { $result = $this->registry->register( 'no-plugin', self::$default_args ); - $this->assertArrayNotHasKey( 'plugin', $result ); + $this->assertArrayHasKey( 'plugin', $result ); + $this->assertArrayNotHasKey( 'file', $result['plugin'] ); + $this->assertSame( '__return_true', $result['plugin']['is_active'] ); } /**