query($wpdb->prepare('DROP TABLE IF EXISTS %i', $wpdb->prefix . $wconvertTable)); } /* * WHAT IS DELIBERATELY NOT HERE. * * - **The other sites of a multisite network.** Multisite is out of scope for * v1 and WConvert is meant to be activated per site, so this cleans the site * it was asked about — the same scope its activation, its options and its * tables all have (`Bootstrap::activate()`). */ // Owned, non-autoloaded capture receipts and anonymous flow labels. A static // statement with nothing to bind, so nothing to prepare; and not cached, // because it runs once, on the way out. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- see above. $wconvertDynamicOptions = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'wconvert\_capture\_%' OR option_name LIKE 'wconvert\_flow\_%' OR option_name LIKE 'wconvert\_mail\_%' OR option_name LIKE 'wconvert\_product\_tracking\_%'"); foreach ($wconvertDynamicOptions as $wconvertOption) { if (preg_match('/^wconvert_((?:capture|mail)_[a-f0-9]{64}|flow_[A-Z0-9]{26}_[a-f0-9]{64}|product_tracking_[A-Z0-9]{26})$/D', $wconvertOption)) delete_option($wconvertOption); } delete_option('wconvert_submission_checkpoint'); /* * Every scheduled job. * * WP-Cron events by hook, whatever their arguments — the import cleanup is * scheduled once per administrator, with that administrator as its argument, * so clearing it by hook and no arguments would miss every one. The pruner * and the recovery sweep are cleared on deactivation already, which WordPress * runs first; they are named again so that a plugin deleted without being * deactivated (WP-CLI's `--deactivate` is optional) leaves nothing behind. */ foreach (['wconvert_prune_leads', 'wconvert_recover_submissions', 'wconvert_transfer_cleanup'] as $wconvertHook) { wp_unschedule_hook($wconvertHook); } // Action Scheduler jobs, by WConvert's group (Queue\ActionSchedulerQueue::GROUP) // and never by table: the tables are shared with every other plugin that // bundles it. The plugin is not loaded here, so the library is loaded from its // own folder when no other plugin has; it initialises itself late. $wconvertScheduler = __DIR__ . '/vendor/woocommerce/action-scheduler/action-scheduler.php'; if (!class_exists('ActionScheduler', false) && is_file($wconvertScheduler)) { require_once $wconvertScheduler; } if (class_exists('ActionScheduler', false) && ActionScheduler::is_initialized() && function_exists('as_unschedule_all_actions')) { as_unschedule_all_actions('', [], 'wconvert'); } // The running copy has hooked its queue runner onto `shutdown`. If that copy // is WConvert's own — loaded here, or chosen from among every bundled copy // earlier in this request — WordPress deletes the folder it autoloads from // before `shutdown`, and the runner fatals on a class it can no longer find. // Unhook it for this request. Another plugin's copy is left alone. if (class_exists('ActionScheduler_QueueRunner', false) && str_starts_with((string) (new ReflectionClass('ActionScheduler_QueueRunner'))->getFileName(), __DIR__ . DIRECTORY_SEPARATOR)) { ActionScheduler_QueueRunner::instance()->unhook_dispatch_async_request(); } // User metadata is blog-scoped because WordPress users span multisite blogs. delete_metadata('user', 0, 'wconvert_picker_preferences_' . get_current_blog_id(), '', true); // Short-lived picker write leases belong only to this site's options table. // Static, like the statement above. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- see above. $wconvertPickerLocks = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'wconvert\_picker\_lock\_%'"); foreach ($wconvertPickerLocks as $wconvertPickerLock) { if (preg_match('/^wconvert_picker_lock_(?:occasions|user_[0-9]+)$/D', $wconvertPickerLock)) delete_option($wconvertPickerLock); } wp_clear_scheduled_hook('wconvert_product_stats_prune'); // Every WConvert transient: the rate-limit records (hashed addresses, minutes // long) and the destination field and schema caches. They would expire on // their own, but a deleted plugin should not leave a row behind for the next // visit to find. Listed from the options table, then deleted through // WordPress so an object cache drops its copy too. Static, like the // statements above. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- see above. $wconvertTransients = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_wconvert\_%'"); foreach ($wconvertTransients as $wconvertTransient) { delete_transient(substr($wconvertTransient, strlen('_transient_'))); }