From 3753ec96014f862dd5941e380891636b8fa1d353 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Tue, 21 Apr 2026 17:34:56 +0530
Subject: [PATCH 01/23] feat: add file mod status check and display notice for
disabled installations
---
.../connectors/default-connectors.php | 9 ++-
routes/connectors-home/default-connectors.tsx | 26 ++++++---
routes/connectors-home/stage.tsx | 56 ++++++++++++++++++-
routes/connectors-home/style.scss | 17 ++++++
4 files changed, 97 insertions(+), 11 deletions(-)
diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php
index 3f71f7b46eef06..4dc956bd9e4dc2 100644
--- a/lib/experimental/connectors/default-connectors.php
+++ b/lib/experimental/connectors/default-connectors.php
@@ -554,7 +554,14 @@ function _gutenberg_get_connector_script_module_data( array $data ): array {
$connectors[ $connector_id ] = $connector_out;
}
ksort( $connectors );
- $data['connectors'] = $connectors;
+
+ $is_file_mods_disabled = defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS;
+ if ( function_exists( 'wp_is_file_mod_allowed' ) ) {
+ $is_file_mods_disabled = ! wp_is_file_mod_allowed( 'install_plugins' );
+ }
+
+ $data['connectors'] = $connectors;
+ $data['isFileModsDisabled'] = $is_file_mods_disabled;
return $data;
}
remove_filter( 'script_module_data_options-connectors-wp-admin', '_wp_connectors_get_connector_script_module_data' );
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index 1ab02370bd8a36..688a05c13a5348 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -43,22 +43,34 @@ interface ConnectorData {
authentication: NonNullable< ConnectorConfig[ 'authentication' ] >;
}
-/**
- * Reads connector data passed from PHP via the script module data mechanism.
- */
-export function getConnectorData(): Record< string, ConnectorData > {
+interface ConnectorScriptModuleData {
+ connectors?: Record< string, ConnectorData >;
+ isFileModsDisabled?: boolean;
+}
+
+function getConnectorScriptModuleData(): ConnectorScriptModuleData {
try {
- const parsed = JSON.parse(
+ return JSON.parse(
document.getElementById(
'wp-script-module-data-options-connectors-wp-admin'
- )?.textContent ?? ''
+ )?.textContent ?? '{}'
);
- return parsed?.connectors ?? {};
} catch {
return {};
}
}
+/**
+ * Reads connector data passed from PHP via the script module data mechanism.
+ */
+export function getConnectorData(): Record< string, ConnectorData > {
+ return getConnectorScriptModuleData().connectors ?? {};
+}
+
+export function getIsFileModsDisabled(): boolean {
+ return !! getConnectorScriptModuleData().isFileModsDisabled;
+}
+
const CONNECTOR_LOGOS: Record< string, React.ComponentType > = {
google: GeminiLogo,
openai: OpenAILogo,
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index c15e3da3dee43c..43837f00f8bb05 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -4,6 +4,7 @@
import { Page } from '@wordpress/admin-ui';
import {
Button,
+ Notice,
__experimentalHeading as Heading,
__experimentalText as WCText,
__experimentalVStack as VStack,
@@ -14,7 +15,7 @@ import {
} from '@wordpress/connectors';
import { useSelect } from '@wordpress/data';
import { createInterpolateElement } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
/**
@@ -22,7 +23,10 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import './style.scss';
import { AiPluginCallout } from './ai-plugin-callout';
-import { registerDefaultConnectors } from './default-connectors';
+import {
+ getIsFileModsDisabled,
+ registerDefaultConnectors,
+} from './default-connectors';
import { unlock } from '../lock-unlock';
const { store } = unlock( connectorsPrivateApis );
@@ -31,6 +35,8 @@ const { store } = unlock( connectorsPrivateApis );
registerDefaultConnectors();
function ConnectorsPage() {
+ const isFileModsDisabled = getIsFileModsDisabled();
+
const { connectors, canInstallPlugins } = useSelect(
( select ) => ( {
connectors: unlock( select( store ) ).getConnectors(),
@@ -45,6 +51,21 @@ function ConnectorsPage() {
const renderableConnectors = connectors.filter(
( connector: ConnectorConfig ) => connector.render
);
+ const aiProviderPluginSlugs = Array.from(
+ new Set(
+ connectors
+ .filter(
+ ( connector: ConnectorConfig ) =>
+ connector.type === 'ai_provider'
+ )
+ .map(
+ ( connector: ConnectorConfig ) =>
+ connector.plugin?.file?.split( '/' )[ 0 ]
+ )
+ .filter( ( slug ): slug is string => !! slug )
+ )
+ ).sort();
+ const manualInstallPluginSlugs = [ 'ai', ...aiProviderPluginSlugs ];
const isEmpty = renderableConnectors.length === 0;
return (
@@ -59,6 +80,35 @@ function ConnectorsPage() {
isEmpty ? ' connectors-page--empty' : ''
}` }
>
+ { isFileModsDisabled && (
+
+
+ { __(
+ 'Plugin installation from wp-admin is disabled because DISALLOW_FILE_MODS is enabled. Install the AI plugin and any AI provider plugins manually using your normal deployment workflow.'
+ ) }
+
+ { __( 'WP-CLI examples:' ) }
+
+ { manualInstallPluginSlugs.map( ( slug ) => {
+ const command = `wp plugin install ${ slug } --activate`;
+ return (
+
+ { sprintf(
+ /* translators: %s: Plugin slug. */
+ __( '%s:' ),
+ slug
+ ) }{ ' ' }
+ { command }
+
+ );
+ } ) }
+
+
+ ) }
{ isEmpty ? (
) }
- { canInstallPlugins && (
+ { canInstallPlugins && ! isFileModsDisabled && (
{ createInterpolateElement(
__(
diff --git a/routes/connectors-home/style.scss b/routes/connectors-home/style.scss
index a0a6d87533c6ba..f3b5d9bc9f4b40 100644
--- a/routes/connectors-home/style.scss
+++ b/routes/connectors-home/style.scss
@@ -29,6 +29,23 @@ $sticky-header-clearance: 120px;
scroll-margin-top: $sticky-header-clearance;
}
+ &__file-mods-notice {
+ margin: 16px 0;
+
+ p {
+ margin: 0;
+ }
+
+ ul {
+ margin: 8px 0 0;
+ padding-inline-start: 20px;
+ }
+
+ code {
+ font-size: 12px;
+ }
+ }
+
&--empty {
flex-grow: 1;
display: flex;
From 659170990bd6b6e02e30170bfc3cb9e95b5713f8 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Wed, 22 Apr 2026 11:52:42 +0530
Subject: [PATCH 02/23] feat: enhance plugin callout button visibility based on
installation status
---
routes/connectors-home/ai-plugin-callout.tsx | 54 ++++++++++----------
1 file changed, 26 insertions(+), 28 deletions(-)
diff --git a/routes/connectors-home/ai-plugin-callout.tsx b/routes/connectors-home/ai-plugin-callout.tsx
index 2431dfeff2ae86..aa63a8e02e6a37 100644
--- a/routes/connectors-home/ai-plugin-callout.tsx
+++ b/routes/connectors-home/ai-plugin-callout.tsx
@@ -197,11 +197,6 @@ export function AiPluginCallout() {
return null;
}
- // Not installed and no permissions to install.
- if ( pluginStatus === 'not-installed' && canInstallPlugins === false ) {
- return null;
- }
-
// Installed but can't activate (no manage permissions).
if ( pluginStatus === 'inactive' && canManagePlugins === false ) {
return null;
@@ -215,6 +210,8 @@ export function AiPluginCallout() {
( ! initialHasConnectedProvider || justActivated );
const showInstallActivate =
pluginStatus === 'not-installed' || pluginStatus === 'inactive';
+ const hideButtons =
+ pluginStatus === 'not-installed' && canInstallPlugins === false;
const getMessage = () => {
if ( isJustConnected ) {
@@ -262,29 +259,30 @@ export function AiPluginCallout() {
a: ,
} ) }
- { showInstallActivate ? (
-
- { getPrimaryButtonProps().label }
-
- ) : (
-
- { __( 'Control features in the AI plugin' ) }
-
- ) }
+ { ! hideButtons &&
+ ( showInstallActivate ? (
+
+ { getPrimaryButtonProps().label }
+
+ ) : (
+
+ { __( 'Control features in the AI plugin' ) }
+
+ ) ) }
From e6ee93b458fef6f13e2db63bd140956bcb8773c9 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Thu, 23 Apr 2026 12:34:22 +0530
Subject: [PATCH 03/23] feat: update connector search link based on file mod
status and always show
---
routes/connectors-home/stage.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 43837f00f8bb05..8ae0465232f3ac 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -67,6 +67,10 @@ function ConnectorsPage() {
).sort();
const manualInstallPluginSlugs = [ 'ai', ...aiProviderPluginSlugs ];
const isEmpty = renderableConnectors.length === 0;
+ const searchUrl =
+ canInstallPlugins && ! isFileModsDisabled
+ ? 'plugin-install.php?s=connector&tab=search&type=tag'
+ : 'https://wordpress.org/plugins/search/ai-connectors/';
return (
) }
- { canInstallPlugins && ! isFileModsDisabled && (
{ createInterpolateElement(
__(
@@ -168,12 +171,11 @@ function ConnectorsPage() {
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
-
+
),
}
) }
- ) }
);
From 74813ab8386a017ae0c61387fe2be36f36fea271 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Thu, 23 Apr 2026 12:37:21 +0530
Subject: [PATCH 04/23] feat: update notice status to "notice" from "warning"
---
routes/connectors-home/stage.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 8ae0465232f3ac..ab16411bea3bcf 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -86,7 +86,7 @@ function ConnectorsPage() {
>
{ isFileModsDisabled && (
From 2dcd6a6792dd4175301f3f13b87497d53a04ef0b Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Fri, 24 Apr 2026 16:53:59 +0530
Subject: [PATCH 05/23] feat: update connector search link to use translation
function for better localization
---
routes/connectors-home/stage.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index ab16411bea3bcf..ef91c657f569cc 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -70,7 +70,7 @@ function ConnectorsPage() {
const searchUrl =
canInstallPlugins && ! isFileModsDisabled
? 'plugin-install.php?s=connector&tab=search&type=tag'
- : 'https://wordpress.org/plugins/search/ai-connectors/';
+ : __( 'https://wordpress.org/plugins/search/ai-connectors/' );
return (
Date: Fri, 24 Apr 2026 17:00:15 +0530
Subject: [PATCH 06/23] feat: simplify plugin installation notice for better
clarity
---
routes/connectors-home/stage.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index ef91c657f569cc..92732781a7a2ae 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -92,7 +92,7 @@ function ConnectorsPage() {
>
{ __(
- 'Plugin installation from wp-admin is disabled because DISALLOW_FILE_MODS is enabled. Install the AI plugin and any AI provider plugins manually using your normal deployment workflow.'
+ 'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
) }
{ __( 'WP-CLI examples:' ) }
From bd8a86743a1456a8f68398131d5824dac8b154b7 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Mon, 27 Apr 2026 14:35:04 +0530
Subject: [PATCH 07/23] feat: enhance connectors page to include AI plugin
activation status and filter manual install plugins accordingly
---
routes/connectors-home/stage.tsx | 38 +++++++++++++++++++++++++++-----
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 92732781a7a2ae..cf1396897485e3 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -37,14 +37,23 @@ registerDefaultConnectors();
function ConnectorsPage() {
const isFileModsDisabled = getIsFileModsDisabled();
- const { connectors, canInstallPlugins } = useSelect(
- ( select ) => ( {
+ const { connectors, canInstallPlugins, isAiPluginActive } = useSelect(
+ ( select ) => {
+ const coreSelect = select( coreStore );
+ const aiPlugin = coreSelect.getEntityRecord(
+ 'root',
+ 'plugin',
+ 'ai/ai'
+ ) as { status: string } | undefined;
+ return {
connectors: unlock( select( store ) ).getConnectors(),
- canInstallPlugins: select( coreStore ).canUser( 'create', {
+ canInstallPlugins: coreSelect.canUser( 'create', {
kind: 'root',
name: 'plugin',
} ),
- } ),
+ isAiPluginActive: aiPlugin?.status === 'active',
+ };
+ },
[]
);
@@ -65,7 +74,24 @@ function ConnectorsPage() {
.filter( ( slug ): slug is string => !! slug )
)
).sort();
- const manualInstallPluginSlugs = [ 'ai', ...aiProviderPluginSlugs ];
+ const activatedPluginSlugs = new Set(
+ connectors
+ .filter(
+ ( connector: ConnectorConfig ) => connector.plugin?.isActivated
+ )
+ .map(
+ ( connector: ConnectorConfig ) =>
+ connector.plugin?.file?.split( '/' )[ 0 ]
+ )
+ .filter( ( slug: string | undefined ): slug is string => !! slug )
+ );
+ // AI provider connectors are only registered when the 'ai' plugin is active.
+ if ( isAiPluginActive ) {
+ activatedPluginSlugs.add( 'ai' );
+ }
+ const manualInstallPluginSlugs = [ 'ai', ...aiProviderPluginSlugs ].filter(
+ ( slug ) => ! activatedPluginSlugs.has( slug )
+ );
const isEmpty = renderableConnectors.length === 0;
const searchUrl =
canInstallPlugins && ! isFileModsDisabled
@@ -84,7 +110,7 @@ function ConnectorsPage() {
isEmpty ? ' connectors-page--empty' : ''
}` }
>
- { isFileModsDisabled && (
+ { isFileModsDisabled && manualInstallPluginSlugs.length > 0 && (
Date: Tue, 28 Apr 2026 11:30:59 +0530
Subject: [PATCH 08/23] feat: enhance WP-CLI examples section with collapsible
details for improved user experience
---
routes/connectors-home/stage.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index cf1396897485e3..ce83f062933442 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -121,7 +121,8 @@ function ConnectorsPage() {
'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
) }
- { __( 'WP-CLI examples:' ) }
+
+ { __( 'WP-CLI examples' ) }
{ manualInstallPluginSlugs.map( ( slug ) => {
const command = `wp plugin install ${ slug } --activate`;
@@ -137,6 +138,7 @@ function ConnectorsPage() {
);
} ) }
+
) }
{ isEmpty ? (
From b3b05edb338fd19da83cd8df98b10db49eaf1017 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Tue, 28 Apr 2026 18:24:51 +0530
Subject: [PATCH 09/23] feat: enhance manual installation notice to include
permission check
---
routes/connectors-home/stage.tsx | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index ce83f062933442..7a0d636903bd53 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -110,21 +110,27 @@ function ConnectorsPage() {
isEmpty ? ' connectors-page--empty' : ''
}` }
>
- { isFileModsDisabled && manualInstallPluginSlugs.length > 0 && (
+ { manualInstallPluginSlugs.length > 0 &&
+ ( isFileModsDisabled || ! canInstallPlugins ) && (
+ { isFileModsDisabled ? (
+ <>
{ __(
'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
) }
- { __( 'WP-CLI examples' ) }
+
+ { __( 'WP-CLI examples' ) }
+
- { manualInstallPluginSlugs.map( ( slug ) => {
+ { manualInstallPluginSlugs.map(
+ ( slug ) => {
const command = `wp plugin install ${ slug } --activate`;
return (
@@ -133,12 +139,23 @@ function ConnectorsPage() {
__( '%s:' ),
slug
) }{ ' ' }
- { command }
+
+ { command }
+
);
- } ) }
+ }
+ ) }
+ >
+ ) : (
+
+ { __(
+ 'You do not have permission to install plugins. Please ask a site administrator to install them for you.'
+ ) }
+
+ ) }
) }
{ isEmpty ? (
From 3fbd434fa482f8526b264a71073829d4a4b4dcb1 Mon Sep 17 00:00:00 2001
From: Gautam Mehta
Date: Thu, 7 May 2026 18:52:50 +0530
Subject: [PATCH 10/23] feat: refactor notice component to use newer component
---
routes/connectors-home/stage.tsx | 126 ++++++++++++++++---------------
1 file changed, 64 insertions(+), 62 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 7a0d636903bd53..f69f6cea06ea16 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -4,7 +4,6 @@
import { Page } from '@wordpress/admin-ui';
import {
Button,
- Notice,
__experimentalHeading as Heading,
__experimentalText as WCText,
__experimentalVStack as VStack,
@@ -17,6 +16,8 @@ import { useSelect } from '@wordpress/data';
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
+// eslint-disable-next-line @wordpress/use-recommended-components
+import { Notice } from '@wordpress/ui';
/**
* Internal dependencies
@@ -46,11 +47,11 @@ function ConnectorsPage() {
'ai/ai'
) as { status: string } | undefined;
return {
- connectors: unlock( select( store ) ).getConnectors(),
+ connectors: unlock( select( store ) ).getConnectors(),
canInstallPlugins: coreSelect.canUser( 'create', {
- kind: 'root',
- name: 'plugin',
- } ),
+ kind: 'root',
+ name: 'plugin',
+ } ),
isAiPluginActive: aiPlugin?.status === 'active',
};
},
@@ -112,52 +113,53 @@ function ConnectorsPage() {
>
{ manualInstallPluginSlugs.length > 0 &&
( isFileModsDisabled || ! canInstallPlugins ) && (
-
- { isFileModsDisabled ? (
- <>
-
- { __(
- 'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
- ) }
-
-
-
- { __( 'WP-CLI examples' ) }
-
-
- { manualInstallPluginSlugs.map(
- ( slug ) => {
- const command = `wp plugin install ${ slug } --activate`;
- return (
-
- { sprintf(
- /* translators: %s: Plugin slug. */
- __( '%s:' ),
- slug
- ) }{ ' ' }
-
- { command }
-
-
- );
- }
+
+
+ { isFileModsDisabled ? (
+ <>
+
+ { __(
+ 'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
) }
-
-
- >
- ) : (
-
- { __(
- 'You do not have permission to install plugins. Please ask a site administrator to install them for you.'
- ) }
-
- ) }
-
- ) }
+
+
+
+ { __( 'WP-CLI examples' ) }
+
+
+ { manualInstallPluginSlugs.map(
+ ( slug ) => {
+ const command = `wp plugin install ${ slug } --activate`;
+ return (
+
+ { sprintf(
+ /* translators: %s: Plugin slug. */
+ __( '%s:' ),
+ slug
+ ) }{ ' ' }
+
+ { command }
+
+
+ );
+ }
+ ) }
+
+
+ >
+ ) : (
+
+ { __(
+ 'You do not have permission to install plugins. Please ask a site administrator to install them for you.'
+ ) }
+
+ ) }
+
+
+ ) }
{ isEmpty ? (
) }
-
- { createInterpolateElement(
- __(
- 'If the connector you need is not listed, search the plugin directory to see if a connector is available.'
- ),
- {
- a: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content
+
+ { createInterpolateElement(
+ __(
+ 'If the connector you need is not listed, search the plugin directory to see if a connector is available.'
+ ),
+ {
+ a: (
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
- ),
- }
- ) }
-
+ ),
+ }
+ ) }
+
);
From 3b85c02b27a0fb4017b2eaf7413ff7234c4122fa Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Fri, 8 May 2026 19:22:33 +0900
Subject: [PATCH 11/23] Avoid string concatenation
---
routes/connectors-home/stage.tsx | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index f69f6cea06ea16..62a0f67d02126b 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -135,14 +135,21 @@ function ConnectorsPage() {
const command = `wp plugin install ${ slug } --activate`;
return (
- { sprintf(
- /* translators: %s: Plugin slug. */
- __( '%s:' ),
- slug
- ) }{ ' ' }
-
- { command }
-
+ { createInterpolateElement(
+ sprintf(
+ /* translators: 1: Plugin slug. 2: WP-CLI command. */
+ __(
+ '%1$s: %2$s'
+ ),
+ slug,
+ command
+ ),
+ {
+ code: (
+
+ ),
+ }
+ ) }
);
}
From 9fe9e43ea40f0bf344d26b2e5ad5b02c26a1f945 Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Fri, 8 May 2026 19:51:31 +0900
Subject: [PATCH 12/23] Remove unnecessary top margin
---
routes/connectors-home/style.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/connectors-home/style.scss b/routes/connectors-home/style.scss
index f3b5d9bc9f4b40..71027148b1f52c 100644
--- a/routes/connectors-home/style.scss
+++ b/routes/connectors-home/style.scss
@@ -30,7 +30,7 @@ $sticky-header-clearance: 120px;
}
&__file-mods-notice {
- margin: 16px 0;
+ margin-bottom: 16px;
p {
margin: 0;
From b955d23ad533cf9195f33a31185cbdc5a829853c Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 15:17:14 +0100
Subject: [PATCH 13/23] Apply suggestions from code review
Co-authored-by: Weston Ruter
---
lib/experimental/connectors/default-connectors.php | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php
index 4dc956bd9e4dc2..6d939f93314f0e 100644
--- a/lib/experimental/connectors/default-connectors.php
+++ b/lib/experimental/connectors/default-connectors.php
@@ -554,14 +554,8 @@ function _gutenberg_get_connector_script_module_data( array $data ): array {
$connectors[ $connector_id ] = $connector_out;
}
ksort( $connectors );
-
- $is_file_mods_disabled = defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS;
- if ( function_exists( 'wp_is_file_mod_allowed' ) ) {
- $is_file_mods_disabled = ! wp_is_file_mod_allowed( 'install_plugins' );
- }
-
$data['connectors'] = $connectors;
- $data['isFileModsDisabled'] = $is_file_mods_disabled;
+ $data['isFileModsDisabled'] = ! wp_is_file_mod_allowed( 'install_plugins' );
return $data;
}
remove_filter( 'script_module_data_options-connectors-wp-admin', '_wp_connectors_get_connector_script_module_data' );
From 1f3091eff4b53a442b2f15fe03f66a9169c969d4 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 16:49:26 +0200
Subject: [PATCH 14/23] feat: drop WP-CLI examples and link to plugin directory
for unavailable plugins
---
routes/connectors-home/default-connectors.tsx | 15 +++++-
routes/connectors-home/stage.tsx | 53 +++----------------
routes/connectors-home/style.scss | 13 -----
3 files changed, 20 insertions(+), 61 deletions(-)
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index 688a05c13a5348..c632410280ef42 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -13,7 +13,7 @@ import {
} from '@wordpress/connectors';
import { select } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
-import { Badge } from '@wordpress/ui';
+import { Badge, Link } from '@wordpress/ui';
/**
* Internal dependencies
@@ -108,6 +108,12 @@ const ConnectedBadge = () => (
);
+const PluginDirectoryLink = ( { slug }: { slug: string } ) => (
+
+ { __( 'Learn more' ) }
+
+);
+
const UnavailableActionBadge = () => { __( 'Not available' ) } ;
function ApiKeyConnector( {
@@ -178,7 +184,12 @@ function ApiKeyConnector( {
actionArea={
{ isConnected && }
- { showUnavailableBadge && }
+ { showUnavailableBadge &&
+ ( pluginSlug ? (
+
+ ) : (
+
+ ) ) }
{ showActionButton && (
- { isFileModsDisabled ? (
- <>
-
- { __(
- 'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
- ) }
-
-
-
- { __( 'WP-CLI examples' ) }
-
-
- { manualInstallPluginSlugs.map(
- ( slug ) => {
- const command = `wp plugin install ${ slug } --activate`;
- return (
-
- { createInterpolateElement(
- sprintf(
- /* translators: 1: Plugin slug. 2: WP-CLI command. */
- __(
- '%1$s: %2$s'
- ),
- slug,
- command
- ),
- {
- code: (
-
- ),
- }
- ) }
-
- );
- }
- ) }
-
-
- >
- ) : (
-
- { __(
+ { isFileModsDisabled
+ ? __(
+ 'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
+ )
+ : __(
'You do not have permission to install plugins. Please ask a site administrator to install them for you.'
- ) }
-
- ) }
+ ) }
) }
diff --git a/routes/connectors-home/style.scss b/routes/connectors-home/style.scss
index 71027148b1f52c..3566ae7234b664 100644
--- a/routes/connectors-home/style.scss
+++ b/routes/connectors-home/style.scss
@@ -31,19 +31,6 @@ $sticky-header-clearance: 120px;
&__file-mods-notice {
margin-bottom: 16px;
-
- p {
- margin: 0;
- }
-
- ul {
- margin: 8px 0 0;
- padding-inline-start: 20px;
- }
-
- code {
- font-size: 12px;
- }
}
&--empty {
From 24580fa27275a3295f44dea3105e98f120837878 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 16:57:02 +0200
Subject: [PATCH 15/23] fix: notice should track plugin install status, not
activation
---
routes/connectors-home/stage.tsx | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index f230463b783f4d..24b71407018ec3 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -38,21 +38,21 @@ registerDefaultConnectors();
function ConnectorsPage() {
const isFileModsDisabled = getIsFileModsDisabled();
- const { connectors, canInstallPlugins, isAiPluginActive } = useSelect(
+ const { connectors, canInstallPlugins, isAiPluginInstalled } = useSelect(
( select ) => {
const coreSelect = select( coreStore );
const aiPlugin = coreSelect.getEntityRecord(
'root',
'plugin',
'ai/ai'
- ) as { status: string } | undefined;
+ );
return {
connectors: unlock( select( store ) ).getConnectors(),
canInstallPlugins: coreSelect.canUser( 'create', {
kind: 'root',
name: 'plugin',
} ),
- isAiPluginActive: aiPlugin?.status === 'active',
+ isAiPluginInstalled: !! aiPlugin,
};
},
[]
@@ -75,10 +75,10 @@ function ConnectorsPage() {
.filter( ( slug ): slug is string => !! slug )
)
).sort();
- const activatedPluginSlugs = new Set(
+ const installedPluginSlugs = new Set(
connectors
.filter(
- ( connector: ConnectorConfig ) => connector.plugin?.isActivated
+ ( connector: ConnectorConfig ) => connector.plugin?.isInstalled
)
.map(
( connector: ConnectorConfig ) =>
@@ -86,12 +86,11 @@ function ConnectorsPage() {
)
.filter( ( slug: string | undefined ): slug is string => !! slug )
);
- // AI provider connectors are only registered when the 'ai' plugin is active.
- if ( isAiPluginActive ) {
- activatedPluginSlugs.add( 'ai' );
+ if ( isAiPluginInstalled ) {
+ installedPluginSlugs.add( 'ai' );
}
const manualInstallPluginSlugs = [ 'ai', ...aiProviderPluginSlugs ].filter(
- ( slug ) => ! activatedPluginSlugs.has( slug )
+ ( slug ) => ! installedPluginSlugs.has( slug )
);
const isEmpty = renderableConnectors.length === 0;
const searchUrl =
From ae57aaf196db3fb100c6e98071319ebab944912a Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 16:06:01 +0100
Subject: [PATCH 16/23] Update routes/connectors-home/default-connectors.tsx
Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
---
routes/connectors-home/default-connectors.tsx | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index c632410280ef42..11a8886a483c48 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -109,7 +109,14 @@ const ConnectedBadge = () => (
);
const PluginDirectoryLink = ( { slug }: { slug: string } ) => (
-
+
{ __( 'Learn more' ) }
);
From 65b40120351ab6518bffd3364c1ae8d1e3086e18 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:15:32 +0200
Subject: [PATCH 17/23] refactor: use Stack from @wordpress/ui instead of
__experimentalHStack
---
routes/connectors-home/default-connectors.tsx | 10 +++++-----
tools/eslint/suppressions.json | 5 -----
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index 11a8886a483c48..a6a746c0bf5b63 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { __experimentalHStack as HStack, Button } from '@wordpress/components';
+import { Button } from '@wordpress/components';
import { useRef } from '@wordpress/element';
import {
__experimentalRegisterConnector as registerConnector,
@@ -12,8 +12,8 @@ import {
type ConnectorRenderProps,
} from '@wordpress/connectors';
import { select } from '@wordpress/data';
-import { __ } from '@wordpress/i18n';
-import { Badge, Link } from '@wordpress/ui';
+import { __, sprintf } from '@wordpress/i18n';
+import { Badge, Link, Stack } from '@wordpress/ui';
/**
* Internal dependencies
@@ -189,7 +189,7 @@ function ApiKeyConnector( {
name={ name }
description={ description }
actionArea={
-
+
{ isConnected && }
{ showUnavailableBadge &&
( pluginSlug ? (
@@ -214,7 +214,7 @@ function ApiKeyConnector( {
{ getButtonLabel() }
) }
-
+
}
>
{ isExpanded && pluginStatus === 'active' && (
diff --git a/tools/eslint/suppressions.json b/tools/eslint/suppressions.json
index bf65f223980395..c727b76a9f5447 100644
--- a/tools/eslint/suppressions.json
+++ b/tools/eslint/suppressions.json
@@ -1621,11 +1621,6 @@
"count": 1
}
},
- "routes/connectors-home/default-connectors.tsx": {
- "@wordpress/use-recommended-components": {
- "count": 1
- }
- },
"routes/connectors-home/stage.tsx": {
"@wordpress/no-non-module-stylesheet-imports": {
"count": 1
From 40e1a3e74ed146323108aaabb4de79e5be752a74 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:28:03 +0200
Subject: [PATCH 18/23] test: update connectors capability tests for Learn more
link
---
test/e2e/specs/admin/connectors.spec.js | 17 ++++++++++++-----
tools/eslint/suppressions.json | 5 +++++
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/test/e2e/specs/admin/connectors.spec.js b/test/e2e/specs/admin/connectors.spec.js
index 0e891d67a10cbe..b617237b6ab733 100644
--- a/test/e2e/specs/admin/connectors.spec.js
+++ b/test/e2e/specs/admin/connectors.spec.js
@@ -461,11 +461,13 @@ test.describe( 'Connectors', () => {
slug: 'gutenberg-test-connectors-never-installed',
name: 'Test Install Required Connector',
action: 'Install',
+ pluginSlug: 'gutenberg-test-connectors-never-installed',
};
const activateRequiredConnector = {
slug: 'hello',
name: 'Test Activate Required Connector',
action: 'Activate',
+ pluginSlug: 'hello',
};
const clearCapabilityRestriction = async ( requestUtils ) => {
await requestUtils.rest( {
@@ -519,7 +521,7 @@ test.describe( 'Connectors', () => {
CONNECTORS_PAGE_QUERY
);
- for ( const { slug, name, action } of [
+ for ( const { slug, name, action, pluginSlug } of [
installRequiredConnector,
activateRequiredConnector,
] ) {
@@ -528,9 +530,14 @@ test.describe( 'Connectors', () => {
await expect(
card.getByRole( 'heading', { name, level: 2 } )
).toBeVisible();
- await expect(
- card.getByText( 'Not available' )
- ).toBeVisible();
+ const learnMoreLink = card.getByRole( 'link', {
+ name: 'Learn more',
+ } );
+ await expect( learnMoreLink ).toBeVisible();
+ await expect( learnMoreLink ).toHaveAttribute(
+ 'href',
+ `https://wordpress.org/plugins/${ pluginSlug }/`
+ );
await expect(
card.getByRole( 'button', { name: action } )
).toBeHidden();
@@ -540,7 +547,7 @@ test.describe( 'Connectors', () => {
page.getByRole( 'link', {
name: 'search the plugin directory',
} )
- ).toBeHidden();
+ ).toBeVisible();
} );
} );
} );
diff --git a/tools/eslint/suppressions.json b/tools/eslint/suppressions.json
index c727b76a9f5447..bf65f223980395 100644
--- a/tools/eslint/suppressions.json
+++ b/tools/eslint/suppressions.json
@@ -1621,6 +1621,11 @@
"count": 1
}
},
+ "routes/connectors-home/default-connectors.tsx": {
+ "@wordpress/use-recommended-components": {
+ "count": 1
+ }
+ },
"routes/connectors-home/stage.tsx": {
"@wordpress/no-non-module-stylesheet-imports": {
"count": 1
From 64041e0df4d31ac6165b26c97bdc1c5505ac456d Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:34:35 +0200
Subject: [PATCH 19/23] Revert HStack to Stack change to minimize PR diff
---
routes/connectors-home/default-connectors.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index a6a746c0bf5b63..9ed2994824adc8 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { Button } from '@wordpress/components';
+import { __experimentalHStack as HStack, Button } from '@wordpress/components';
import { useRef } from '@wordpress/element';
import {
__experimentalRegisterConnector as registerConnector,
@@ -13,7 +13,7 @@ import {
} from '@wordpress/connectors';
import { select } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n';
-import { Badge, Link, Stack } from '@wordpress/ui';
+import { Badge, Link } from '@wordpress/ui';
/**
* Internal dependencies
@@ -189,7 +189,7 @@ function ApiKeyConnector( {
name={ name }
description={ description }
actionArea={
-
+
{ isConnected && }
{ showUnavailableBadge &&
( pluginSlug ? (
@@ -214,7 +214,7 @@ function ApiKeyConnector( {
{ getButtonLabel() }
) }
-
+
}
>
{ isExpanded && pluginStatus === 'active' && (
From 7027f7b3d5921711ff4956c7d4030fe4f698dee8 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:37:13 +0200
Subject: [PATCH 20/23] Remove wordpress.org search fallback; hide link when
restricted
---
routes/connectors-home/stage.tsx | 30 ++++++++++++-------------
test/e2e/specs/admin/connectors.spec.js | 2 +-
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 24b71407018ec3..9c4b5068a66d6b 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -93,10 +93,6 @@ function ConnectorsPage() {
( slug ) => ! installedPluginSlugs.has( slug )
);
const isEmpty = renderableConnectors.length === 0;
- const searchUrl =
- canInstallPlugins && ! isFileModsDisabled
- ? 'plugin-install.php?s=connector&tab=search&type=tag'
- : __( 'https://wordpress.org/plugins/search/ai-connectors/' );
return (
) }
-
- { createInterpolateElement(
- __(
- 'If the connector you need is not listed, search the plugin directory to see if a connector is available.'
- ),
- {
- a: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content
-
+ { canInstallPlugins && ! isFileModsDisabled && (
+
+ { createInterpolateElement(
+ __(
+ 'If the connector you need is not listed, search the plugin directory to see if a connector is available.'
),
- }
- ) }
-
+ {
+ a: (
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+
+ ),
+ }
+ ) }
+
+ ) }
);
diff --git a/test/e2e/specs/admin/connectors.spec.js b/test/e2e/specs/admin/connectors.spec.js
index b617237b6ab733..d8368261bed5d7 100644
--- a/test/e2e/specs/admin/connectors.spec.js
+++ b/test/e2e/specs/admin/connectors.spec.js
@@ -547,7 +547,7 @@ test.describe( 'Connectors', () => {
page.getByRole( 'link', {
name: 'search the plugin directory',
} )
- ).toBeVisible();
+ ).toBeHidden();
} );
} );
} );
From 3e82fd2f68ed4b9a81c53996807818484610ff00 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:44:05 +0200
Subject: [PATCH 21/23] Rename isFileModsDisabled to isFileModDisabled
---
lib/experimental/connectors/default-connectors.php | 2 +-
routes/connectors-home/default-connectors.tsx | 6 +++---
routes/connectors-home/stage.tsx | 10 +++++-----
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php
index 6d939f93314f0e..3087f693a9115a 100644
--- a/lib/experimental/connectors/default-connectors.php
+++ b/lib/experimental/connectors/default-connectors.php
@@ -555,7 +555,7 @@ function _gutenberg_get_connector_script_module_data( array $data ): array {
}
ksort( $connectors );
$data['connectors'] = $connectors;
- $data['isFileModsDisabled'] = ! wp_is_file_mod_allowed( 'install_plugins' );
+ $data['isFileModDisabled'] = ! wp_is_file_mod_allowed( 'install_plugins' );
return $data;
}
remove_filter( 'script_module_data_options-connectors-wp-admin', '_wp_connectors_get_connector_script_module_data' );
diff --git a/routes/connectors-home/default-connectors.tsx b/routes/connectors-home/default-connectors.tsx
index 9ed2994824adc8..2f2dec2db0e9e7 100644
--- a/routes/connectors-home/default-connectors.tsx
+++ b/routes/connectors-home/default-connectors.tsx
@@ -45,7 +45,7 @@ interface ConnectorData {
interface ConnectorScriptModuleData {
connectors?: Record< string, ConnectorData >;
- isFileModsDisabled?: boolean;
+ isFileModDisabled?: boolean;
}
function getConnectorScriptModuleData(): ConnectorScriptModuleData {
@@ -67,8 +67,8 @@ export function getConnectorData(): Record< string, ConnectorData > {
return getConnectorScriptModuleData().connectors ?? {};
}
-export function getIsFileModsDisabled(): boolean {
- return !! getConnectorScriptModuleData().isFileModsDisabled;
+export function getIsFileModDisabled(): boolean {
+ return !! getConnectorScriptModuleData().isFileModDisabled;
}
const CONNECTOR_LOGOS: Record< string, React.ComponentType > = {
diff --git a/routes/connectors-home/stage.tsx b/routes/connectors-home/stage.tsx
index 9c4b5068a66d6b..cfe6479ac3c3c5 100644
--- a/routes/connectors-home/stage.tsx
+++ b/routes/connectors-home/stage.tsx
@@ -25,7 +25,7 @@ import { Notice } from '@wordpress/ui';
import './style.scss';
import { AiPluginCallout } from './ai-plugin-callout';
import {
- getIsFileModsDisabled,
+ getIsFileModDisabled,
registerDefaultConnectors,
} from './default-connectors';
import { unlock } from '../lock-unlock';
@@ -36,7 +36,7 @@ const { store } = unlock( connectorsPrivateApis );
registerDefaultConnectors();
function ConnectorsPage() {
- const isFileModsDisabled = getIsFileModsDisabled();
+ const isFileModDisabled = getIsFileModDisabled();
const { connectors, canInstallPlugins, isAiPluginInstalled } = useSelect(
( select ) => {
@@ -107,13 +107,13 @@ function ConnectorsPage() {
}` }
>
{ manualInstallPluginSlugs.length > 0 &&
- ( isFileModsDisabled || ! canInstallPlugins ) && (
+ ( isFileModDisabled || ! canInstallPlugins ) && (
- { isFileModsDisabled
+ { isFileModDisabled
? __(
'Plugins cannot be installed here due to your site configuration. Install them manually using your normal deployment workflow.'
)
@@ -173,7 +173,7 @@ function ConnectorsPage() {
) }
- { canInstallPlugins && ! isFileModsDisabled && (
+ { canInstallPlugins && ! isFileModDisabled && (
{ createInterpolateElement(
__(
From 23f2ee5ae099cac3ce05ba19305974bd3f06d7e0 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 8 May 2026 08:47:05 -0700
Subject: [PATCH 22/23] Fix variable assignment alignment PHPCS issue
---
lib/experimental/connectors/default-connectors.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/experimental/connectors/default-connectors.php b/lib/experimental/connectors/default-connectors.php
index 3087f693a9115a..d12ed5391e7a72 100644
--- a/lib/experimental/connectors/default-connectors.php
+++ b/lib/experimental/connectors/default-connectors.php
@@ -554,7 +554,7 @@ function _gutenberg_get_connector_script_module_data( array $data ): array {
$connectors[ $connector_id ] = $connector_out;
}
ksort( $connectors );
- $data['connectors'] = $connectors;
+ $data['connectors'] = $connectors;
$data['isFileModDisabled'] = ! wp_is_file_mod_allowed( 'install_plugins' );
return $data;
}
From df811bee0f14bbaad8c74ce9b6caa9ee4d866e15 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Fri, 8 May 2026 17:50:14 +0200
Subject: [PATCH 23/23] Add backport changelog for core PR 11779
---
backport-changelog/7.0/11779.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 backport-changelog/7.0/11779.md
diff --git a/backport-changelog/7.0/11779.md b/backport-changelog/7.0/11779.md
new file mode 100644
index 00000000000000..08557620ebadf9
--- /dev/null
+++ b/backport-changelog/7.0/11779.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/11779
+
+* https://github.com/WordPress/gutenberg/pull/77521