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 +158 -21 1.10 → trunk View file →
@@ -1,10 +1,8 @@
1 1 <?php
2 2
3 3 use Imagify\Traits\InstanceGetterTrait;
4 4
5 -defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
6 -
7 5 /**
8 6 * Class that handles the auto-optimization process.
9 7 * This occurs when a new image is uploaded, and when an optimized image is worked with (resized, etc).
10 8 * The process will work only if wp_generate_attachment_metadata() and wp_update_attachment_metadata() are used.
@@ -40,8 +38,16 @@
40 38 */
41 39 private $is_wp_53;
42 40
43 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 + /**
44 50 * The ID of the attachment that failed to be uploaded.
45 51 *
46 52 * @var int
47 53 * @since 1.9.8
@@ -75,11 +81,12 @@
75 81 $priority = IMAGIFY_INT_MAX - 30;
76 82 $this->is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0;
77 83
78 84 // Automatic optimization tunel.
79 - add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
80 - add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 2 );
81 - add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 );
85 + add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
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 );
88 + add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 );
82 89
83 90 if ( $this->is_wp_53 ) {
84 91 // WP 5.3+.
85 92 add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 );
@@ -84,18 +91,30 @@
84 91 // WP 5.3+.
85 92 add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 );
86 93 // Upload failure recovering.
87 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).
88 - } else {
89 - add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
90 - add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
91 95 }
92 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 +
93 112 add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 );
94 113
95 114 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
96 115 add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
97 - add_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
116 + add_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
98 117 }
99 118
100 119 /**
101 120 * Remove the hooks.
@@ -105,11 +124,12 @@
105 124 public function remove_hooks() {
106 125 $priority = IMAGIFY_INT_MAX - 30;
107 126
108 127 // Automatic optimization tunel.
109 - remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
128 + remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
129 + remove_action( 'rest_after_insert_attachment', [ $this, 'flag_awaiting_client_side_subsizes' ], $priority );
110 130 remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority );
111 - remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority );
131 + remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority );
112 132
113 133 if ( $this->is_wp_53 ) {
114 134 // WP 5.3+.
115 135 remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority );
@@ -114,18 +134,18 @@
114 134 // WP 5.3+.
115 135 remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority );
116 136 // Upload failure recovering.
117 137 remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 );
118 - } else {
119 - remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
120 - remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
121 138 }
122 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 +
123 143 remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority );
124 144
125 145 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
126 146 remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
127 - remove_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
147 + remove_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
128 148 }
129 149
130 150
131 151 /** ----------------------------------------------------------------------------------------- */
@@ -146,17 +166,59 @@
146 166 }
147 167 }
148 168
149 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 + /**
150 208 * Store the "generate step" when wp_generate_attachment_metadata() is used.
151 209 *
152 210 * @since 1.9.10
211 + * @since 2.3.3 Added the $context parameter.
153 212 *
154 - * @param array $metadata An array of attachment meta data.
155 - * @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.
156 218 * @return array
157 219 */
158 - public function maybe_store_generate_step( $metadata, $attachment_id ) {
220 + public function maybe_store_generate_step( $metadata, $attachment_id, $context = null ) {
159 221 if ( self::is_optimization_prevented( $attachment_id ) ) {
160 222 return $metadata;
161 223 }
162 224
@@ -164,8 +226,32 @@
164 226 $this->unset_steps( $attachment_id );
165 227 return $metadata;
166 228 }
167 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 +
168 254 $this->set_step( $attachment_id, 'generate' );
169 255
170 256 return $metadata;
171 257 }
@@ -170,8 +256,43 @@
170 256 return $metadata;
171 257 }
172 258
173 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 + /**
174 295 * After the attachment meta data has been generated (partially, since WP 5.3), init the auto-optimization.
175 296 * Two cases are possible to trigger the optimization:
176 297 * - It's a new upload and auto-optimization is enabled.
177 298 * - It's not a new upload (it is regenerated) and the attachment is already optimized.
@@ -285,8 +406,18 @@
285 406
286 407 // Ready for the next step.
287 408 $this->set_step( $attachment_id, 'update' );
288 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 +
289 420 /**
290 421 * Triggered after a media auto-optimization init.
291 422 *
292 423 * @since 1.9.8
@@ -363,10 +494,16 @@
363 494 if ( $is_new_upload ) {
364 495 /**
365 496 * It's a new upload.
366 497 */
367 - // Optimize.
368 - $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 + );
369 506 } else {
370 507 /**
371 508 * The media has already been optimized (or at least it has been tried).
372 509 */
@@ -490,9 +627,9 @@
490 627 if ( ! get_post( $this->upload_failure_id ) ) {
491 628 return $content;
492 629 }
493 630
494 - $json = @json_decode( $content );
631 + $json = json_decode( $content );
495 632
496 633 if ( empty( $json->success ) ) {
497 634 return $content;
498 635 }