` query var set by `Settings::handle_actions()` after a * form action redirect. This stays on the standard `admin_notices` hook * because it's page-scoped and transient; the shared collection would be * wrong for a "server saved" flash. * * 2. Push persistent-condition notices (missing MCP adapter, missing * wpb-access-control library) into the cross-plugin `acrossai_notices` * filter introduced in acrossai-co/main-menu 0.0.30. The vendor package * handles rendering (Notices submenu + WP-native dismissible summary) * and per-user fingerprint-based dismissal — this class only supplies * the records. * * Constitution: singleton + private __construct + zero add_action/add_filter. * Hooks wired by Includes\Main::define_admin_hooks(). */ class Notices { /** @var Notices|null */ protected static $_instance = null; /** * Get the singleton instance. * * @return self */ public static function instance(): self { if ( null === self::$_instance ) { self::$_instance = new self(); } return self::$_instance; } /** * Private constructor — enforces the plugin-wide singleton convention. * No add_action / add_filter here; hooks are wired by * Includes\Main::define_admin_hooks(). */ private function __construct() {} // ───────────────────────────────────────────────────────────────────────── // FR-016 — Action-result notice from `?notice=...` query var. // ───────────────────────────────────────────────────────────────────────── /** * Render success / error notice based on the `notice` query var set by * post-action redirects in Settings::handle_actions(). Wired on `admin_notices`. */ public function render_action_result_notice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $notice = isset( $_GET['notice'] ) ? sanitize_key( wp_unslash( $_GET['notice'] ) ) : ''; if ( '' === $notice ) { return; } $messages = array( 'server_created' => array( 'success', __( 'Server created.', 'acrossai-mcp-manager' ) ), 'server_saved' => array( 'success', __( 'Server saved.', 'acrossai-mcp-manager' ) ), 'server_deleted' => array( 'success', __( 'Server deleted.', 'acrossai-mcp-manager' ) ), 'server_toggled' => array( 'success', __( 'Server status toggled.', 'acrossai-mcp-manager' ) ), 'bulk_completed' => array( 'success', __( 'Bulk action completed.', 'acrossai-mcp-manager' ) ), 'slug_exists' => array( 'error', __( 'Slug already in use.', 'acrossai-mcp-manager' ) ), 'empty_name' => array( 'error', __( 'Server name is required.', 'acrossai-mcp-manager' ) ), 'db_error' => array( 'error', __( 'Database write failed.', 'acrossai-mcp-manager' ) ), 'server_not_found' => array( 'error', __( 'Server not found.', 'acrossai-mcp-manager' ) ), ); if ( ! isset( $messages[ $notice ] ) ) { return; } printf( '
%s
. */
__( 'The wpb-access-control library (%s) is not loaded. Per-server MCP access-control rules are inactive and all tool calls will pass (fail-open). Install or activate the library to enforce saved rules.', 'acrossai-mcp-manager' ),
'WPBoilerplate\\AccessControl\\AccessControlManager'
),
'type' => 'warning',
'source' => __( 'MCP Manager', 'acrossai-mcp-manager' ),
);
}
if ( (bool) get_option( 'acrossai_mcp_npm_login_enabled', false ) ) {
$auth_url = \AcrossAI_MCP_Manager\Public\Partials\FrontendAuth::get_base_url();
$notices[] = array(
'id' => 'acrossai_mcp_manager_cli_auth_cache_exclusion',
'title' => __( 'Exclude CLI auth URL from page cache', 'acrossai-mcp-manager' ),
'message' => sprintf(
/* translators: %s: the frontend CLI authorization URL, wrapped in . */
__( 'The npm / npx CLI connection flow is enabled. The frontend authorization page at %s contains time-sensitive auth codes and nonces. If your hosting, CDN, or caching plugin caches this URL, authentication will silently fail. Exclude this path from all page-caching rules.', 'acrossai-mcp-manager' ),
'' . esc_url( $auth_url ) . ''
),
'type' => 'warning',
'source' => __( 'MCP Manager', 'acrossai-mcp-manager' ),
);
}
return $notices;
}
}