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 | classes/Job/MediaOptimization.php +164 -64 1.10 → trunk View file →
@@ -1,28 +1,34 @@
1 1 <?php
2 2 namespace Imagify\Job;
3 3
4 -use Imagify\Media\MediaInterface;
5 -use Imagify\Optimization\File;
6 4 use Imagify\Optimization\Process\ProcessInterface;
5 +use Imagify\Traits\InstanceGetterTrait;
6 +use WP_Error;
7 7
8 -defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
9 -
10 8 /**
11 9 * Job class for media optimization.
12 10 *
13 - * @since 1.9
14 - * @author Grégory Viguier
11 + * @since 1.9
15 12 */
16 -class MediaOptimization extends \Imagify_Abstract_Background_Process {
13 +final class MediaOptimization extends \Imagify_Abstract_Background_Process {
14 + use InstanceGetterTrait;
17 15
18 16 /**
17 + * Batch-key segment used to mark a batch as "urgent" (high priority).
18 + * Shared by generate_key() (write path) and get_batches() (read path) so both
19 + * always agree on the exact same string shape.
20 + *
21 + * @var string
22 + * @since 2.3.2
23 + */
24 + private const URGENT_KEY_SEGMENT = 'batch_urgent';
25 +
26 + /**
19 27 * Background process: the action to perform.
20 28 *
21 - * @var string
22 - * @since 1.9
23 - * @access protected
24 - * @author Grégory Viguier
29 + * @var string
30 + * @since 1.9
25 31 */
26 32 protected $action = 'optimize_media';
27 33
28 34 /**
@@ -27,31 +33,17 @@
27 33
28 34 /**
29 35 * The optimization process instance.
30 36 *
31 - * @var ProcessInterface
32 - * @since 1.9
33 - * @access protected
34 - * @author Grégory Viguier
37 + * @var ?ProcessInterface
38 + * @since 1.9
35 39 */
36 40 protected $optimization_process;
37 41
38 42 /**
39 - * The single instance of the class.
40 - *
41 - * @var object
42 - * @since 1.9
43 - * @access protected
44 - * @author Grégory Viguier
45 - */
46 - protected static $_instance;
47 -
48 - /**
49 43 * Handle job logic.
50 44 *
51 - * @since 1.9
52 - * @access protected
53 - * @author Grégory Viguier
45 + * @since 1.9
54 46 *
55 47 * @param array $item {
56 48 * The data to use for this job.
57 49 *
@@ -66,8 +58,9 @@
66 58 * It should contain the following though:
67 59 *
68 60 * @type string $hook_suffix Suffix used to trigger hooks before and after optimization. Should be always provided.
69 61 * @type bool $delete_backup True to delete the backup file after the optimization process. This is used when a temporary backup of the original file has been created, but backup option is disabled. Default is false.
62 + * @type bool $bulk True when this optimization was triggered by a bulk run (see Bulk::force_optimize()). Forwarded verbatim to the 'imagify_before_*'/'imagify_after_*' hook callbacks below. Default is false.
70 63 * }
71 64 * }
72 65 * @return array|bool The modified item to put back in the queue. False to remove the item from the queue.
73 66 */
@@ -93,13 +86,130 @@
93 86 return false;
94 87 }
95 88
96 89 /**
90 + * Generate the batch key.
91 + * Overrides the vendored method to give priority ("urgent") batches a distinct
92 + * key shape, so get_batches() can order them first.
93 + *
94 + * @since 2.3.2
95 + *
96 + * @param int $length Optional, length of the string to return. Default 64.
97 + * @param string $key Optional, string with a prefix key. Default 'batch'.
98 + * @return string
99 + */
100 + protected function generate_key( $length = 64, $key = 'batch' ) {
101 + if ( 'batch' === $key && $this->has_priority_item_queued() ) {
102 + return parent::generate_key( $length, self::URGENT_KEY_SEGMENT );
103 + }
104 +
105 + return parent::generate_key( $length, $key );
106 + }
107 +
108 + /**
109 + * Tell if any of the not-yet-persisted queued items carries a truthy 'priority' key.
110 + *
111 + * A single truthy item marks the whole batch urgent, which only makes sense because
112 + * a batch is always a singleton at this point: generate_key() is called from
113 + * push_to_queue(), immediately followed by save(), one item at a time. If several
114 + * items were ever pushed to $this->data before save() is called, they would all end
115 + * up sharing the same "urgent" key as soon as one of them requests priority.
116 + *
117 + * @since 2.3.2
118 + *
119 + * @return bool
120 + */
121 + private function has_priority_item_queued(): bool {
122 + if ( empty( $this->data ) || ! is_array( $this->data ) ) {
123 + return false;
124 + }
125 +
126 + foreach ( $this->data as $item ) {
127 + if ( is_array( $item ) && ! empty( $item['priority'] ) ) {
128 + return true;
129 + }
130 + }
131 +
132 + return false;
133 + }
134 +
135 + /**
136 + * Get batches.
137 + * Overrides the vendored method to return "urgent" (priority) batches before
138 + * normal ones, each tier still ordered FIFO (by option/meta ID) internally.
139 + *
140 + * @since 2.3.2
141 + *
142 + * @param int $limit Number of batches to return, defaults to all.
143 + * @return array of stdClass
144 + */
145 + public function get_batches( $limit = 0 ) {
146 + global $wpdb;
147 +
148 + if ( empty( $limit ) || ! is_int( $limit ) ) {
149 + $limit = 0;
150 + }
151 +
152 + $table = $wpdb->options;
153 + $column = 'option_name';
154 + $key_column = 'option_id';
155 + $value_column = 'option_value';
156 +
157 + if ( is_multisite() ) {
158 + $table = $wpdb->sitemeta;
159 + $column = 'meta_key';
160 + $key_column = 'meta_id';
161 + $value_column = 'meta_value';
162 + }
163 +
164 + $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
165 + $urgent_key = $wpdb->esc_like( $this->identifier . '_' . self::URGENT_KEY_SEGMENT . '_' ) . '%';
166 +
167 + $sql = '
168 + SELECT *
169 + FROM ' . $table . '
170 + WHERE ' . $column . ' LIKE %s
171 + ORDER BY ( ' . $column . ' LIKE %s ) DESC, ' . $key_column . ' ASC
172 + ';
173 +
174 + $args = [ $key, $urgent_key ];
175 +
176 + if ( ! empty( $limit ) ) {
177 + $sql .= ' LIMIT %d';
178 + $args[] = $limit;
179 + }
180 +
181 + $items = $wpdb->get_results(
182 + $wpdb->prepare(
183 + $sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
184 + $args
185 + )
186 + );
187 +
188 + $batches = [];
189 +
190 + if ( ! empty( $items ) ) {
191 + $allowed_classes = $this->allowed_batch_data_classes;
192 +
193 + $batches = array_map(
194 + static function ( $item ) use ( $column, $value_column, $allowed_classes ) {
195 + $batch = new \stdClass();
196 + $batch->key = $item->{$column};
197 + $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes );
198 +
199 + return $batch;
200 + },
201 + $items
202 + );
203 + }
204 +
205 + return $batches;
206 + }
207 +
208 + /**
97 209 * Trigger hooks before the optimization job.
98 210 *
99 - * @since 1.9
100 - * @access private
101 - * @author Grégory Viguier
211 + * @since 1.9
102 212 *
103 213 * @param array $item See $this->task().
104 214 * @return array The item.
105 215 */
@@ -106,9 +216,9 @@
106 216 private function task_before( $item ) {
107 217 if ( ! empty( $item['error'] ) && is_wp_error( $item['error'] ) ) {
108 218 $wp_error = $item['error'];
109 219 } else {
110 - $wp_error = new \WP_Error();
220 + $wp_error = new WP_Error();
111 221 }
112 222
113 223 /**
114 224 * Fires before optimizing a media.
@@ -114,17 +224,16 @@
114 224 * Fires before optimizing a media.
115 225 * Any number of files can be optimized, not necessarily all of the media files.
116 226 * If you want to return a WP_Error, use the existing $wp_error object.
117 227 *
118 - * @since 1.9
119 - * @author Grégory Viguier
228 + * @since 1.9
120 229 *
121 - * @param array|\WP_Error $data New data to pass along the item. A \WP_Error object to stop the process.
122 - * @param \WP_Error $wp_error Add errors to this object and return it to stop the process.
230 + * @param array|WP_Error $data New data to pass along the item. A WP_Error object to stop the process.
231 + * @param WP_Error $wp_error Add errors to this object and return it to stop the process.
123 232 * @param ProcessInterface $process The optimization process.
124 233 * @param array $item The item being processed. See $this->task().
125 234 */
126 - $data = apply_filters( 'imagify_before_optimize', [], $wp_error, $this->optimization_process, $item );
235 + $data = apply_filters( 'imagify_before_optimize', [], $wp_error, $this->optimization_process, $item ); // @phpstan-ignore-line
127 236
128 237 if ( is_wp_error( $data ) ) {
129 238 $wp_error = $data;
130 239 } elseif ( $data && is_array( $data ) ) {
@@ -150,17 +259,16 @@
150 259 * Fires before optimizing a media.
151 260 * Any number of files can be optimized, not necessarily all of the media files.
152 261 * If you want to return a WP_Error, use the existing $wp_error object.
153 262 *
154 - * @since 1.9
155 - * @author Grégory Viguier
263 + * @since 1.9
156 264 *
157 - * @param array|\WP_Error $data New data to pass along the item. A \WP_Error object to stop the process.
158 - * @param \WP_Error $wp_error Add errors to this object and return it to stop the process.
265 + * @param array|WP_Error $data New data to pass along the item. A WP_Error object to stop the process.
266 + * @param WP_Error $wp_error Add errors to this object and return it to stop the process.
159 267 * @param ProcessInterface $process The optimization process.
160 268 * @param array $item The item being processed. See $this->task().
161 269 */
162 - $data = apply_filters( "imagify_before_{$hook_suffix}", [], $wp_error, $this->optimization_process, $item );
270 + $data = apply_filters( "imagify_before_{$hook_suffix}", [], $wp_error, $this->optimization_process, $item ); // @phpstan-ignore-line
163 271
164 272 if ( is_wp_error( $data ) ) {
165 273 $wp_error = $data;
166 274 } elseif ( $data && is_array( $data ) ) {
@@ -182,11 +290,9 @@
182 290
183 291 /**
184 292 * Start the optimization job.
185 293 *
186 - * @since 1.9
187 - * @access private
188 - * @author Grégory Viguier
294 + * @since 1.9
189 295 *
190 296 * @param array $item See $this->task().
191 297 * @return array The item.
192 298 */
@@ -210,10 +316,10 @@
210 316 $item['sizes'] = [];
211 317 $item['error'] = $data;
212 318
213 319 } elseif ( 'already_optimized' === $data['status'] ) {
214 - // Status is "already_optimized", try to create WebP versions only.
215 - $item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_webp' ] );
320 + // Status is "already_optimized", try to create next-gen versions only.
321 + $item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_next_gen' ] );
216 322
217 323 } elseif ( 'success' !== $data['status'] ) {
218 324 // Don't go further if the full size has not the "success" status.
219 325 $item['sizes'] = [];
@@ -231,11 +337,9 @@
231 337
232 338 /**
233 339 * Trigger hooks after the optimization job.
234 340 *
235 - * @since 1.9
236 - * @access private
237 - * @author Grégory Viguier
341 + * @since 1.9
238 342 *
239 343 * @param array $item See $this->task().
240 344 * @return array The item.
241 345 */
@@ -247,10 +351,9 @@
247 351 /**
248 352 * Fires after optimizing a media.
249 353 * Any number of files can be optimized, not necessarily all of the media files.
250 354 *
251 - * @since 1.9
252 - * @author Grégory Viguier
355 + * @since 1.9
253 356 *
254 357 * @param ProcessInterface $process The optimization process.
255 358 * @param array $item The item being processed. See $this->task().
256 359 */
@@ -266,10 +369,9 @@
266 369 /**
267 370 * Fires after optimizing a media.
268 371 * Any number of files can be optimized, not necessarily all of the media files.
269 372 *
270 - * @since 1.9
271 - * @author Grégory Viguier
373 + * @since 1.9
272 374 *
273 375 * @param ProcessInterface $process The optimization process.
274 376 * @param array $item The item being processed. See $this->task().
275 377 */
@@ -282,11 +384,9 @@
282 384 /**
283 385 * Validate an item.
284 386 * On success, the property $this->optimization_process is set.
285 387 *
286 - * @since 1.9
287 - * @access protected
288 - * @author Grégory Viguier
388 + * @since 1.9
289 389 *
290 390 * @param array $item See $this->task().
291 391 * @return array|bool The item. False if invalid.
292 392 */
@@ -344,9 +444,9 @@
344 444 if ( empty( $item['task'] ) ) {
345 445 $item['task'] = 'before';
346 446 }
347 447
348 - if ( ! $item['task'] || ! method_exists( $this, 'task_' . $item['task'] ) ) {
448 + if ( ! method_exists( $this, 'task_' . $item['task'] ) ) {
349 449 return false;
350 450 }
351 451
352 452 if ( ! $item['sizes'] && 'after' !== $item['task'] ) {
@@ -363,11 +463,9 @@
363 463
364 464 /**
365 465 * Get the process instance.
366 466 *
367 - * @since 1.9
368 - * @access protected
369 - * @author Grégory Viguier
467 + * @since 1.9
370 468 *
371 469 * @param array $item See $this->task().
372 470 * @return ProcessInterface|bool The instance object on success. False on failure.
373 471 */
@@ -385,11 +483,9 @@
385 483 /**
386 484 * Sanitize and validate an optimization level.
387 485 * If not provided (false, null), fallback to the level set in the plugin's settings.
388 486 *
389 - * @since 1.9
390 - * @access protected
391 - * @author Grégory Viguier
487 + * @since 1.9
392 488 *
393 489 * @param mixed $optimization_level The optimization level.
394 490 * @return int
395 491 */
@@ -394,8 +490,12 @@
394 490 * @return int
395 491 */
396 492 protected function sanitize_optimization_level( $optimization_level ) {
397 493 if ( ! is_numeric( $optimization_level ) ) {
494 + if ( get_imagify_option( 'lossless' ) ) {
495 + return 0;
496 + }
497 +
398 498 return get_imagify_option( 'optimization_level' );
399 499 }
400 500
401 501 return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level );