'ids', 'number' => 0, ) ); foreach ( $site_ids as $site_id ) { switch_to_blog( (int) $site_id ); self::cleanup_site(); restore_current_blog(); } self::cleanup_network(); } else { self::cleanup_site(); } self::cleanup_users(); } /** * Remove everything for the current site. */ public static function cleanup_site(): void { Schema::drop_all(); self::delete_options(); self::cleanup_action_scheduler(); wp_cache_delete( 'alloptions', 'options' ); } /** * Delete every option and transient whose name starts with unbreak_. */ private static function delete_options(): void { global $wpdb; $patterns = array( 'unbreak\_%', '\_transient\_unbreak\_%', '\_transient\_timeout\_unbreak\_%', '\_site\_transient\_unbreak\_%', '\_site\_transient\_timeout\_unbreak\_%', ); foreach ( $patterns as $pattern ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- One-off uninstall lookup. $names = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $pattern ) ); foreach ( $names as $name ) { delete_option( $name ); } } } /** * Remove network-wide (sitemeta) options on multisite. */ private static function cleanup_network(): void { global $wpdb; $patterns = array( 'unbreak\_%', '\_site\_transient\_unbreak\_%', '\_site\_transient\_timeout\_unbreak\_%' ); foreach ( $patterns as $pattern ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- One-off uninstall lookup. $names = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s", $pattern ) ); foreach ( $names as $name ) { delete_site_option( $name ); } } } /** * Remove user meta written by the plugin (per-user UI preferences). */ private static function cleanup_users(): void { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- One-off uninstall cleanup. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", 'unbreak\_%' ) ); } /** * Remove our Action Scheduler actions and logs. If that leaves the shared Action Scheduler tables * completely empty, drop them too (Action Scheduler recreates them when another plugin needs them). */ private static function cleanup_action_scheduler(): void { global $wpdb; $actions = $wpdb->prefix . 'actionscheduler_actions'; $groups = $wpdb->prefix . 'actionscheduler_groups'; $logs = $wpdb->prefix . 'actionscheduler_logs'; $claims = $wpdb->prefix . 'actionscheduler_claims'; if ( ! self::table_exists( $actions ) || ! self::table_exists( $groups ) ) { return; } // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- Uninstall cleanup of known tables; everything is prepared. $group_id = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT group_id FROM %i WHERE slug = %s', $groups, self::AS_GROUP ) ); if ( $group_id > 0 ) { if ( self::table_exists( $logs ) ) { $wpdb->query( $wpdb->prepare( 'DELETE l FROM %i l INNER JOIN %i a ON l.action_id = a.action_id WHERE a.group_id = %d', $logs, $actions, $group_id ) ); } $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE group_id = %d', $actions, $group_id ) ); $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE group_id = %d', $groups, $group_id ) ); } $remaining_actions = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $actions ) ); $remaining_groups = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $groups ) ); if ( 0 === $remaining_actions && 0 === $remaining_groups ) { foreach ( array( $actions, $groups, $logs, $claims ) as $table ) { $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) ); } $as_options = array( 'schema-ActionScheduler_StoreSchema', 'schema-ActionScheduler_LoggerSchema', 'action_scheduler_hybrid_store_demarkation', 'action_scheduler_migration_status', 'action_scheduler_lock_async-request-runner', 'as_has_wp_comment_logs', ); foreach ( $as_options as $option ) { delete_option( $option ); } } // phpcs:enable } /** * Whether a table exists. * * @param string $table Full table name. */ private static function table_exists( string $table ): bool { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Schema introspection. return $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ); } }