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

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