General -> Data), default OFF: * * OFF (default) : remove NOTHING data-related. A reinstall restores an * identical setup. Only scheduled events are cleared, because * cron / Action Scheduler jobs left pointing at now-deleted * code fire errors — those are dangling execution hooks, not * data. * ON : remove everything the plugin ever created — custom table, * lead + agent posts and meta, every salyut_agents_* option and * transient (including dead/orphaned ones and the opt-in * setting itself), and all scheduled events. * * Single-site only. The plugin is not multisite-aware anywhere (no Network * header, no per-site activation). If multisite support is ever added, this * file must loop get_sites() and repeat the wipe per blog. * * @package SalyutAgents */ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { exit; } global $wpdb; $salyut_agents_delete_data = (bool) get_option( 'salyut_agents_delete_data_on_uninstall' ); /* ------------------------------------------------------------------------- * Scheduled events — ALWAYS, on both paths. * * WordPress does not remove a deleted plugin's cron events; they persist in * the `cron` option and WP-Cron keeps firing them as no-ops (recurring events * reschedule themselves on run). Clear them explicitly. * ---------------------------------------------------------------------- */ wp_clear_scheduled_hook( 'salyut_agents_daily_digest' ); wp_clear_scheduled_hook( 'salyut_agents_cleanup_orphaned_sessions' ); /* * Direct $wpdb is unavoidable and safe here: this is a one-shot delete routine * that runs once on plugin removal. It touches this plugin's own custom table * (DROP) and core tables ($wpdb->posts / postmeta / options), always via * $wpdb->prefix . a literal identifier or a hardcoded core property — never user * input. Every WHERE is either a literal LIKE prefix pattern or an IN() list * built with array_map('intval', ...). Object caching is irrelevant during * uninstall. The interpolation / caching / schema-change sniffs are disabled * for the remainder of the file for these reasons. */ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $salyut_agents_as_table = $wpdb->prefix . 'actionscheduler_actions'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $salyut_agents_as_table ) ) === $salyut_agents_as_table ) { if ( $salyut_agents_delete_data ) { // Full wipe: every salyut_agents_* action, its logs, and the group row. $salyut_agents_as_ids = $wpdb->get_col( "SELECT action_id FROM {$salyut_agents_as_table} WHERE hook LIKE 'salyut\\_agents\\_%'" ); if ( $salyut_agents_as_ids ) { $salyut_agents_as_in = implode( ',', array_map( 'intval', $salyut_agents_as_ids ) ); $wpdb->query( "DELETE FROM {$wpdb->prefix}actionscheduler_logs WHERE action_id IN ({$salyut_agents_as_in})" ); $wpdb->query( "DELETE FROM {$salyut_agents_as_table} WHERE action_id IN ({$salyut_agents_as_in})" ); } $wpdb->query( "DELETE FROM {$wpdb->prefix}actionscheduler_groups WHERE slug = 'salyut-agents'" ); } else { // OFF: cancel only the pending jobs so nothing fires against gone code. $wpdb->query( "DELETE FROM {$salyut_agents_as_table} WHERE hook LIKE 'salyut\\_agents\\_%' AND status IN ('pending','in-progress')" ); } } if ( ! $salyut_agents_delete_data ) { return; // OFF — every option, post, meta row and the messages table are left exactly as-is. } /* ------------------------------------------------------------------------- * ON — full wipe. * ---------------------------------------------------------------------- */ // Lead + agent posts and all of their meta. $wpdb->query( "DELETE pm FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type IN ('salyut_lead','salyut_agent')" ); $wpdb->query( "DELETE FROM {$wpdb->posts} WHERE post_type IN ('salyut_lead','salyut_agent')" ); // Plugin meta on any other post (e.g. _salyut_agents_agent_id on pages) and orphans. $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '\\_salyut\\_agents\\_%' OR meta_key LIKE 'salyut\\_agents\\_%'" ); // Custom table. Mirrors salyut_agents_drop_messages_table() in includes/db.php; // inlined so this file stays independent of the plugin's includes. $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}salyut_agents_messages" ); // Every salyut_agents_* option plus all plugin transients. A prefix sweep is used // rather than a hardcoded list so dead/orphaned options are removed too. // // NOTE: the pattern is anchored to `salyut_agents_` on purpose. Do NOT widen it to // %salyut-agents% — that would match core rows such as `theme_mods_salyut-agents` (when // a theme happens to be named "salyut-agents") and the plugin-dependency cache key. $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'salyut\\_agents\\_%' OR option_name LIKE '\\_transient\\_salyut\\_agents\\_%' OR option_name LIKE '\\_transient\\_timeout\\_salyut\\_agents\\_%' OR option_name LIKE '\\_site\\_transient\\_salyut\\_agents\\_%' OR option_name LIKE '\\_site\\_transient\\_timeout\\_salyut\\_agents\\_%'" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter // The provider-notice dismissal flag (includes/provider-status.php, // includes/settings.php), per user rather than per site — delete_metadata() // with an object_id of 0 and $delete_all true removes it for every user. delete_metadata( 'user', 0, 'salyut_agents_provider_notice_dismissed', '', true ); wp_cache_flush();