'ids', 'number' => 0 ] ) as $site_id ) { switch_to_blog( (int) $site_id ); self::remove_site_data(); restore_current_blog(); } return; } self::remove_site_data(); } /** Drops one site's tables, options, transients and dump files. */ private static function remove_site_data(): void { global $wpdb; foreach ( self::OWNED_TABLES as $table ) { TableDdl::drop( $wpdb, $wpdb->prefix . $table ); } $options = (array) $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- uninstall reads the live option names, fresh read required $wpdb->prepare( 'SELECT option_name FROM %i WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s', $wpdb->options, $wpdb->esc_like( 'shiftora_' ) . '%', $wpdb->esc_like( '_transient_shiftora_' ) . '%', $wpdb->esc_like( '_transient_timeout_shiftora_' ) . '%' ) ); foreach ( $options as $name ) { delete_option( (string) $name ); } DataDirectory::remove_all(); } /** Deactivation entry point. */ public static function deactivate(): void { self::cancel_scheduled_work(); StallRecovery::unschedule_cron(); } /** * Cancels every owned hook on both Action Scheduler and WP-Cron. * * @param null|callable(string):void $as Action Scheduler unscheduler. * @param null|callable(string):void $wp_cron WP-Cron sweep, called once per owned hook; null clears every args variant. */ public static function cancel_scheduled_work( ?callable $as = null, ?callable $wp_cron = null ): void { $as = $as ?? static function ( string $hook ): void { if ( function_exists( 'as_unschedule_all_actions' ) ) { as_unschedule_all_actions( $hook ); } }; $wp_cron = $wp_cron ?? static function ( string $hook ): void { // wp_clear_scheduled_hook() only matches identical args and the chunk hooks carry a migration_id, so walk the raw cron array. $crons = _get_cron_array(); if ( ! is_array( $crons ) || [] === $crons ) { return; } foreach ( $crons as $timestamp => $events ) { if ( ! isset( $events[ $hook ] ) || ! is_array( $events[ $hook ] ) ) { continue; } foreach ( array_keys( $events[ $hook ] ) as $key ) { $args = isset( $events[ $hook ][ $key ]['args'] ) && is_array( $events[ $hook ][ $key ]['args'] ) ? $events[ $hook ][ $key ]['args'] : []; wp_unschedule_event( (int) $timestamp, $hook, $args ); } } }; foreach ( self::SHIFTORA_CRON_HOOKS as $hook ) { $as( $hook ); $wp_cron( $hook ); } } }