PluginProbe
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress / 4.1.2
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress v4.1.2
4.1.2 4.1.1 4.1.0 4.0.1 4.0.0 3.3.1 3.3.0 trunk 1.0.0 2.0.0 2.1.0 3.0.0 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8
← All changes | admin/class-migration.php +187 -0 4.0.04.1.2 View file →
@@ -12,11 +12,19 @@
12 12 */
13 13 class Quickwebp_Migration {
14 14
15 15 /**
16 + * Last attachment ID processed by the current migration batch.
17 + *
18 + * @var int
19 + */
20 + private $migration_cursor = 0;
21 +
22 + /**
16 23 * Init the migration.
17 24 */
18 25 public function init_migration() {
26 + $this->maybe_schedule_optimization_stats_migration();
19 27
20 28 $last_version = get_option( 'quickwebp_plugin_version', '1.0.0' );
21 29
22 30 if ( version_compare( $last_version, QUICKWEBP_VERSION, '<' ) ) {
@@ -21,8 +29,187 @@
21 29
22 30 if ( version_compare( $last_version, QUICKWEBP_VERSION, '<' ) ) {
23 31 $this->run_migration_tasks( $last_version );
24 32 update_option( 'quickwebp_plugin_version', QUICKWEBP_VERSION );
33 + }
34 + }
35 +
36 + /**
37 + * Schedule the optimization statistics migration outside the admin request.
38 + */
39 + private function maybe_schedule_optimization_stats_migration() {
40 + if ( get_option( 'quickwebp_optimization_stats_migrated', false ) ) {
41 + return;
42 + }
43 +
44 + if ( ! wp_next_scheduled( 'quickwebp_optimization_stats_migration_hook' ) ) {
45 + wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'quickwebp_optimization_stats_migration_hook' );
46 + }
47 + }
48 +
49 + /**
50 + * Process one bounded batch of the optimization statistics migration.
51 + */
52 + public function migrate_optimization_stats_batch() {
53 + if ( get_option( 'quickwebp_optimization_stats_migrated', false ) ) {
54 + return;
55 + }
56 +
57 + $lock_token = $this->acquire_migration_lock();
58 + if ( ! $lock_token ) {
59 + $this->schedule_next_migration_batch();
60 + return;
61 + }
62 +
63 + $state = get_option( 'quickwebp_optimization_stats_migration_state', array() );
64 + $state = wp_parse_args(
65 + is_array( $state ) ? $state : array(),
66 + array(
67 + 'cursor' => 0,
68 + 'images_optimized' => 0,
69 + 'bytes_before' => 0,
70 + 'bytes_after' => 0,
71 + 'oldest_time' => 0,
72 + )
73 + );
74 + $this->migration_cursor = absint( $state['cursor'] );
75 +
76 + add_filter( 'posts_where', array( $this, 'filter_migration_posts_after_cursor' ), 10, 2 );
77 + $query = new WP_Query(
78 + array(
79 + 'post_type' => 'attachment',
80 + 'post_status' => 'any',
81 + 'posts_per_page' => 50,
82 + 'fields' => 'ids',
83 + 'orderby' => 'ID',
84 + 'order' => 'ASC',
85 + 'no_found_rows' => true,
86 + 'update_post_meta_cache' => true,
87 + 'update_post_term_cache' => false,
88 + 'quickwebp_migration' => true,
89 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
90 + 'meta_query' => array(
91 + 'relation' => 'OR',
92 + array(
93 + 'key' => 'quickwebp_already_optimized',
94 + 'compare' => 'EXISTS',
95 + ),
96 + array(
97 + 'key' => 'quickwebp_data',
98 + 'compare' => 'EXISTS',
99 + ),
100 + ),
101 + )
102 + );
103 + remove_filter( 'posts_where', array( $this, 'filter_migration_posts_after_cursor' ), 10 );
104 +
105 + foreach ( $query->posts as $attachment_id ) {
106 + $data = get_post_meta( $attachment_id, 'quickwebp_data', true );
107 + $state['cursor'] = $attachment_id;
108 + $state['images_optimized']++;
109 + $attachment_time = (int) get_post_time( 'U', true, $attachment_id );
110 + $state['oldest_time'] = ! $state['oldest_time'] ? $attachment_time : min( $state['oldest_time'], $attachment_time );
111 +
112 + if ( ! is_array( $data ) ) {
113 + continue;
114 + }
115 +
116 + foreach ( $data as $size_data ) {
117 + $state['bytes_before'] += absint( $size_data['original_size'] ?? 0 );
118 + $state['bytes_after'] += absint( $size_data['optimized_size'] ?? 0 );
119 + }
120 + }
121 +
122 + if ( count( $query->posts ) === 50 ) {
123 + update_option( 'quickwebp_optimization_stats_migration_state', $state, false );
124 + $this->release_migration_lock( $lock_token );
125 + $this->schedule_next_migration_batch();
126 + return;
127 + }
128 +
129 + $stats = array(
130 + 'images_optimized' => absint( $state['images_optimized'] ),
131 + 'bytes_before' => absint( $state['bytes_before'] ),
132 + 'bytes_after' => absint( $state['bytes_after'] ),
133 + 'bytes_saved' => max( 0, absint( $state['bytes_before'] ) - absint( $state['bytes_after'] ) ),
134 + );
135 +
136 + if ( $stats['images_optimized'] > 0 ) {
137 + update_option( 'quickwebp_optimization_stats', $stats, false );
138 + }
139 +
140 + if ( ! get_option( 'quickwebp_installed_at', false ) ) {
141 + $installed_at = $state['oldest_time'] ? absint( $state['oldest_time'] ) : time() - ( 8 * DAY_IN_SECONDS );
142 + add_option( 'quickwebp_installed_at', $installed_at, '', false );
143 + }
144 +
145 + update_option( 'quickwebp_optimization_stats_migrated', 1, false );
146 + delete_option( 'quickwebp_optimization_stats_migration_state' );
147 + $this->release_migration_lock( $lock_token );
148 + }
149 +
150 + /**
151 + * Restrict the migration query to attachment IDs after the saved cursor.
152 + *
153 + * @param string $where Query WHERE clause.
154 + * @param WP_Query $query Current query.
155 + * @return string
156 + */
157 + public function filter_migration_posts_after_cursor( $where, $query ) {
158 + global $wpdb;
159 +
160 + if ( $query->get( 'quickwebp_migration' ) ) {
161 + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID > %d", $this->migration_cursor );
162 + }
163 +
164 + return $where;
165 + }
166 +
167 + /**
168 + * Schedule the next migration batch unless one is already pending.
169 + */
170 + private function schedule_next_migration_batch() {
171 + if ( ! wp_next_scheduled( 'quickwebp_optimization_stats_migration_hook' ) ) {
172 + wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'quickwebp_optimization_stats_migration_hook' );
173 + }
174 + }
175 +
176 + /**
177 + * Acquire an expiring migration lock.
178 + *
179 + * @return string|false
180 + */
181 + private function acquire_migration_lock() {
182 + $lock_token = wp_generate_uuid4();
183 + $lock = array(
184 + 'token' => $lock_token,
185 + 'expires' => time() + ( 5 * MINUTE_IN_SECONDS ),
186 + );
187 +
188 + if ( add_option( 'quickwebp_optimization_stats_migration_lock', $lock, '', false ) ) {
189 + return $lock_token;
190 + }
191 +
192 + $existing_lock = get_option( 'quickwebp_optimization_stats_migration_lock', array() );
193 + if ( is_array( $existing_lock ) && absint( $existing_lock['expires'] ?? 0 ) < time() ) {
194 + delete_option( 'quickwebp_optimization_stats_migration_lock' );
195 + if ( add_option( 'quickwebp_optimization_stats_migration_lock', $lock, '', false ) ) {
196 + return $lock_token;
197 + }
198 + }
199 +
200 + return false;
201 + }
202 +
203 + /**
204 + * Release the migration lock owned by this process.
205 + *
206 + * @param string $lock_token Lock ownership token.
207 + */
208 + private function release_migration_lock( $lock_token ) {
209 + $lock = get_option( 'quickwebp_optimization_stats_migration_lock', array() );
210 + if ( is_array( $lock ) && hash_equals( (string) ( $lock['token'] ?? '' ), $lock_token ) ) {
211 + delete_option( 'quickwebp_optimization_stats_migration_lock' );
25 212 }
26 213 }
27 214
28 215 /**