PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
← All changes | inc/classes/class-imagify-auto-optimization.php +151 -12 2.2.9 → trunk View file →
@@ -38,8 +38,16 @@
38 38 */
39 39 private $is_wp_53;
40 40
41 41 /**
42 + * Attachment IDs whose optimization must wait for the metadata to be stored.
43 + *
44 + * @var array
45 + * @since 2.3.3
46 + */
47 + private $deferred = [];
48 +
49 + /**
42 50 * The ID of the attachment that failed to be uploaded.
43 51 *
44 52 * @var int
45 53 * @since 1.9.8
@@ -74,9 +82,10 @@
74 82 $this->is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0;
75 83
76 84 // Automatic optimization tunel.
77 85 add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
78 - add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 2 );
86 + add_action( 'rest_after_insert_attachment', [ $this, 'flag_awaiting_client_side_subsizes' ], $priority, 3 );
87 + add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 3 );
79 88 add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 );
80 89
81 90 if ( $this->is_wp_53 ) {
82 91 // WP 5.3+.
@@ -82,13 +91,25 @@
82 91 // WP 5.3+.
83 92 add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 );
84 93 // Upload failure recovering.
85 94 add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1).
86 - } else {
87 - add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
88 - add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
89 95 }
90 96
97 + /**
98 + * Also used on WP 5.3+ to optimize once the metadata is stored, which the client side
99 + * upload of WP 7.1 needs: it stores every sub size at once, so reading them before the
100 + * write would only ever see the full size. Harmless the rest of the time, since an
101 + * optimization that already ran has cleared its steps by then.
102 + *
103 + * The trade-off is that these two hooks now fire for every post meta write on the site
104 + * rather than only on old WordPress versions. The callback returns on anything that is
105 + * not '_wp_attachment_metadata', which is the first thing it checks, so the cost is one
106 + * string comparison. Carrying the state on the attachment instead would mean an extra
107 + * read on every upload, for a narrower guarantee.
108 + */
109 + add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
110 + add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
111 +
91 112 add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 );
92 113
93 114 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
94 115 add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
@@ -104,8 +125,9 @@
104 125 $priority = IMAGIFY_INT_MAX - 30;
105 126
106 127 // Automatic optimization tunel.
107 128 remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
129 + remove_action( 'rest_after_insert_attachment', [ $this, 'flag_awaiting_client_side_subsizes' ], $priority );
108 130 remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority );
109 131 remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority );
110 132
111 133 if ( $this->is_wp_53 ) {
@@ -112,13 +134,13 @@
112 134 // WP 5.3+.
113 135 remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority );
114 136 // Upload failure recovering.
115 137 remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 );
116 - } else {
117 - remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
118 - remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
119 138 }
120 139
140 + remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
141 + remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
142 +
121 143 remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority );
122 144
123 145 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
124 146 remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
@@ -144,17 +166,59 @@
144 166 }
145 167 }
146 168
147 169 /**
170 + * Remember that the browser will send the sub sizes of an attachment separately.
171 + *
172 + * WordPress 7.1 can let the browser process an upload. The attachment is then created
173 + * with no sub sizes at all, each one is sent afterwards through the sideload endpoint,
174 + * and a last request finalizes the metadata. Optimizing when the attachment is created
175 + * would only ever cover the full size.
176 + *
177 + * This runs before the metadata is first generated, and the flag is stored because the
178 + * sub sizes arrive in later requests.
179 + *
180 + * @since 2.3.3
181 + *
182 + * @param object $attachment Inserted or updated attachment object. A \WP_Post when WordPress fires this.
183 + * @param object $request Request object. A \WP_REST_Request when WordPress fires this.
184 + * @param bool $creating True when creating an attachment, false when updating.
185 + */
186 + public function flag_awaiting_client_side_subsizes( $attachment, $request, $creating ) {
187 + if ( ! $creating || ! is_object( $attachment ) || ! isset( $attachment->ID ) ) {
188 + return;
189 + }
190 +
191 + if ( ! is_object( $request ) || ! is_callable( [ $request, 'get_param' ] ) ) {
192 + return;
193 + }
194 +
195 + if ( false !== $request->get_param( 'generate_sub_sizes' ) ) {
196 + return;
197 + }
198 +
199 + if ( ! imagify_is_attachment_mime_type_supported( $attachment->ID ) ) {
200 + // Nothing would ever read the flag for this attachment.
201 + return;
202 + }
203 +
204 + set_transient( $this->get_awaiting_subsizes_transient_name( $attachment->ID ), 1, HOUR_IN_SECONDS );
205 + }
206 +
207 + /**
148 208 * Store the "generate step" when wp_generate_attachment_metadata() is used.
149 209 *
150 210 * @since 1.9.10
211 + * @since 2.3.3 Added the $context parameter.
151 212 *
152 - * @param array $metadata An array of attachment meta data.
153 - * @param int $attachment_id Current attachment ID.
213 + * @param array $metadata An array of attachment meta data.
214 + * @param int $attachment_id Current attachment ID.
215 + * @param string $context Can be 'create' when the metadata was initially created for a
216 + * new attachment, or 'update' when it was updated. Passed by WordPress
217 + * since 5.3, so only null if something else applies the filter.
154 218 * @return array
155 219 */
156 - public function maybe_store_generate_step( $metadata, $attachment_id ) {
220 + public function maybe_store_generate_step( $metadata, $attachment_id, $context = null ) {
157 221 if ( self::is_optimization_prevented( $attachment_id ) ) {
158 222 return $metadata;
159 223 }
160 224
@@ -162,8 +226,32 @@
162 226 $this->unset_steps( $attachment_id );
163 227 return $metadata;
164 228 }
165 229
230 + if ( $this->is_awaiting_client_side_subsizes( $attachment_id ) ) {
231 + if ( 'create' === $context ) {
232 + /**
233 + * The browser has not sent its sub sizes yet. Optimizing now would cover the
234 + * full size only, and leave every thumbnail untouched.
235 + */
236 + return $metadata;
237 + }
238 +
239 + /**
240 + * The sub sizes are in. The upload step was set in the request that created the
241 + * attachment and did not outlive it, so set it again: as far as Imagify is
242 + * concerned this still is a brand new upload.
243 + */
244 + $this->clear_awaiting_client_side_subsizes( $attachment_id );
245 + $this->set_step( $attachment_id, 'upload' );
246 +
247 + /**
248 + * The sub sizes are all stored in one go, right after this, so the optimization
249 + * has to wait for that write. Reading them now would only see the full size.
250 + */
251 + $this->deferred[ $attachment_id ] = 1;
252 + }
253 +
166 254 $this->set_step( $attachment_id, 'generate' );
167 255
168 256 return $metadata;
169 257 }
@@ -168,8 +256,43 @@
168 256 return $metadata;
169 257 }
170 258
171 259 /**
260 + * Tell if the browser is still to send the sub sizes of an attachment.
261 + *
262 + * @since 2.3.3
263 + *
264 + * @param int $attachment_id Current attachment ID.
265 + * @return bool
266 + */
267 + public function is_awaiting_client_side_subsizes( $attachment_id ) {
268 + return (bool) get_transient( $this->get_awaiting_subsizes_transient_name( $attachment_id ) );
269 + }
270 +
271 + /**
272 + * Forget that the browser was to send the sub sizes of an attachment.
273 + *
274 + * @since 2.3.3
275 + *
276 + * @param int $attachment_id Current attachment ID.
277 + */
278 + public function clear_awaiting_client_side_subsizes( $attachment_id ) {
279 + delete_transient( $this->get_awaiting_subsizes_transient_name( $attachment_id ) );
280 + }
281 +
282 + /**
283 + * Get the transient name used to await the sub sizes of an attachment.
284 + *
285 + * @since 2.3.3
286 + *
287 + * @param int $attachment_id Current attachment ID.
288 + * @return string
289 + */
290 + private function get_awaiting_subsizes_transient_name( $attachment_id ) {
291 + return 'imagify_awaiting_subsizes_' . (int) $attachment_id;
292 + }
293 +
294 + /**
172 295 * After the attachment meta data has been generated (partially, since WP 5.3), init the auto-optimization.
173 296 * Two cases are possible to trigger the optimization:
174 297 * - It's a new upload and auto-optimization is enabled.
175 298 * - It's not a new upload (it is regenerated) and the attachment is already optimized.
@@ -283,8 +406,18 @@
283 406
284 407 // Ready for the next step.
285 408 $this->set_step( $attachment_id, 'update' );
286 409
410 + if ( ! empty( $this->deferred[ $attachment_id ] ) ) {
411 + /**
412 + * The metadata is not stored yet, so the sizes to optimize cannot be read.
413 + * $this->do_auto_optimization_after_meta_update() takes over once it is.
414 + */
415 + unset( $this->deferred[ $attachment_id ] );
416 +
417 + return $metadata;
418 + }
419 +
287 420 /**
288 421 * Triggered after a media auto-optimization init.
289 422 *
290 423 * @since 1.9.8
@@ -361,10 +494,16 @@
361 494 if ( $is_new_upload ) {
362 495 /**
363 496 * It's a new upload.
364 497 */
365 - // Optimize.
366 - $process->optimize( null, [ 'is_new_upload' => 1 ] );
498 + // Optimize. Flag as priority so it jumps ahead of any bulk optimization queue.
499 + $process->optimize(
500 + null,
501 + [
502 + 'is_new_upload' => 1,
503 + 'priority' => true,
504 + ]
505 + );
367 506 } else {
368 507 /**
369 508 * The media has already been optimized (or at least it has been tried).
370 509 */