PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.4
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.4
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.4, at classes/Job/MediaOptimization.php

403 lines 11.0 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['task'] = 'after';
211 $item['error'] = $data;
212 return $item;
213 }
214
215 if ( ! $this->optimization_process->get_data()->is_optimized() ) {
216 // Don't go thurther if the full size has not the "success" status.
217 $item['task'] = 'after';
218 return $item;
219 }
220 }
221
222 if ( ! $item['sizes'] ) {
223 // No more files to optimize.
224 $item['task'] = 'after';
225 }
226
227 // Optimize the next file.
228 return $item;
229 }
230
231 /**
232 * Trigger hooks after the optimization job.
233 *
234 * @since 1.9
235 * @access private
236 * @author Grégory Viguier
237 *
238 * @param array $item See $this->task().
239 * @return array The item.
240 */
241 private function task_after( $item ) {
242 if ( ! empty( $item['data']['delete_backup'] ) ) {
243 $this->optimization_process->delete_backup();
244 }
245
246 /**
247 * Fires after optimizing a media.
248 * Any number of files can be optimized, not necessarily all of the media files.
249 *
250 * @since 1.9
251 * @author Grégory Viguier
252 *
253 * @param ProcessInterface $process The optimization process.
254 * @param array $item The item being processed. See $this->task().
255 */
256 do_action( 'imagify_after_optimize', $this->optimization_process, $item );
257
258 if ( empty( $item['data']['hook_suffix'] ) ) {
259 $item['task'] = false;
260 return $item;
261 }
262
263 $hook_suffix = $item['data']['hook_suffix'];
264
265 /**
266 * Fires after optimizing a media.
267 * Any number of files can be optimized, not necessarily all of the media files.
268 *
269 * @since 1.9
270 * @author Grégory Viguier
271 *
272 * @param ProcessInterface $process The optimization process.
273 * @param array $item The item being processed. See $this->task().
274 */
275 do_action( "imagify_after_{$hook_suffix}", $this->optimization_process, $item );
276
277 $item['task'] = false;
278 return $item;
279 }
280
281 /**
282 * Validate an item.
283 * On success, the property $this->optimization_process is set.
284 *
285 * @since 1.9
286 * @access protected
287 * @author Grégory Viguier
288 *
289 * @param array $item See $this->task().
290 * @return array|bool The item. False if invalid.
291 */
292 protected function validate_item( $item ) {
293 $this->optimization_process = null;
294
295 $default = [
296 'task' => '',
297 'id' => 0,
298 'sizes' => [],
299 'sizes_done' => [],
300 'optimization_level' => null,
301 'process_class' => '',
302 'data' => [],
303 ];
304
305 $item = imagify_merge_intersect( $item, $default );
306
307 // Validate some types first.
308 if ( ! is_array( $item['sizes'] ) ) {
309 return false;
310 }
311
312 if ( isset( $item['error'] ) && ! is_wp_error( $item['error'] ) ) {
313 unset( $item['error'] );
314 }
315
316 if ( isset( $item['data']['hook_suffix'] ) && ! is_string( $item['data']['hook_suffix'] ) ) {
317 unset( $item['data']['hook_suffix'] );
318 }
319
320 $item['id'] = (int) $item['id'];
321 $item['optimization_level'] = $this->sanitize_optimization_level( $item['optimization_level'] );
322
323 if ( ! $item['id'] || ! $item['process_class'] ) {
324 return false;
325 }
326
327 // Process.
328 $item['process_class'] = '\\' . ltrim( $item['process_class'], '\\' );
329
330 if ( ! class_exists( $item['process_class'] ) ) {
331 return false;
332 }
333
334 $process = $this->get_process( $item );
335
336 if ( ! $process ) {
337 return false;
338 }
339
340 $this->optimization_process = $process;
341
342 // Validate the current task.
343 if ( empty( $item['task'] ) ) {
344 $item['task'] = 'before';
345 }
346
347 if ( ! $item['task'] || ! method_exists( $this, 'task_' . $item['task'] ) ) {
348 return false;
349 }
350
351 if ( ! $item['sizes'] && 'after' !== $item['task'] ) {
352 // Allow to have no sizes, but only after the optimize task is complete.
353 return false;
354 }
355
356 if ( ! isset( $item['sizes_done'] ) || ! is_array( $item['sizes_done'] ) ) {
357 $item['sizes_done'] = [];
358 }
359
360 return $item;
361 }
362
363 /**
364 * Get the process instance.
365 *
366 * @since 1.9
367 * @access protected
368 * @author Grégory Viguier
369 *
370 * @param array $item See $this->task().
371 * @return ProcessInterface|bool The instance object on success. False on failure.
372 */
373 protected function get_process( $item ) {
374 $process_class = $item['process_class'];
375 $process = new $process_class( $item['id'] );
376
377 if ( ! $process instanceof ProcessInterface || ! $process->is_valid() ) {
378 return false;
379 }
380
381 return $process;
382 }
383
384 /**
385 * Sanitize and validate an optimization level.
386 * If not provided (false, null), fallback to the level set in the plugin's settings.
387 *
388 * @since 1.9
389 * @access protected
390 * @author Grégory Viguier
391 *
392 * @param mixed $optimization_level The optimization level.
393 * @return int
394 */
395 protected function sanitize_optimization_level( $optimization_level ) {
396 if ( ! is_numeric( $optimization_level ) ) {
397 return get_imagify_option( 'optimization_level' );
398 }
399
400 return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level );
401 }
402 }
403