$improveab_plugin ) { if ( ! isset( $improveab_plugin['TextDomain'] ) || 'improveab-ab-testing' !== $improveab_plugin['TextDomain'] || 0 !== validate_plugin( $improveab_file ) ) { continue; } if ( dirname( $improveab_file ) . '/uninstall.php' !== $improveab_own ) { return; } } // A closure held in a variable of this file, not a named function: a copy // that stood aside above defines nothing, and two copies deleted in one // request never declare a function twice. /** * Remove the plugin's state for the current site. * * Data is kept by default so an accidental delete-and-reinstall loses * nothing: tests, results and settings stay. Tables and options are only * removed when the user opted in via Settings → Data & privacy → "Delete * all data when the plugin is removed". * Caches, the upgrade lock and the scheduled events are removed either way: * they are worthless without the plugin, and the runtime cache can be an * autoloaded 64 KB blob that would otherwise load on every request forever. * * @return void */ $improveab_uninstall_site = static function () { global $wpdb; delete_option( 'improveab_runtime_cache' ); delete_option( 'improveab_runtime_idle' ); delete_option( 'improveab_runtime_events' ); delete_option( 'improveab_runtime_lock' ); delete_option( 'improveab_cache_gen' ); delete_option( 'improveab_data_gen' ); delete_option( 'improveab_upgrading' ); delete_option( 'improveab_tally_lock' ); delete_option( 'improveab_rollup_lock' ); delete_option( 'improveab_create_lock' ); delete_option( 'improveab_orphans' ); // A slow report's recompute lock (remember()), held for one request. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- the plugin's own lock rows. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( 'improveab_agg_lock_' ) . '%' ) ); delete_transient( 'improveab_split_map' ); // The /event rate-limit counters (their own table) are nobody's data: // they go on every uninstall. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- the plugin's own counter rows. if ( $wpdb->prefix . 'improveab_rl' === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $wpdb->prefix . 'improveab_rl' ) ) ) ) { $wpdb->query( "DELETE FROM {$wpdb->prefix}improveab_rl" ); } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching // Scheduled events are always cleared, every one under the plugin's // prefix whatever scheduled it: nothing should keep firing for a plugin // that no longer exists. $improveab_hooks = array(); foreach ( (array) get_option( 'cron', array() ) as $improveab_events ) { foreach ( is_array( $improveab_events ) ? array_keys( $improveab_events ) : array() as $improveab_hook ) { if ( is_string( $improveab_hook ) && 0 === strpos( $improveab_hook, 'improveab_' ) ) { $improveab_hooks[ $improveab_hook ] = true; } } } foreach ( array_keys( $improveab_hooks ) as $improveab_hook ) { wp_unschedule_hook( $improveab_hook ); } $settings = get_option( 'improveab_settings', array() ); if ( ! is_array( $settings ) || empty( $settings['delete_on_uninstall'] ) ) { // The record of which edition ran stays with the data it describes: // installed again, either edition finds it and knows whether the // visitors counted so far were assigned by other code. return; } // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Removing the plugin's own custom tables on user-requested uninstall. $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_events" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_daily" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_tally" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_buckets" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_variants" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_tests" ); $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}improveab_rl" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange // Every option under the plugin's reserved prefix, whichever edition // or version stored it (one uploaded over another never ran its own // uninstall, so what that one kept is only here to remove), with the // plugin's transients and site transients and an order's lock: the // schema's version, the record of the edition that ran, the settings, // and the short-lived rows that would otherwise wait for the daily // expired-transient sweep. One by one through delete_option(), so a // persistent object cache forgets them too (its copy of the // autoloaded options among them); with such a cache the transients // never touched this table and lapse with their TTL. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Finding the plugin's own option rows on user-requested uninstall. $improveab_options = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( 'improveab_' ) . '%', $wpdb->esc_like( '_transient_improveab_' ) . '%', $wpdb->esc_like( '_transient_timeout_improveab_' ) . '%', $wpdb->esc_like( '_site_transient_improveab_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_improveab_' ) . '%' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching foreach ( (array) $improveab_options as $improveab_option ) { delete_option( $improveab_option ); } // The tests a WooCommerce order's revenue was credited to (order meta), // in the posts tables or, with HPOS, in WooCommerce's own orders meta // table. Test ids start again at 1 after a reinstall, so a credit left // behind would zero a new test's revenue from that order. delete_metadata( 'post', 0, '_improveab_credited', '', true ); delete_metadata( 'post', 0, '_improveab_pending', '', true ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Removing the plugin's own order meta on user-requested uninstall; WooCommerce has no bulk API for it. if ( $wpdb->prefix . 'wc_orders_meta' === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $wpdb->prefix . 'wc_orders_meta' ) ) ) ) { $wpdb->delete( $wpdb->prefix . 'wc_orders_meta', array( 'meta_key' => '_improveab_credited' ) ); $wpdb->delete( $wpdb->prefix . 'wc_orders_meta', array( 'meta_key' => '_improveab_pending' ) ); } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key // Each user's settings on this site under the plugin's prefix (the // guides seen, the results view): user options, so their meta keys // carry this site's table prefix and another site's stay. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Finding the plugin's own user settings on user-requested uninstall. $improveab_prefs = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT meta_key FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $wpdb->esc_like( $wpdb->get_blog_prefix() . 'improveab_' ) . '%' ) ); foreach ( (array) $improveab_prefs as $improveab_pref ) { delete_metadata( 'user', 0, $improveab_pref, '', true ); } // The manage-tests capability lives in the roles option; take it back. foreach ( wp_roles()->role_objects as $role ) { if ( $role->has_cap( 'improveab_manage_tests' ) ) { $role->remove_cap( 'improveab_manage_tests' ); } } }; // Tables, options, roles and cron are all site-scoped, and WordPress runs // this file once from whichever site the delete was requested on. Walk the // network so no site keeps orphaned tables or a recurring event with no // listener (mirrors the per-site activation in ImproveAB_Install::activate()). if ( is_multisite() && function_exists( 'get_sites' ) ) { foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $improveab_site_id ) { switch_to_blog( (int) $improveab_site_id ); $improveab_uninstall_site(); restore_current_blog(); } } else { $improveab_uninstall_site(); }