From f36ba9ee0a264f322ba4cb094f5b83123b7131f6 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 11:12:42 +0900 Subject: [PATCH 01/14] Connectors: Add is_active callback support to plugin registration Backports the WP Core changes from https://github.com/WordPress/wordpress-develop/pull/11565 so that AI provider connectors (and Akismet) report the correct plugin status. Without an explicit `is_active` callback the registry's new `__return_true` default made every pre-registered connector appear active even when the underlying plugin was not installed, so the Connectors screen showed a "Set up" button instead of "Install" whenever Gutenberg was active. - Mirror the registry's `__return_true` default and the simplified is_installed/is_activated derivation in the script module data. - Wire AI providers to `$ai_registry->hasProvider( $id )` so their status reflects whether the provider plugin is actually available. - Set Akismet's `is_active` to `is_plugin_active( 'akismet/akismet.php' )` since Gutenberg pre-registers it (Core does not), and the new default would otherwise mark it as always installed. --- .../class-wp-connector-registry.php | 18 ++++++-- .../connectors/default-connectors.php | 41 +++++++++---------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index e6fbd2d5842c5f..231368854a85ca 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -98,10 +98,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. 'akismet/akismet.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. @@ -234,8 +236,12 @@ 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'] ) ) { @@ -252,6 +258,10 @@ public function register( string $id, array $args ): ?array { } } + if ( ! isset( $connector['plugin']['is_active'] ) ) { + $connector['plugin']['is_active'] = '__return_true'; + } + $this->registered_connectors[ $id ] = $connector; return $connector; } diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php index 2d08f162738004..ad7c9187fcae6c 100644 --- a/lib/experimental/connectors/default-connectors.php +++ b/lib/experimental/connectors/default-connectors.php @@ -31,7 +31,13 @@ function _gutenberg_connectors_init(): void { 'description' => __( 'Protect your site from spam.', 'gutenberg' ), 'type' => 'spam_filtering', 'plugin' => array( - 'file' => 'akismet/akismet.php', + 'file' => 'akismet/akismet.php', + 'is_active' => static function (): bool { + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + return is_plugin_active( 'akismet/akismet.php' ); + }, ), 'authentication' => array( 'method' => 'api_key', @@ -201,6 +207,17 @@ function _gutenberg_register_default_ai_providers( WP_Connector_Registry $regist } } } + + 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 ); } } @@ -485,10 +502,6 @@ function _gutenberg_get_connector_script_module_data( array $data ): array { $registry = \WordPress\AiClient\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']; @@ -525,22 +538,8 @@ function _gutenberg_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 ); - } - - if ( $is_activated ) { - $is_installed = true; - } else { - $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $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, From 0b06523106c1591de85c9d81ab1bf766b41e36fe Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 11:30:37 +0900 Subject: [PATCH 02/14] Connectors: Show Akismet with Install button when not installed The previous client-side workaround hid Akismet entirely whenever the plugin was not installed, because the server could not report Akismet's install status reliably. With the new `is_active` callback wired up on the server side, `isInstalled` is now accurate, so Akismet can be rendered like any other default connector and offer an Install button when the plugin is missing. --- routes/connectors-home/default-connectors.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx index 1ab02370bd8a36..1181d52f66d021 100644 --- a/routes/connectors-home/default-connectors.tsx +++ b/routes/connectors-home/default-connectors.tsx @@ -225,12 +225,6 @@ export function registerDefaultConnectors() { const sanitize = ( s: string ) => s.replace( /[^a-z0-9-_]/gi, '-' ); for ( const [ connectorId, data ] of Object.entries( connectors ) ) { - // Special case: Hide Akismet unless it is already installed. - // See https://core.trac.wordpress.org/ticket/65012 - if ( connectorId === 'akismet' && ! data.plugin?.isInstalled ) { - continue; - } - const { authentication } = data; const connectorName = sanitize( connectorId ); From d261e8d44336ec3eea9f2768d9ed9f63c5e75a4e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 11:33:21 +0900 Subject: [PATCH 03/14] Connectors: Restore E2E coverage for Akismet Install button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-adds the test that was removed when Akismet was hidden client-side in #76962. Now that the server reports Akismet's install status correctly and the JS hide logic is gone, the screen should render the Akismet card with an Install button when the plugin is not installed — make sure that's covered. --- test/e2e/specs/admin/connectors.spec.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/e2e/specs/admin/connectors.spec.js b/test/e2e/specs/admin/connectors.spec.js index 404b6a387054ea..cea816c2ab8806 100644 --- a/test/e2e/specs/admin/connectors.spec.js +++ b/test/e2e/specs/admin/connectors.spec.js @@ -376,6 +376,28 @@ test.describe( 'Connectors', () => { } ); } ); + test( 'should display Akismet connector with install button', async ( { + page, + admin, + } ) => { + await admin.visitAdminPage( SETTINGS_PAGE_PATH, CONNECTORS_PAGE_QUERY ); + + const card = page.locator( '.connector-item--akismet' ); + await expect( card ).toBeVisible(); + + const heading = card.getByRole( 'heading', { + name: 'Akismet Anti-Spam', + level: 2, + } ); + await expect( heading ).toBeVisible(); + await expect( + card.getByText( 'Protect your site from spam.' ) + ).toBeVisible(); + + const button = card.getByRole( 'button', { name: 'Install' } ); + await expect( button ).toBeVisible(); + } ); + test( 'should display the AI plugin callout banner with install button', async ( { page, admin, From 50612eff2dee1e7b0b14dbd0c50386d6c41ff120 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 12:57:47 +0900 Subject: [PATCH 04/14] Connectors: Update E2E test to expect Install button label --- test/e2e/specs/admin/connectors.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/specs/admin/connectors.spec.js b/test/e2e/specs/admin/connectors.spec.js index cea816c2ab8806..cd45bda0277a76 100644 --- a/test/e2e/specs/admin/connectors.spec.js +++ b/test/e2e/specs/admin/connectors.spec.js @@ -53,7 +53,7 @@ test.describe( 'Connectors', () => { ); } ); - test( 'should display default providers with setup buttons', async ( { + test( 'should display default providers with install buttons', async ( { page, admin, } ) => { @@ -66,7 +66,7 @@ test.describe( 'Connectors', () => { } ); await expect( pageTitle ).toBeVisible(); - // Verify each connector card shows name as heading, description, and Set up button. + // Verify each connector card shows name as heading, description, and Install button. for ( const { slug, name, description } of CONNECTORS ) { const card = page.locator( `.connector-item--${ slug }` ); await expect( card ).toBeVisible(); @@ -86,9 +86,9 @@ test.describe( 'Connectors', () => { headingId ); - const button = card.getByRole( 'button', { name: 'Set up' } ); + const button = card.getByRole( 'button', { name: 'Install' } ); await expect( button ).toBeVisible(); - // Set up button should not have aria-expanded until expanded. + // Install button should not have aria-expanded until expanded. await expect( button ).not.toHaveAttribute( 'aria-expanded' ); } From 8f54b96ec4fa8120db441e2345e507fca92bd51a Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 13:27:15 +0900 Subject: [PATCH 05/14] Connectors: Drop default is_active fallback in registry All pre-registered connectors now supply their own `is_active` callback, so the `__return_true` default is no longer needed. Removing it aligns the registry with the upstream WP Core PR and avoids silently masking connectors that forget to declare an `is_active` callback. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/compat/wordpress-7.0/class-wp-connector-registry.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index 231368854a85ca..1c5fc69dd382bb 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -258,10 +258,6 @@ public function register( string $id, array $args ): ?array { } } - if ( ! isset( $connector['plugin']['is_active'] ) ) { - $connector['plugin']['is_active'] = '__return_true'; - } - $this->registered_connectors[ $id ] = $connector; return $connector; } From 44c682521ceb39421c25729177ef3162c2a9f3ce Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 13:30:49 +0900 Subject: [PATCH 06/14] Connectors: Guard against missing is_active in script module data Now that the registry no longer injects a default `__return_true` for `is_active`, a connector registered with a `plugin.file` but without an `is_active` callback would trigger an undefined-index warning. Treat that case as active on the assumption the plugin only registers the connector when it is loaded. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/experimental/connectors/default-connectors.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php index ad7c9187fcae6c..5feae8fcc54ecc 100644 --- a/lib/experimental/connectors/default-connectors.php +++ b/lib/experimental/connectors/default-connectors.php @@ -537,8 +537,13 @@ function _gutenberg_get_connector_script_module_data( array $data ): array { ); if ( ! empty( $connector_data['plugin']['file'] ) ) { - $file = $connector_data['plugin']['file']; - $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); + $file = $connector_data['plugin']['file']; + if ( ! isset( $connector_data['plugin']['is_active'] ) ) { + // Assume plugin has registered own connector and is therefore active. + $is_activated = true; + } else { + $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( From 3e64029c5a1e02f03d0ea373bb4831d94b0ce29c Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 13:34:15 +0900 Subject: [PATCH 07/14] Connectors: Detect Akismet via AKISMET_VERSION constant Switch the Akismet `is_active` callback from `is_plugin_active()` to a `defined( 'AKISMET_VERSION' )` check so it does not have to load `wp-admin/includes/plugin.php`. This keeps the callback safe to invoke in non-admin requests where pulling in the admin plugin helpers would be unnecessary overhead. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/experimental/connectors/default-connectors.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php index 5feae8fcc54ecc..7734d4b0508db1 100644 --- a/lib/experimental/connectors/default-connectors.php +++ b/lib/experimental/connectors/default-connectors.php @@ -33,10 +33,7 @@ function _gutenberg_connectors_init(): void { 'plugin' => array( 'file' => 'akismet/akismet.php', 'is_active' => static function (): bool { - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - return is_plugin_active( 'akismet/akismet.php' ); + return defined( 'AKISMET_VERSION' ); }, ), 'authentication' => array( From 858868396c8e928dbcb65126c3ad5c3b530448a0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 15:23:17 +0900 Subject: [PATCH 08/14] ADd backport changelog --- backport-changelog/7.0/11701.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/7.0/11701.md diff --git a/backport-changelog/7.0/11701.md b/backport-changelog/7.0/11701.md new file mode 100644 index 00000000000000..48049f2db1eb61 --- /dev/null +++ b/backport-changelog/7.0/11701.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/11701 + +* https://github.com/WordPress/gutenberg/pull/77897 From 3d990add84a1a3d369f77ebb60e3239b3bea8490 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 4 May 2026 16:07:31 +0900 Subject: [PATCH 09/14] Connectors: Remove outdated is_active default from docblock The default `__return_true` fallback was dropped from the registry, but the docblock still advertised it. Remove the stale line so callers do not assume a default exists. --- lib/compat/wordpress-7.0/class-wp-connector-registry.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index 1c5fc69dd382bb..d63276c8370d21 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -103,7 +103,6 @@ final class WP_Connector_Registry { * '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. From e62f20226cba8fd1709895fcd5ebe1a9feedc3d7 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Tue, 5 May 2026 18:32:44 +0900 Subject: [PATCH 10/14] Connectors: Restore Akismet hide-when-not-installed special case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring back the JS-side guard that hides Akismet when the plugin is not installed. Akismet is bundled with WordPress and the product decision is to keep it out of the connectors list when missing — other connectors remain visible because plugins like Woo or SEO suites benefit from the auto-install/activate flow for their own third-party integrations. Also drop the E2E test that asserted the inverse (Install button visible for Akismet), since it conflicts with the restored behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- routes/connectors-home/default-connectors.tsx | 6 +++++ test/e2e/specs/admin/connectors.spec.js | 22 ------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx index 1181d52f66d021..1ab02370bd8a36 100644 --- a/routes/connectors-home/default-connectors.tsx +++ b/routes/connectors-home/default-connectors.tsx @@ -225,6 +225,12 @@ export function registerDefaultConnectors() { const sanitize = ( s: string ) => s.replace( /[^a-z0-9-_]/gi, '-' ); for ( const [ connectorId, data ] of Object.entries( connectors ) ) { + // Special case: Hide Akismet unless it is already installed. + // See https://core.trac.wordpress.org/ticket/65012 + if ( connectorId === 'akismet' && ! data.plugin?.isInstalled ) { + continue; + } + const { authentication } = data; const connectorName = sanitize( connectorId ); diff --git a/test/e2e/specs/admin/connectors.spec.js b/test/e2e/specs/admin/connectors.spec.js index cd45bda0277a76..0e891d67a10cbe 100644 --- a/test/e2e/specs/admin/connectors.spec.js +++ b/test/e2e/specs/admin/connectors.spec.js @@ -376,28 +376,6 @@ test.describe( 'Connectors', () => { } ); } ); - test( 'should display Akismet connector with install button', async ( { - page, - admin, - } ) => { - await admin.visitAdminPage( SETTINGS_PAGE_PATH, CONNECTORS_PAGE_QUERY ); - - const card = page.locator( '.connector-item--akismet' ); - await expect( card ).toBeVisible(); - - const heading = card.getByRole( 'heading', { - name: 'Akismet Anti-Spam', - level: 2, - } ); - await expect( heading ).toBeVisible(); - await expect( - card.getByText( 'Protect your site from spam.' ) - ).toBeVisible(); - - const button = card.getByRole( 'button', { name: 'Install' } ); - await expect( button ).toBeVisible(); - } ); - test( 'should display the AI plugin callout banner with install button', async ( { page, admin, From 8610c82a7f6b203dc2edf92b53b76ece29714510 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Tue, 5 May 2026 18:38:31 +0900 Subject: [PATCH 11/14] Connectors: Document default is_active behavior in docblock When the is_active callback is omitted and a file is provided, the connector is treated as active under the assumption that the plugin must be loaded in order to register itself. Spell that out so callers do not mistake the omission for a no-op. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/compat/wordpress-7.0/class-wp-connector-registry.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index d63276c8370d21..79a31f92df2335 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -103,6 +103,9 @@ final class WP_Connector_Registry { * 'hello.php'). * @type callable $is_active Optional callback to determine whether the plugin * is active. Receives no arguments and must return bool. + * When omitted and `file` is provided, the connector is + * treated as active on the assumption that the plugin + * must be loaded in order to register itself. * } * } * @return array|null The registered connector data on success, null on failure. From 1d66f6f120edc8bc2f776c5663aaf8c6b1bff4b6 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Tue, 5 May 2026 18:45:55 +0900 Subject: [PATCH 12/14] Connectors: Use validate_plugin() to detect installed plugin file Switch the install probe from a raw file_exists() to validate_plugin(), which adds path traversal protection and confirms the file is a recognised plugin (not just any file at that path). Restores the require_once for wp-admin/includes/plugin.php that this function needs. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/experimental/connectors/default-connectors.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php index 7734d4b0508db1..02c5e170073758 100644 --- a/lib/experimental/connectors/default-connectors.php +++ b/lib/experimental/connectors/default-connectors.php @@ -499,6 +499,10 @@ function _gutenberg_get_connector_script_module_data( array $data ): array { $registry = \WordPress\AiClient\AiClient::defaultRegistry(); + if ( ! function_exists( 'validate_plugin' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $connectors = array(); foreach ( wp_get_connectors() as $connector_id => $connector_data ) { $auth = $connector_data['authentication']; @@ -541,7 +545,7 @@ function _gutenberg_get_connector_script_module_data( array $data ): array { } else { $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); } - $is_installed = $is_activated || file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) ); + $is_installed = $is_activated || 0 === validate_plugin( $file ); $connector_out['plugin'] = array( 'file' => $file, From 9f687dc71fb2898403e193a7abba34b1b0f1e9d4 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 6 May 2026 10:55:56 +0900 Subject: [PATCH 13/14] Connectors: Default plugin is_active to __return_true Align with WordPress core, which sets is_active to '__return_true' when omitted at registration. This lets the rendering site call is_active unconditionally instead of branching on whether it was provided. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/compat/wordpress-7.0/class-wp-connector-registry.php | 8 +++++--- lib/experimental/connectors/default-connectors.php | 9 ++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index 79a31f92df2335..231368854a85ca 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -103,9 +103,7 @@ final class WP_Connector_Registry { * 'hello.php'). * @type callable $is_active Optional callback to determine whether the plugin * is active. Receives no arguments and must return bool. - * When omitted and `file` is provided, the connector is - * treated as active on the assumption that the plugin - * must be loaded in order to register itself. + * Defaults to `__return_true`. * } * } * @return array|null The registered connector data on success, null on failure. @@ -260,6 +258,10 @@ public function register( string $id, array $args ): ?array { } } + if ( ! isset( $connector['plugin']['is_active'] ) ) { + $connector['plugin']['is_active'] = '__return_true'; + } + $this->registered_connectors[ $id ] = $connector; return $connector; } diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php index 02c5e170073758..5a2b48a6f3d570 100644 --- a/lib/experimental/connectors/default-connectors.php +++ b/lib/experimental/connectors/default-connectors.php @@ -538,13 +538,8 @@ function _gutenberg_get_connector_script_module_data( array $data ): array { ); if ( ! empty( $connector_data['plugin']['file'] ) ) { - $file = $connector_data['plugin']['file']; - if ( ! isset( $connector_data['plugin']['is_active'] ) ) { - // Assume plugin has registered own connector and is therefore active. - $is_activated = true; - } else { - $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); - } + $file = $connector_data['plugin']['file']; + $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); $is_installed = $is_activated || 0 === validate_plugin( $file ); $connector_out['plugin'] = array( From 8414c35f3cd15a7d3201ccc84fb31d90a80ea0df Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 6 May 2026 11:19:22 +0900 Subject: [PATCH 14/14] Connectors: Align phpstan type with stored connector shape After the registry now always initializes `plugin` and defaults `is_active` to `__return_true`, mark `plugin` as required and `file` as optional in the @phpstan-type so static analysis matches what `register()` actually returns. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/compat/wordpress-7.0/class-wp-connector-registry.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-7.0/class-wp-connector-registry.php b/lib/compat/wordpress-7.0/class-wp-connector-registry.php index 231368854a85ca..bb4c0a8d619983 100644 --- a/lib/compat/wordpress-7.0/class-wp-connector-registry.php +++ b/lib/compat/wordpress-7.0/class-wp-connector-registry.php @@ -27,9 +27,9 @@ * constant_name?: non-empty-string, * env_var_name?: non-empty-string * }, - * plugin?: array{ - * file: non-empty-string, - * is_active?: callable(): bool + * plugin: array{ + * file?: non-empty-string, + * is_active: callable(): bool * } * } */