',
esc_attr( self::ROOT_ID ),
esc_html__( 'The Quick Connect wizard requires JavaScript. Enable JavaScript in your browser to use it.', 'acrossai-abilities-manager' )
);
}
/**
* Add the full-screen takeover body class.
*
* Hooked to `admin_body_class`.
*
* @since 0.0.34
* @param string $classes Space-separated body classes.
* @return string Possibly-extended class list.
*/
public function add_body_class( $classes ): string {
if ( ! $this->is_quick_connect_request() ) {
return (string) $classes;
}
return trim( $classes . ' ' . self::BODY_CLASS );
}
/**
* Suppress unrelated admin notices while the wizard is on screen.
*
* Hooked to `in_admin_header` at priority 1000. The CSS takeover hides
* WordPress chrome but notices render inside our own content column, so they
* must be removed rather than hidden.
*
* Deliberately request-scoped: without the guard this would strip notices
* site-wide, including security-critical ones from core and other plugins.
*
* @since 0.0.34
* @return void
*/
public function suppress_admin_notices(): void {
if ( ! $this->is_quick_connect_request() ) {
return;
}
remove_all_actions( 'admin_notices' );
remove_all_actions( 'all_admin_notices' );
remove_all_actions( 'user_admin_notices' );
remove_all_actions( 'network_admin_notices' );
}
/**
* Enqueue the wizard bundle.
*
* Hooked to `admin_enqueue_scripts`. Gated on the request flag, never the
* hook suffix, so the ~200 KB bundle cannot leak onto the abilities table,
* the settings page, or the Integrations page (spec FR-042 / SC-010).
*
* @since 0.0.34
* @return void
*/
public function enqueue_assets(): void {
if ( ! $this->is_quick_connect_request() ) {
return;
}
$build_url = defined( 'ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL' )
? \ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL . 'build/'
: plugins_url( 'build/', dirname( __DIR__, 3 ) . '/acrossai-abilities-manager.php' );
$build_path = defined( 'ACROSSAI_ABILITIES_MANAGER_PLUGIN_PATH' )
? \ACROSSAI_ABILITIES_MANAGER_PLUGIN_PATH . 'build/'
: dirname( __DIR__, 3 ) . '/build/';
$asset_file = $build_path . 'js/quick-connect.asset.php';
// Never include an asset manifest unguarded: a missing or unbuilt bundle
// would fatal the whole admin (BUG-UNCONDITIONAL-ASSET-INCLUDE).
if ( ! file_exists( $asset_file ) ) {
return;
}
$asset = require $asset_file;
$version = (string) ( $asset['version'] ?? '0.0.0' );
wp_enqueue_script(
self::HANDLE,
$build_url . 'js/quick-connect.js',
(array) ( $asset['dependencies'] ?? array() ),
$version,
true
);
wp_set_script_translations( self::HANDLE, 'acrossai-abilities-manager' );
wp_localize_script( self::HANDLE, 'acrossaiQuickConnect', $this->bootstrap_data() );
if ( file_exists( $build_path . 'css/quick-connect.css' ) ) {
wp_enqueue_style(
self::HANDLE,
$build_url . 'css/quick-connect.css',
array(),
$version
);
}
}
/**
* Data handed to the React bundle.
*
* Data-minimal by design: URLs the client cannot safely build, plus the REST
* nonce. No secrets, no PII.
*
* @since 0.0.34
* @return array Bootstrap payload.
*/
private function bootstrap_data(): array {
$assets_url = defined( 'ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL' )
? \ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL . 'assets/quick-connect/'
: plugins_url( 'assets/quick-connect/', dirname( __DIR__, 3 ) . '/acrossai-abilities-manager.php' );
return array(
'restUrl' => esc_url_raw( rest_url( 'acrossai/v1/quick-connect' ) ),
'restNonce' => wp_create_nonce( 'wp_rest' ),
'adminUrl' => esc_url_raw( admin_url( 'admin.php?page=acrossai-abilities-manager' ) ),
'pluginInstallUrl' => esc_url_raw( admin_url( 'plugin-install.php' ) ),
'logoUrl' => esc_url_raw( $assets_url . 'acrossai-logo.svg' ),
'iconUrl' => esc_url_raw( $assets_url . 'icon.svg' ),
'mcpAdapterRepoUrl' => 'https://github.com/WordPress/mcp-adapter',
/*
* GitHub's permanent "latest release" alias, so this URL does not go
* stale as the adapter releases.
*
* The wizard sends the operator to this page rather than linking the
* asset directly. A direct asset link starts a download on click with
* nothing on screen saying what arrived; the release page keeps the
* version and the assets list visible, so choosing `mcp-adapter.zip`
* over the source archive is a decision the operator can see
* themselves making. That choice matters: the source zipball unpacks
* to `WordPress-mcp-adapter-`, installing under the wrong folder
* name and silently breaking detection and updates.
*/
'mcpAdapterReleasesUrl' => 'https://github.com/WordPress/mcp-adapter/releases/latest',
'pluginUploadUrl' => esc_url_raw( admin_url( 'plugin-install.php?tab=upload' ) ),
'pluginsListUrl' => esc_url_raw( admin_url( 'plugins.php' ) ),
);
}
}