PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.1
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 / Bulk / Bulk.php

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

578 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Bulk;
3
4 use Exception;
5 use Imagify\Traits\InstanceGetterTrait;
6
7 /**
8 * Bulk optimization
9 */
10 class Bulk {
11 use InstanceGetterTrait;
12
13 /**
14 * Class init: launch hooks.
15 *
16 * @since 2.1
17 */
18 public function init() {
19 add_action( 'imagify_optimize_media', [ $this, 'optimize_media' ], 10, 3 );
20 add_action( 'imagify_convert_webp', [ $this, 'generate_webp_versions' ], 10, 2 );
21 add_action( 'imagify_convert_webp_finished', [ $this, 'clear_webp_transients' ], 10, 2 );
22 add_action( 'wp_ajax_imagify_bulk_optimize', [ $this, 'bulk_optimize_callback' ] );
23 add_action( 'wp_ajax_imagify_missing_webp_generation', [ $this, 'missing_webp_callback' ] );
24 add_action( 'wp_ajax_imagify_get_folder_type_data', [ $this, 'get_folder_type_data_callback' ] );
25 add_action( 'wp_ajax_imagify_bulk_info_seen', [ $this, 'bulk_info_seen_callback' ] );
26 add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] );
27 add_action( 'imagify_after_optimize', [ $this, 'check_optimization_status' ], 10, 2 );
28 add_action( 'imagify_deactivation', [ $this, 'delete_transients_data' ] );
29 }
30
31 /**
32 * Delete transients data on deactivation
33 *
34 * @return void
35 */
36 public function delete_transients_data() {
37 delete_transient( 'imagify_custom-folders_optimize_running' );
38 delete_transient( 'imagify_wp_optimize_running' );
39 delete_transient( 'imagify_bulk_optimization_complete' );
40 delete_transient( 'imagify_missing_webp_total' );
41 }
42
43 /**
44 * Checks bulk optimization status after each optimization task
45 *
46 * @param ProcessInterface $process The optimization process.
47 * @param array $item The item being processed.
48 *
49 * @return void
50 */
51 public function check_optimization_status( $process, $item ) {
52 $custom_folders = get_transient( 'imagify_custom-folders_optimize_running' );
53 $library_wp = get_transient( 'imagify_wp_optimize_running' );
54
55 if (
56 ! $custom_folders
57 &&
58 ! $library_wp
59 ) {
60 return;
61 }
62
63 $data = $process->get_data();
64 $progress = get_transient( 'imagify_bulk_optimization_result' );
65
66 if ( $data->is_optimized() ) {
67 $size_data = $data->get_size_data();
68
69 if ( false === $progress ) {
70 $progress = [
71 'total' => 0,
72 'original_size' => 0,
73 'optimized_size' => 0,
74 ];
75 }
76
77 $progress['total']++;
78
79 $progress['original_size'] += $size_data['original_size'];
80 $progress['optimized_size'] += $size_data['optimized_size'];
81
82 set_transient( 'imagify_bulk_optimization_result', $progress, DAY_IN_SECONDS );
83 }
84
85 $remaining = 0;
86
87 if ( false !== $custom_folders ) {
88 if ( false !== strpos( $item['process_class'], 'CustomFolders' ) ) {
89 $custom_folders['remaining']--;
90
91 set_transient( 'imagify_custom-folders_optimize_running', $custom_folders, DAY_IN_SECONDS );
92
93 $remaining += $custom_folders['remaining'];
94 }
95 }
96
97 if ( false !== $library_wp ) {
98 if ( false !== strpos( $item['process_class'], 'WP' ) ) {
99 $library_wp['remaining']--;
100
101 set_transient( 'imagify_wp_optimize_running', $library_wp, DAY_IN_SECONDS );
102
103 $remaining += $library_wp['remaining'];
104 }
105 }
106
107 if ( 0 >= $remaining ) {
108 delete_transient( 'imagify_custom-folders_optimize_running' );
109 delete_transient( 'imagify_wp_optimize_running' );
110 set_transient( 'imagify_bulk_optimization_complete', 1, DAY_IN_SECONDS );
111 }
112 }
113
114 /**
115 * Decrease optimization running counter for the given context
116 *
117 * @param string $context Context to update.
118 *
119 * @return void
120 */
121 private function decrease_counter( string $context ) {
122 $counter = get_transient( "imagify_{$context}_optimize_running" );
123
124 if ( false === $counter ) {
125 return;
126 }
127
128 $counter['total'] = $counter['total'] - 1;
129 $counter['remaining'] = $counter['remaining'] - 1;
130
131 if (
132 0 === $counter['total']
133 &&
134 0 >= $counter['remaining']
135 ) {
136 delete_transient( "imagify_{$context}_optimize_running" );
137 }
138
139 set_transient( "imagify_{$context}_optimize_running", $counter, DAY_IN_SECONDS );
140 }
141
142 /**
143 * Process a media with the requested imagify bulk action.
144 *
145 * @since 2.1
146 *
147 * @param int $media_id Media ID.
148 * @param string $context Current context.
149 * @param int $optimization_level Optimization level.
150 */
151 public function optimize_media( int $media_id, string $context, int $optimization_level ) {
152 if ( ! $media_id || ! $context || ! is_numeric( $optimization_level ) ) {
153 $this->decrease_counter( $context );
154
155 return;
156 }
157
158 $this->force_optimize( $media_id, $context, $optimization_level );
159 }
160
161 /**
162 * Runs the bulk optimization
163 *
164 * @param string $context Current context (WP/Custom folders).
165 * @param int $optimization_level Optimization level.
166 *
167 * @return array
168 */
169 public function run_optimize( string $context, int $optimization_level ) {
170 if ( ! $this->can_optimize() ) {
171 return [
172 'success' => false,
173 'message' => 'over-quota',
174 ];
175 }
176
177 $media_ids = $this->get_bulk_instance( $context )->get_unoptimized_media_ids( $optimization_level );
178
179 if ( empty( $media_ids ) ) {
180 return [
181 'success' => false,
182 'message' => 'no-images',
183 ];
184 }
185
186 foreach ( $media_ids as $media_id ) {
187 try {
188 as_enqueue_async_action(
189 'imagify_optimize_media',
190 [
191 'id' => $media_id,
192 'context' => $context,
193 'level' => $optimization_level,
194 ],
195 "imagify-{$context}-optimize-media"
196 );
197 } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
198 // nothing to do.
199 }
200 }
201
202 $data = [
203 'total' => count( $media_ids ),
204 'remaining' => count( $media_ids ),
205 ];
206
207 set_transient( "imagify_{$context}_optimize_running", $data, DAY_IN_SECONDS );
208
209 return [
210 'success' => true,
211 'message' => 'success',
212 ];
213 }
214
215 /**
216 * Runs the WebP generation
217 *
218 * @param array $contexts An array of contexts (WP/Custom folders).
219 *
220 * @return array
221 */
222 public function run_generate_webp( array $contexts ) {
223 if ( ! $this->can_optimize() ) {
224 return [
225 'success' => false,
226 'message' => 'over-quota',
227 ];
228 }
229
230 delete_transient( 'imagify_stat_without_webp' );
231
232 $medias = [];
233
234 foreach ( $contexts as $context ) {
235 $media = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_webp();
236
237 if ( ! $media['ids'] && $media['errors']['no_backup'] ) {
238 // No backup, no WebP.
239 return [
240 'success' => false,
241 'message' => 'no-backup',
242 ];
243 } elseif ( ! $media['ids'] && $media['errors']['no_file_path'] ) {
244 // Error.
245 return [
246 'success' => false,
247 'message' => __( 'The path to the selected files could not be retrieved.', 'imagify' ),
248 ];
249 }
250
251 $medias[ $context ] = $media['ids'];
252 }
253
254 if ( empty( $medias ) ) {
255 return [
256 'success' => false,
257 'message' => 'no-images',
258 ];
259 }
260
261 $total = 0;
262
263 foreach ( $medias as $context => $media_ids ) {
264 $total += count( $media_ids );
265
266 foreach ( $media_ids as $media_id ) {
267 try {
268 as_enqueue_async_action(
269 'imagify_convert_webp',
270 [
271 'id' => $media_id,
272 'context' => $context,
273 ],
274 "imagify-{$context}-convert-webp"
275 );
276 } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
277 // nothing to do.
278 }
279 }
280 }
281
282 set_transient( 'imagify_missing_webp_total', $total, HOUR_IN_SECONDS );
283
284 return [
285 'success' => true,
286 'message' => $total,
287 ];
288 }
289
290 /**
291 * Get the Bulk class name depending on a context.
292 *
293 * @since 2.1
294 *
295 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
296 * @return string The Bulk class name.
297 */
298 private function get_bulk_class_name( string $context ): string {
299 switch ( $context ) {
300 case 'wp':
301 $class_name = WP::class;
302 break;
303
304 case 'custom-folders':
305 $class_name = CustomFolders::class;
306 break;
307
308 default:
309 $class_name = Noop::class;
310 }
311
312 /**
313 * Filter the name of the class to use for bulk process.
314 *
315 * @since 1.9
316 *
317 * @param int $class_name The class name.
318 * @param string $context The context name.
319 */
320 $class_name = apply_filters( 'imagify_bulk_class_name', $class_name, $context );
321
322 return '\\' . ltrim( $class_name, '\\' );
323 }
324
325 /**
326 * Get the Bulk instance depending on a context.
327 *
328 * @since 2.1
329 *
330 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
331 *
332 * @return BulkInterface The optimization process instance.
333 */
334 public function get_bulk_instance( string $context ): BulkInterface {
335 $class_name = $this->get_bulk_class_name( $context );
336 return new $class_name();
337 }
338
339 /**
340 * Optimize all files from a media, whatever this media’s previous optimization status (will be restored if needed).
341 * This is used by the bulk optimization page.
342 *
343 * @since 1.9
344 *
345 * @param int $media_id The media ID.
346 * @param string $context The context.
347 * @param int $level The optimization level.
348 *
349 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
350 */
351 private function force_optimize( int $media_id, string $context, int $level ) {
352 if ( ! $this->can_optimize() ) {
353 $this->decrease_counter( $context );
354
355 return false;
356 }
357
358 $process = imagify_get_optimization_process( $media_id, $context );
359 $data = $process->get_data();
360
361 // Restore before re-optimizing.
362 if ( $data->is_optimized() ) {
363 $result = $process->restore();
364
365 if ( is_wp_error( $result ) ) {
366 $this->decrease_counter( $context );
367
368 // Return an error message.
369 return $result;
370 }
371 }
372
373 return $process->optimize( $level );
374 }
375
376 /**
377 * Generate WebP images if they are missing.
378 *
379 * @since 2.1
380 *
381 * @param int $media_id Media ID.
382 * @param string $context Current context.
383 *
384 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
385 */
386 public function generate_webp_versions( int $media_id, string $context ) {
387 if ( ! $this->can_optimize() ) {
388 return false;
389 }
390
391 return imagify_get_optimization_process( $media_id, $context )->generate_webp_versions();
392 }
393
394 /**
395 * Check if the user has a valid account and has quota. Die on failure.
396 *
397 * @since 2.1
398 */
399 public function can_optimize() {
400 if ( ! \Imagify_Requirements::is_api_key_valid() ) {
401 return false;
402 }
403
404 if ( \Imagify_Requirements::is_over_quota() ) {
405 return false;
406 }
407
408 return true;
409 }
410
411 /**
412 * Get the submitted context.
413 *
414 * @since 1.9
415 *
416 * @param string $method The method used: 'GET' (default), or 'POST'.
417 * @param string $parameter The name of the parameter to look for.
418 * @return string
419 */
420 public function get_context( $method = 'GET', $parameter = 'context' ) {
421 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
422 $context = filter_input( $method, $parameter, FILTER_SANITIZE_STRING );
423
424 return imagify_sanitize_context( $context );
425 }
426
427 /**
428 * Get the submitted optimization level.
429 *
430 * @since 1.7
431 * @since 1.9 Added $method and $parameter parameters.
432 * @author Grégory Viguier
433 *
434 * @param string $method The method used: 'GET' (default), or 'POST'.
435 * @param string $parameter The name of the parameter to look for.
436 * @return int
437 */
438 public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) {
439 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
440 $level = filter_input( $method, $parameter );
441
442 if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) {
443 if ( get_imagify_option( 'lossless' ) ) {
444 return 0;
445 }
446
447 return get_imagify_option( 'optimization_level' );
448 }
449
450 return (int) $level;
451 }
452
453 /** ----------------------------------------------------------------------------------------- */
454 /** BULK OPTIMIZATION CALLBACKS ============================================================= */
455 /** ----------------------------------------------------------------------------------------- */
456
457 /**
458 * Launch the bulk optimization action
459 *
460 * @return void
461 */
462 public function bulk_optimize_callback() {
463 imagify_check_nonce( 'imagify-bulk-optimize' );
464
465 $context = $this->get_context();
466 $level = $this->get_optimization_level();
467
468 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
469 imagify_die();
470 }
471
472 $data = $this->run_optimize( $context, $level );
473
474 if ( false === $data['success'] ) {
475 wp_send_json_error( [ 'message' => $data['message'] ] );
476 }
477
478 wp_send_json_success( [ 'total' => $data['message'] ] );
479 }
480
481 /**
482 * Launch the missing WebP versions generation
483 *
484 * @return void
485 */
486 public function missing_webp_callback() {
487 imagify_check_nonce( 'imagify-bulk-optimize' );
488
489 $contexts = explode( '_', sanitize_key( wp_unslash( $_GET['context'] ) ) );
490
491 foreach ( $contexts as $context ) {
492 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
493 imagify_die();
494 }
495 }
496
497 $data = $this->run_generate_webp( $contexts );
498
499 if ( false === $data['success'] ) {
500 wp_send_json_error( [ 'message' => $data['message'] ] );
501 }
502
503 wp_send_json_success( [ 'total' => $data['message'] ] );
504 }
505
506 /**
507 * Get stats data for a specific folder type.
508 *
509 * @since 1.7
510 */
511 public function get_folder_type_data_callback() {
512 imagify_check_nonce( 'imagify-bulk-optimize' );
513
514 $context = $this->get_context();
515
516 if ( ! $context ) {
517 imagify_die( __( 'Invalid request', 'imagify' ) );
518 }
519
520 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
521 imagify_die();
522 }
523
524 $bulk = $this->get_bulk_instance( $context );
525
526 wp_send_json_success( $bulk->get_context_data() );
527 }
528
529 /**
530 * Set the "bulk info" popup state as "seen".
531 *
532 * @since 1.7
533 */
534 public function bulk_info_seen_callback() {
535 imagify_check_nonce( 'imagify-bulk-optimize' );
536
537 $context = $this->get_context();
538
539 if ( ! $context ) {
540 imagify_die( __( 'Invalid request', 'imagify' ) );
541 }
542
543 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
544 imagify_die();
545 }
546
547 set_transient( 'imagify_bulk_optimization_infos', 1, WEEK_IN_SECONDS );
548
549 wp_send_json_success();
550 }
551
552 /**
553 * Get generic stats to display in the bulk page.
554 *
555 * @since 1.7.1
556 */
557 public function bulk_get_stats_callback() {
558 imagify_check_nonce( 'imagify-bulk-optimize' );
559
560 $folder_types = filter_input( INPUT_GET, 'types', FILTER_SANITIZE_STRING, FILTER_REQUIRE_ARRAY );
561 $folder_types = is_array( $folder_types ) ? array_filter( $folder_types, 'is_string' ) : [];
562
563 if ( ! $folder_types ) {
564 imagify_die( __( 'Invalid request', 'imagify' ) );
565 }
566
567 foreach ( $folder_types as $folder_type_data ) {
568 $context = ! empty( $folder_type_data['context'] ) ? $folder_type_data['context'] : 'noop';
569
570 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
571 imagify_die();
572 }
573 }
574
575 wp_send_json_success( imagify_get_bulk_stats( array_flip( $folder_types ) ) );
576 }
577 }
578