--apply` to recover. * * Multisite: every site is checked and cleaned. The plugin may have been active * per site, and it is always inactive by the time it can be deleted, so network * activation cannot tell which sites hold wpdo_* tables. * * @package WP_Data_Optimizer */ declare(strict_types=1); if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { exit; } global $wpdb; // ── Load shared cleanup logic + snapshot classes ───────────────────────── $wpdo_bootstrap_files = array( __DIR__ . '/includes/class-tmdo-logger.php', __DIR__ . '/includes/class-tmdo-feature-flags.php', __DIR__ . '/includes/class-tmdo-installer.php', __DIR__ . '/includes/snapshots/class-tmdo-snapshot-manager.php', __DIR__ . '/includes/snapshots/class-tmdo-snapshot-writer.php', __DIR__ . '/includes/snapshots/class-tmdo-snapshot-reader.php', __DIR__ . '/includes/snapshots/class-tmdo-snapshot-pruner.php', ); $wpdo_bootstrap_ok = true; foreach ( $wpdo_bootstrap_files as $wpdo_bootstrap_file ) { if ( ! is_file( $wpdo_bootstrap_file ) ) { $wpdo_bootstrap_ok = false; continue; } require_once $wpdo_bootstrap_file; } // ── Sites to check and clean (0 = single site, no blog switching) ──────── $wpdo_site_ids = is_multisite() ? array_map( 'intval', get_sites( array( 'fields' => 'ids', 'number' => 0, ) ) ) : array( 0 ); $wpdo_for_each_site = static function ( callable $callback ) use ( $wpdo_site_ids ): void { foreach ( $wpdo_site_ids as $wpdo_blog_id ) { if ( $wpdo_blog_id ) { switch_to_blog( $wpdo_blog_id ); } try { $callback( $wpdo_blog_id ); } finally { if ( $wpdo_blog_id ) { restore_current_blog(); } } } }; // ── Refuse while any site holds data that exists only in wpdo_* tables ─── if ( ! $wpdo_bootstrap_ok ) { wp_die( esc_html( '2meet Data Optimizer:無法載入資料安全檢查,為避免資料遺失,已停止解除安裝。' ) ); } $wpdo_blocked = array(); $wpdo_for_each_site( static function ( int $blog_id ) use ( &$wpdo_blocked ): void { foreach ( TMDO_Installer::uninstall_blockers() as $reason ) { $wpdo_blocked[] = ( $blog_id ? "站台 #{$blog_id}:" : '' ) . $reason; } } ); if ( $wpdo_blocked ) { wp_die( '
' . esc_html( '2meet Data Optimizer 已停止解除安裝:以下資料只存在 wpdo_* 表,刪除外掛會一併刪除這些表。' ) . '
' . '' . esc_html( '請先以 wp tmdo bridge-restore-native 把資料寫回 WordPress 原生表,再解除安裝。若只想移除外掛程式,可手動刪除外掛目錄,資料表會保留。' ) . '
' ); } // ── v2.2.0 M1.5: pre-uninstall snapshot (main-site context only) ───────── // Snapshot is taken once on the main site — it captures the catalog of all // wpdo_snapshots rows (which is per-site so per-site snapshots survive the // site-level table DROP that happens later). Best-effort. if ( class_exists( 'TMDO_Snapshot_Manager' ) ) { $wpdo_snapshot = TMDO_Snapshot_Manager::create( 'pre_uninstall', array(), array( 'notes' => 'Auto-snapshot taken at plugin uninstall time', 'retention_days' => 365, 'inline_threshold_bytes' => 0, ) ); if ( ! empty( $wpdo_snapshot['ok'] ) ) { $wpdo_snapshot_id = $wpdo_snapshot['snapshot_id']; $wpdo_backup_dir = TMDO_Snapshot_Manager::backup_dir(); if ( is_dir( $wpdo_backup_dir ) ) { file_put_contents( trailingslashit( $wpdo_backup_dir ) . 'README-pre-uninstall.txt', "WPDO pre-uninstall snapshot: {$wpdo_snapshot_id}\n" . 'Created: ' . gmdate( 'Y-m-d H:i:s' ) . " UTC\n\n" . "To restore after re-installing wp-data-optimizer:\n" . " 1. Re-install the plugin (it will re-create wp_wpdo_snapshots).\n" . " 2. Manually re-insert this snapshot's catalog row, or use wp wpdo snapshot list to confirm.\n" . " 3. Run: wp wpdo snapshot restore {$wpdo_snapshot_id} --apply\n" ); } } } // ── Drop wpdo_* tables and options on every site ───────────────────────── $wpdo_for_each_site( static function (): void { TMDO_Installer::drop_all_tables_for_current_blog(); } );