prefix (not user input). // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared /** * @param callable $fn */ function todownload_with_blog(int $blog_id, callable $fn): void { if (function_exists('switch_to_blog')) { switch_to_blog($blog_id); try { $fn(); } finally { restore_current_blog(); } return; } $fn(); } /** * @param string $prefix */ function todownload_uninstall_for_prefix(string $prefix): void { global $wpdb; $prefix = esc_sql( $prefix ); $options_table = esc_sql( $prefix . 'options' ); // Remove known options. delete_option('ToDownload_settings'); delete_option('ToDownload_schema_version'); delete_option('ToDownload_rewrite_version'); delete_option('ToDownload_webhook_log_v1'); delete_option('ToDownload_account_page_id'); delete_option('ToDownload_post_type_migration_v1_done'); delete_option('ToDownload_public_id_backfill_v1_done'); // Best-effort: remove any other plugin options (including legacy keys). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Internal options table. $wpdb->query($wpdb->prepare( 'DELETE FROM ' . $options_table . ' WHERE option_name LIKE %s', 'ToDownload_%' )); // Remove plugin transients. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Internal options table. $wpdb->query($wpdb->prepare( 'DELETE FROM ' . $options_table . ' WHERE option_name LIKE %s OR option_name LIKE %s', '_transient_todownload_%', '_transient_timeout_todownload_%' )); // Drop custom tables. $tables = [ $prefix . 'ToDownload_customers', $prefix . 'ToDownload_orders', $prefix . 'ToDownload_order_items', $prefix . 'ToDownload_license_keys', ]; foreach ($tables as $t) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- Uninstall cleanup. $wpdb->query('DROP TABLE IF EXISTS ' . esc_sql( $t )); } } function todownload_uninstall_user_meta(): void { global $wpdb; $usermeta = esc_sql( $wpdb->usermeta ); // Delete all plugin user meta keys (including legacy ones). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query($wpdb->prepare( 'DELETE FROM ' . $usermeta . ' WHERE meta_key LIKE %s', 'ToDownload_%' )); } function todownload_uninstall_roles(): void { if (function_exists('remove_role')) { remove_role('todownload_seller'); } } function todownload_uninstall_run(): void { if (function_exists('is_multisite') && is_multisite()) { $site_ids = function_exists('get_sites') ? get_sites(['fields' => 'ids']) : []; if (is_array($site_ids)) { foreach ($site_ids as $blog_id) { $blog_id = (int) $blog_id; if ($blog_id <= 0) { continue; } todownload_with_blog($blog_id, function () use ($blog_id): void { global $wpdb; $prefix = $wpdb->get_blog_prefix($blog_id); todownload_uninstall_for_prefix($prefix); }); } } } else { global $wpdb; todownload_uninstall_for_prefix($wpdb->prefix); } todownload_uninstall_user_meta(); todownload_uninstall_roles(); } todownload_uninstall_run(); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared