Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions lib/compat/wordpress-7.0/class-wp-connector-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
westonruter marked this conversation as resolved.
Resolved
* Defaults to `__return_true`.
Comment thread
t-hamano marked this conversation as resolved.
Resolved
* }
* }
* @return array|null The registered connector data on success, null on failure.
Expand Down Expand Up @@ -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'] ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible here that $args['plugin']['file'] is not a string. Not likely, but just wanted to point that this is not validated here. If that validation is added here, it should be added as well to the core PR.

$connector['plugin']['file'] = $args['plugin']['file'];
}

if ( isset( $args['plugin']['is_active'] ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we aligned with core and set $connector['plugin']['is_active'] == '__return_true', if a is_active was not passed?

@westonruter westonruter May 5, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that in the phpdoc for \WP_Connector_Registry::register() in core:

	 *         @type callable $is_active Optional callback to determine whether the plugin
	 *                                   is active. Receives no arguments and must return bool.
	 *                                   Defaults to `__return_true`.

and in the method:

		if ( ! isset( $connector['plugin']['is_active'] ) ) {
			$connector['plugin']['is_active'] = '__return_true';
		}

So it does seem to make sense to update this to match what is in core.

This aligns with what was done in WordPress/wordpress-develop@b3b40be.

This would undo some things in the companion core PR: WordPress/wordpress-develop#11701

if ( ! is_callable( $args['plugin']['is_active'] ) ) {
Expand All @@ -252,6 +258,10 @@ public function register( string $id, array $args ): ?array {
}
}

if ( ! isset( $connector['plugin']['is_active'] ) ) {
$connector['plugin']['is_active'] = '__return_true';
}

Comment thread
t-hamano marked this conversation as resolved.
Resolved
$this->registered_connectors[ $id ] = $connector;
return $connector;
}
Expand Down
41 changes: 20 additions & 21 deletions lib/experimental/connectors/default-connectors.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
},
Comment thread
t-hamano marked this conversation as resolved.
Resolved
),
'authentication' => array(
'method' => 'api_key',
Expand Down Expand Up @@ -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 );
}
}
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -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'] );
Comment thread
t-hamano marked this conversation as resolved.
Resolved
$is_installed = $is_activated || file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) );
Comment thread
westonruter marked this conversation as resolved.
Resolved

$connector_out['plugin'] = array(
'file' => $file,
Expand Down
6 changes: 0 additions & 6 deletions routes/connectors-home/default-connectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment thread
t-hamano marked this conversation as resolved.
Resolved

const { authentication } = data;

const connectorName = sanitize( connectorId );
Expand Down
30 changes: 26 additions & 4 deletions test/e2e/specs/admin/connectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
} ) => {
Expand All @@ -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();
Expand All @@ -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' );
}

Expand Down Expand Up @@ -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,
Expand Down
Loading