PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.8
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.8
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
imagify / classes / Job / MediaOptimization.php

MediaOptimization.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 1.9.8, at classes/Job/MediaOptimization.php

404 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Job;
3
4 use Imagify\Media\MediaInterface;
5 use Imagify\Optimization\File;
6 use Imagify\Optimization\Process\ProcessInterface;
7
8 defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
9
10 /**
11 * Job class for media optimization.
12 *
13 * @since 1.9
14 * @author Grégory Viguier
15 */
16 class MediaOptimization extends \Imagify_Abstract_Background_Process {
17
18 /**
19 * Background process: the action to perform.
20 *
21 * @var string
22 * @since 1.9
23 * @access protected
24 * @author Grégory Viguier
25 */
26 protected $action = 'optimize_media';
27
28 /**
29 * The optimization process instance.
30 *
31 * @var ProcessInterface
32 * @since 1.9
33 * @access protected
34 * @author Grégory Viguier
35 */
36 protected $optimization_process;
37
38 /**
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 * Handle job logic.
50 *
51 * @since 1.9
52 * @access protected
53 * @author Grégory Viguier
54 *
55 * @param array $item {
56 * The data to use for this job.
57 *
58 * @type string $task The task to perform. Optional: set it only if you know what you’re doing.
59 * @type int $id The media ID.
60 * @type array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
61 * @type array $sizes_done Used internally to store the media sizes that have been processed.
62 * @type int $optimization_level The optimization level. Null for the level set in the settings.
63 * @type string $process_class The name of the process class. The class must implement ProcessInterface.
64 * @type array $data {
65 * Can be used to pass any data. Keep it short, don’t forget it will be stored in the database.
66 * It should contain the following though:
67 *
68 * @type string $hook_suffix Suffix used to trigger hooks before and after optimization. Should be always provided.
69 * @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.
70 * }
71 * }
72 * @return array|bool The modified item to put back in the queue. False to remove the item from the queue.
73 */
74 protected function task( $item ) {
75 $item = $this->validate_item( $item );
76
77 if ( ! $item ) {
78 // Not valid.
79 return false;
80 }
81
82 // Launch the task.
83 $method = 'task_' . $item['task'];
84 $item = $this->$method( $item );
85
86 if ( $item['task'] ) {
87 // Next task.
88 return $item;
89 }
90
91 // End of the queue.
92 $this->optimization_process->unlock();
93 return false;
94 }
95
96 /**
97 * Trigger hooks before the optimization job.
98 *
99 * @since 1.9
100 * @access private
101 * @author Grégory Viguier
102 *
103 * @param array $item See $this->task().
104 * @return array The item.
105 */
106 private function task_before( $item ) {
107 if ( ! empty( $item['error'] ) && is_wp_error( $item['error'] ) ) {
108 $wp_error = $item['error'];
109 } else {
110 $wp_error = new \WP_Error();
111 }
112
113 /**
114 * Fires before optimizing a media.
115 * Any number of files can be optimized, not necessarily all of the media files.
116 * If you want to return a WP_Error, use the existing $wp_error object.
117 *
118 * @since 1.9
119 * @author Grégory Viguier
120 *
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.
123 * @param ProcessInterface $process The optimization process.
124 * @param array $item The item being processed. See $this->task().
125 */
126 $data = apply_filters( 'imagify_before_optimize', [], $wp_error, $this->optimization_process, $item );
127
128 if ( is_wp_error( $data ) ) {
129 $wp_error = $data;
130 } elseif ( $data && is_array( $data ) ) {
131 $item['data'] = array_merge( $data, $item['data'] );
132 }
133
134 if ( $wp_error->get_error_codes() ) {
135 // Don't optimize if there is an error.
136 $item['task'] = 'after';
137 $item['error'] = $wp_error;
138 return $item;
139 }
140
141 if ( empty( $item['data']['hook_suffix'] ) ) {
142 // Next task.
143 $item['task'] = 'optimize';
144 return $item;
145 }
146
147 $hook_suffix = $item['data']['hook_suffix'];
148
149 /**
150 * Fires before optimizing a media.
151 * Any number of files can be optimized, not necessarily all of the media files.
152 * If you want to return a WP_Error, use the existing $wp_error object.
153 *
154 * @since 1.9
155 * @author Grégory Viguier
156 *
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.
159 * @param ProcessInterface $process The optimization process.
160 * @param array $item The item being processed. See $this->task().
161 */
162 $data = apply_filters( "imagify_before_{$hook_suffix}", [], $wp_error, $this->optimization_process, $item );
163
164 if ( is_wp_error( $data ) ) {
165 $wp_error = $data;
166 } elseif ( $data && is_array( $data ) ) {
167 $item['data'] = array_merge( $data, $item['data'] );
168 }
169
170 if ( $wp_error->get_error_codes() ) {
171 // Don't optimize if there is an error.
172 $item['task'] = 'after';
173 $item['error'] = $wp_error;
174 return $item;
175 }
176
177 // Next task.
178 $item['task'] = 'optimize';
179
180 return $item;
181 }
182
183 /**
184 * Start the optimization job.
185 *
186 * @since 1.9
187 * @access private
188 * @author Grégory Viguier
189 *
190 * @param array $item See $this->task().
191 * @return array The item.
192 */
193 private function task_optimize( $item ) {
194 // Determine which size we're going to optimize. The 'full' size must be optimized before any other.
195 if ( in_array( 'full', $item['sizes'], true ) ) {
196 $current_size = 'full';
197 $item['sizes'] = array_diff( $item['sizes'], [ 'full' ] );
198 } else {
199 $current_size = array_shift( $item['sizes'] );
200 }
201
202 $item['sizes_done'][] = $current_size;
203
204 // Optimize the file.
205 $data = $this->optimization_process->optimize_size( $current_size, $item['optimization_level'] );
206
207 if ( 'full' === $current_size ) {
208 if ( is_wp_error( $data ) ) {
209 // Don't go further if there is an error.
210 $item['sizes'] = [];
211 $item['error'] = $data;
212
213 } 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' ] );
216
217 } elseif ( 'success' !== $data['status'] ) {
218 // Don't go further if the full size has not the "success" status.
219 $item['sizes'] = [];
220 }
221 }
222
223 if ( ! $item['sizes'] ) {
224 // No more files to optimize.
225 $item['task'] = 'after';
226 }
227
228 // Optimize the next file or go to the next task.
229 return $item;
230 }
231
232 /**
233 * Trigger hooks after the optimization job.
234 *
235 * @since 1.9
236 * @access private
237 * @author Grégory Viguier
238 *
239 * @param array $item See $this->task().
240 * @return array The item.
241 */
242 private function task_after( $item ) {
243 if ( ! empty( $item['data']['delete_backup'] ) ) {
244 $this->optimization_process->delete_backup();
245 }
246
247 /**
248 * Fires after optimizing a media.
249 * Any number of files can be optimized, not necessarily all of the media files.
250 *
251 * @since 1.9
252 * @author Grégory Viguier
253 *
254 * @param ProcessInterface $process The optimization process.
255 * @param array $item The item being processed. See $this->task().
256 */
257 do_action( 'imagify_after_optimize', $this->optimization_process, $item );
258
259 if ( empty( $item['data']['hook_suffix'] ) ) {
260 $item['task'] = false;
261 return $item;
262 }
263
264 $hook_suffix = $item['data']['hook_suffix'];
265
266 /**
267 * Fires after optimizing a media.
268 * Any number of files can be optimized, not necessarily all of the media files.
269 *
270 * @since 1.9
271 * @author Grégory Viguier
272 *
273 * @param ProcessInterface $process The optimization process.
274 * @param array $item The item being processed. See $this->task().
275 */
276 do_action( "imagify_after_{$hook_suffix}", $this->optimization_process, $item );
277
278 $item['task'] = false;
279 return $item;
280 }
281
282 /**
283 * Validate an item.
284 * On success, the property $this->optimization_process is set.
285 *
286 * @since 1.9
287 * @access protected
288 * @author Grégory Viguier
289 *
290 * @param array $item See $this->task().
291 * @return array|bool The item. False if invalid.
292 */
293 protected function validate_item( $item ) {
294 $this->optimization_process = null;
295
296 $default = [
297 'task' => '',
298 'id' => 0,
299 'sizes' => [],
300 'sizes_done' => [],
301 'optimization_level' => null,
302 'process_class' => '',
303 'data' => [],
304 ];
305
306 $item = imagify_merge_intersect( $item, $default );
307
308 // Validate some types first.
309 if ( ! is_array( $item['sizes'] ) ) {
310 return false;
311 }
312
313 if ( isset( $item['error'] ) && ! is_wp_error( $item['error'] ) ) {
314 unset( $item['error'] );
315 }
316
317 if ( isset( $item['data']['hook_suffix'] ) && ! is_string( $item['data']['hook_suffix'] ) ) {
318 unset( $item['data']['hook_suffix'] );
319 }
320
321 $item['id'] = (int) $item['id'];
322 $item['optimization_level'] = $this->sanitize_optimization_level( $item['optimization_level'] );
323
324 if ( ! $item['id'] || ! $item['process_class'] ) {
325 return false;
326 }
327
328 // Process.
329 $item['process_class'] = '\\' . ltrim( $item['process_class'], '\\' );
330
331 if ( ! class_exists( $item['process_class'] ) ) {
332 return false;
333 }
334
335 $process = $this->get_process( $item );
336
337 if ( ! $process ) {
338 return false;
339 }
340
341 $this->optimization_process = $process;
342
343 // Validate the current task.
344 if ( empty( $item['task'] ) ) {
345 $item['task'] = 'before';
346 }
347
348 if ( ! $item['task'] || ! method_exists( $this, 'task_' . $item['task'] ) ) {
349 return false;
350 }
351
352 if ( ! $item['sizes'] && 'after' !== $item['task'] ) {
353 // Allow to have no sizes, but only after the optimize task is complete.
354 return false;
355 }
356
357 if ( ! isset( $item['sizes_done'] ) || ! is_array( $item['sizes_done'] ) ) {
358 $item['sizes_done'] = [];
359 }
360
361 return $item;
362 }
363
364 /**
365 * Get the process instance.
366 *
367 * @since 1.9
368 * @access protected
369 * @author Grégory Viguier
370 *
371 * @param array $item See $this->task().
372 * @return ProcessInterface|bool The instance object on success. False on failure.
373 */
374 protected function get_process( $item ) {
375 $process_class = $item['process_class'];
376 $process = new $process_class( $item['id'] );
377
378 if ( ! $process instanceof ProcessInterface || ! $process->is_valid() ) {
379 return false;
380 }
381
382 return $process;
383 }
384
385 /**
386 * Sanitize and validate an optimization level.
387 * If not provided (false, null), fallback to the level set in the plugin's settings.
388 *
389 * @since 1.9
390 * @access protected
391 * @author Grégory Viguier
392 *
393 * @param mixed $optimization_level The optimization level.
394 * @return int
395 */
396 protected function sanitize_optimization_level( $optimization_level ) {
397 if ( ! is_numeric( $optimization_level ) ) {
398 return get_imagify_option( 'optimization_level' );
399 }
400
401 return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level );
402 }
403 }
404