prefix + // hardcoded stems (no user input reaches SQL). Uses the `%i` identifier // placeholder (WordPress 6.2+) so $wpdb->prepare() escapes the table name // safely and no phpcs:ignore is needed. // Feature 015 — Access Control v2 (FR-012 / FR-013). Purge the plugin's // namespace via the vendor RuleQuery BEFORE the raw DROP so BerlinDB // invalidates its cache for the namespace. class_exists guards against the // "vendor package uninstalled before this plugin" edge case (US5 scenario 3). if ( class_exists( '\WPBoilerplate\AccessControl\Database\Rule\RuleQuery' ) ) { $rule_query = new \WPBoilerplate\AccessControl\Database\Rule\RuleQuery( 'mcp' ); if ( method_exists( $rule_query, 'purge_namespace' ) ) { $rule_query->purge_namespace( 'acrossai-mcp-manager' ); } } $tables = array( $wpdb->prefix . 'acrossai_mcp_servers', $wpdb->prefix . 'acrossai_mcp_cli_auth_logs', $wpdb->prefix . 'acrossai_mcp_oauth_tokens', // F021 (reuses F016's retired table name — same DROP covers both). $wpdb->prefix . 'acrossai_mcp_oauth_audit', // F016 retired — DROP IF EXISTS no-ops. $wpdb->prefix . 'mcp_access_control', // F015 AC rule table (TABLE_SLUG = 'mcp'). $wpdb->prefix . 'acrossai_mcp_server_abilities', // F017 per-server ability overrides. $wpdb->prefix . 'acrossai_mcp_server_tools', // F020 per-server tool selection. $wpdb->prefix . 'acrossai_mcp_oauth_clients', // F021 OAuth clients. $wpdb->prefix . 'acrossai_mcp_oauth_auth_codes', // F021 OAuth auth codes. $wpdb->prefix . 'acrossai_mcp_connector_approved_users', // F032 ConnectorApprovedUsers — per-connector admin-approval list. ); foreach ( $tables as $table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) ); } // Feature 021 — clear the daily OAuth cleanup cron. Deactivator also does // this but uninstall runs when the plugin was still-active-then-deleted, // so we need it here too. wp_clear_scheduled_hook is idempotent. wp_clear_scheduled_hook( 'acrossai_mcp_manager_oauth_cleanup' ); // Feature 015 — delete the vendor-owned schema version option. The // `acrossai_mcp_*` LIKE-sweep below does NOT match `wpb_ac_mcp_*`, so // the vendor's version tracking option must be cleaned up explicitly. delete_option( 'wpb_ac_mcp_db_version' ); // Delete every `acrossai_mcp_*` option via LIKE-sweep on wp_options. $options = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", 'acrossai_mcp_%' ) ); if ( is_array( $options ) ) { foreach ( $options as $option_name ) { delete_option( $option_name ); } }