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
imagify / classes / Bulk / Bulk.php

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

919 lines 24.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 use Imagify\Optimization\Process\ProcessInterface;
7 use WP_Error;
8
9 /**
10 * Bulk optimization
11 */
12 final class Bulk implements BulkOptimizerInterface {
13 use InstanceGetterTrait;
14
15 /**
16 * Class init: launch hooks.
17 *
18 * @since 2.1
19 */
20 public function init() {
21 add_action( 'imagify_optimize_media', [ $this, 'optimize_media' ], 10, 3 );
22 add_action( 'imagify_convert_next_gen', [ $this, 'generate_nextgen_versions' ], 10, 2 ); // @phpstan-ignore-line
23 add_action( 'wp_ajax_imagify_bulk_optimize', [ $this, 'bulk_optimize_callback' ] );
24 add_action( 'wp_ajax_imagify_bulk_stop', [ $this, 'bulk_stop_callback' ] );
25 add_action( 'wp_ajax_imagify_missing_nextgen_generation', [ $this, 'missing_nextgen_callback' ] );
26 add_action( 'wp_ajax_imagify_get_folder_type_data', [ $this, 'get_folder_type_data_callback' ] );
27 add_action( 'wp_ajax_imagify_bulk_info_seen', [ $this, 'bulk_info_seen_callback' ] );
28 add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] );
29 add_action( 'imagify_after_optimize', [ $this, 'check_optimization_status' ], 10, 2 );
30 add_action( 'imagify_deactivation', [ $this, 'delete_transients_data' ] );
31 add_action( 'update_option_imagify_settings', [ $this, 'maybe_generate_missing_nextgen' ], 10, 2 );
32 add_action( 'imagify_upgrade', [ $this, 'maybe_clear_stale_bulk_run' ], 10, 2 );
33 }
34
35 /**
36 * Clear any bulk optimization progress left running from before 2.3.2, on upgrade.
37 *
38 * Queue items enqueued by a bulk run before 2.3.2 don't carry the `bulk` flag used by
39 * self::check_optimization_status() to tell bulk completions from manual/auto ones (see
40 * that method). If a bulk run straddles the upgrade, its remaining items complete without
41 * that flag and are never counted, so the run's `remaining` counter never reaches zero and
42 * the bulk page is stuck showing it as still running. Clearing the running transients lets
43 * a fresh bulk run start clean instead.
44 *
45 * @since 2.3.2
46 *
47 * @param string $network_version Previous version stored on the network.
48 * @param string $site_version Previous version stored on site level.
49 *
50 * @return void
51 */
52 public function maybe_clear_stale_bulk_run( $network_version, $site_version ) {
53 if ( version_compare( $site_version, '2.3.2' ) >= 0 ) {
54 return;
55 }
56
57 delete_transient( 'imagify_custom-folders_optimize_running' );
58 delete_transient( 'imagify_wp_optimize_running' );
59 delete_transient( 'imagify_bulk_optimization_result' );
60 delete_transient( 'imagify_bulk_optimization_complete' );
61 }
62
63 /**
64 * Delete transients data on deactivation
65 *
66 * @return void
67 */
68 public function delete_transients_data() {
69 delete_transient( 'imagify_custom-folders_optimize_running' );
70 delete_transient( 'imagify_wp_optimize_running' );
71 delete_transient( 'imagify_bulk_optimization_complete' );
72 delete_transient( 'imagify_missing_next_gen_total' );
73 }
74
75 /**
76 * Checks bulk optimization status after each optimization task
77 *
78 * @param ProcessInterface $process The optimization process.
79 * @param array $item The item being processed.
80 *
81 * @return void
82 */
83 public function check_optimization_status( $process, $item ) {
84 $custom_folders = get_transient( 'imagify_custom-folders_optimize_running' );
85 $library_wp = get_transient( 'imagify_wp_optimize_running' );
86
87 if (
88 ! $custom_folders
89 &&
90 ! $library_wp
91 ) {
92 return;
93 }
94
95 if ( empty( $item['data']['bulk'] ) ) {
96 // This completion was not enqueued by Bulk::run_optimize() (e.g. a manual click or
97 // an auto-optimize-on-upload that happens to complete while a bulk job is active):
98 // do not let it affect the bulk job's progress counters.
99 return;
100 }
101
102 $data = $process->get_data();
103
104 if ( ! $data ) {
105 return;
106 }
107
108 $progress = get_transient( 'imagify_bulk_optimization_result' );
109
110 if ( $data->is_optimized() ) {
111 $size_data = $data->get_size_data();
112
113 if ( false === $progress ) {
114 $progress = [
115 'total' => 0,
116 'original_size' => 0,
117 'optimized_size' => 0,
118 ];
119 }
120
121 ++$progress['total'];
122
123 $progress['original_size'] += $size_data['original_size'];
124 $progress['optimized_size'] += $size_data['optimized_size'];
125
126 set_transient( 'imagify_bulk_optimization_result', $progress, DAY_IN_SECONDS );
127 }
128
129 $remaining = 0;
130
131 if ( false !== $custom_folders ) {
132 if ( false !== strpos( $item['process_class'], 'CustomFolders' ) ) {
133 --$custom_folders['remaining'];
134
135 set_transient( 'imagify_custom-folders_optimize_running', $custom_folders, DAY_IN_SECONDS );
136
137 $remaining += $custom_folders['remaining'];
138 }
139 }
140
141 if ( false !== $library_wp ) {
142 if ( false !== strpos( $item['process_class'], 'WP' ) ) {
143 --$library_wp['remaining'];
144
145 set_transient( 'imagify_wp_optimize_running', $library_wp, DAY_IN_SECONDS );
146
147 $remaining += $library_wp['remaining'];
148 }
149 }
150
151 if ( 0 >= $remaining ) {
152 delete_transient( 'imagify_custom-folders_optimize_running' );
153 delete_transient( 'imagify_wp_optimize_running' );
154 set_transient( 'imagify_bulk_optimization_complete', 1, DAY_IN_SECONDS );
155 }
156 }
157
158 /**
159 * Decrease optimization running counter for the given context
160 *
161 * @param string $context Context to update.
162 *
163 * @return void
164 */
165 private function decrease_counter( string $context ) {
166 $counter = get_transient( "imagify_{$context}_optimize_running" );
167
168 if ( false === $counter ) {
169 return;
170 }
171
172 $counter['total'] = $counter['total'] - 1;
173 $counter['remaining'] = $counter['remaining'] - 1;
174
175 if (
176 0 === $counter['total']
177 &&
178 0 >= $counter['remaining']
179 ) {
180 delete_transient( "imagify_{$context}_optimize_running" );
181 }
182
183 set_transient( "imagify_{$context}_optimize_running", $counter, DAY_IN_SECONDS );
184 }
185
186 /**
187 * Process a media with the requested imagify bulk action.
188 *
189 * @since 2.1
190 *
191 * @param int $media_id Media ID.
192 * @param string $context Current context.
193 * @param int $optimization_level Optimization level.
194 */
195 public function optimize_media( int $media_id, string $context, int $optimization_level ) {
196 if ( ! $media_id || ! $context ) {
197 $this->decrease_counter( $context );
198
199 return;
200 }
201
202 $this->force_optimize( $media_id, $context, $optimization_level );
203 }
204
205 /**
206 * Runs the bulk optimization
207 *
208 * @param string $context Current context (WP/Custom folders).
209 * @param int $optimization_level Optimization level.
210 *
211 * @return array
212 */
213 public function run_optimize( string $context, int $optimization_level ): array {
214 if ( ! $this->can_optimize() ) {
215 return [
216 'success' => false,
217 'message' => 'over-quota',
218 ];
219 }
220
221 $media_ids = $this->get_bulk_instance( $context )->get_unoptimized_media_ids( $optimization_level );
222
223 $media_ids = array_values(
224 array_filter(
225 $media_ids,
226 function ( $media_id ) use ( $context, $optimization_level ) {
227 /**
228 * Filter the list of media to optimize during a bulk optimization, per media.
229 *
230 * Return false to exclude a given media from the bulk optimization run
231 * (e.g. to skip a specific file type such as PDFs).
232 *
233 * @since 2.3
234 *
235 * @param bool $optimize True to optimize this media, false to exclude it.
236 * @param int $media_id The media ID (attachment ID, or custom-folder file ID).
237 * @param string $context The optimization context ('wp' or 'custom-folders').
238 * @param int $optimization_level The optimization level.
239 */
240 return wpm_apply_filters_typed( 'boolean', 'imagify_bulk_optimize_media', true, $media_id, $context, $optimization_level );
241 }
242 )
243 );
244
245 if ( empty( $media_ids ) ) {
246 return [
247 'success' => false,
248 'message' => 'no-images',
249 ];
250 }
251
252 foreach ( $media_ids as $media_id ) {
253 try {
254 as_enqueue_async_action(
255 'imagify_optimize_media',
256 [
257 'id' => $media_id,
258 'context' => $context,
259 'level' => $optimization_level,
260 ],
261 "imagify-{$context}-optimize-media"
262 );
263 } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
264 // nothing to do.
265 }
266 }
267
268 $data = [
269 'total' => count( $media_ids ),
270 'remaining' => count( $media_ids ),
271 ];
272
273 set_transient( "imagify_{$context}_optimize_running", $data, DAY_IN_SECONDS );
274
275 return [
276 'success' => true,
277 'message' => 'success',
278 ];
279 }
280
281 /**
282 * Stops a running bulk process for the given contexts.
283 *
284 * Cancels every media still waiting in the queue and clears the progress data. Media already
285 * sent to the Imagify API cannot be cancelled: the one being processed when the stop is
286 * requested still completes.
287 *
288 * @since 2.3
289 *
290 * @param array $contexts An array of contexts (WP/Custom folders).
291 *
292 * @return array {
293 * @type bool $success Whether a running process was found.
294 * @type string $message Status message.
295 * @type int $cancelled Number of queued media actually cancelled.
296 * }
297 */
298 public function run_stop( array $contexts ): array {
299 $cancelled = 0;
300 $was_running = false;
301
302 foreach ( $contexts as $context ) {
303 if ( false !== get_transient( "imagify_{$context}_optimize_running" ) ) {
304 $was_running = true;
305 }
306
307 $cancelled += $this->cancel_pending_actions( 'imagify_optimize_media', "imagify-{$context}-optimize-media" );
308 $cancelled += $this->cancel_pending_actions( 'imagify_convert_next_gen', "imagify-{$context}-convert-nextgen" );
309
310 delete_transient( "imagify_{$context}_optimize_running" );
311 }
312
313 delete_transient( 'imagify_bulk_optimization_result' );
314 delete_transient( 'imagify_missing_next_gen_total' );
315
316 if ( ! $was_running && 0 === $cancelled ) {
317 return [
318 'success' => false,
319 'message' => 'not-running',
320 'cancelled' => 0,
321 ];
322 }
323
324 /**
325 * Fires after a bulk process has been manually stopped.
326 *
327 * @since 2.3
328 *
329 * @param array $contexts The contexts that were stopped.
330 * @param int $cancelled Number of queued media actually cancelled.
331 */
332 do_action( 'imagify_bulk_stopped', $contexts, $cancelled );
333
334 return [
335 'success' => true,
336 'message' => 'success',
337 'cancelled' => $cancelled,
338 ];
339 }
340
341 /**
342 * Cancel all the pending actions for a given hook and group.
343 *
344 * Actions already running are left untouched: Action Scheduler cannot interrupt them.
345 *
346 * The pending actions are counted before being cancelled, so the caller reports what was
347 * really removed from the queue. The `remaining` value of the progress transient cannot be
348 * used for that: it is only decremented by the `imagify_after_optimize` hook, which never
349 * fires when an optimization bails out early (unsupported media, already optimized, locked
350 * process) or when the request dies, so it drifts above the real queue size.
351 *
352 * @since 2.3
353 *
354 * @param string $hook The action hook.
355 * @param string $group The Action Scheduler group.
356 *
357 * @return int Number of pending actions that were cancelled.
358 */
359 private function cancel_pending_actions( string $hook, string $group ): int {
360 if ( ! function_exists( 'as_unschedule_all_actions' ) || ! function_exists( 'as_get_scheduled_actions' ) ) {
361 return 0;
362 }
363
364 try {
365 $pending = as_get_scheduled_actions(
366 [
367 'hook' => $hook,
368 'group' => $group,
369 // ActionScheduler_Store::STATUS_PENDING, without depending on the class being loaded.
370 'status' => 'pending',
371 'per_page' => -1,
372 'orderby' => 'none',
373 ],
374 'ids'
375 );
376
377 as_unschedule_all_actions( $hook, [], $group );
378 } catch ( Exception $exception ) {
379 return 0;
380 }
381
382 return is_array( $pending ) ? count( $pending ) : 0;
383 }
384
385 /**
386 * Runs the bulk restore for a given context.
387 *
388 * Restores all optimized media to their original state synchronously.
389 * Does not consume API quota since restore is a local file operation.
390 *
391 * @since 2.3
392 *
393 * @param string $context Current context (WP/Custom folders).
394 *
395 * @return array {
396 * @type bool $success Whether any media was found to restore.
397 * @type string $message Status message.
398 * @type int $restored Number of media successfully restored.
399 * @type int $errors Number of media that failed to restore.
400 * @type int $total Total number of media processed.
401 * }
402 */
403 public function run_restore( string $context ) {
404 $media_ids = $this->get_bulk_instance( $context )->get_optimized_media_ids();
405
406 if ( empty( $media_ids ) ) {
407 return [
408 'success' => false,
409 'message' => 'no-images',
410 'restored' => 0,
411 'errors' => 0,
412 'total' => 0,
413 ];
414 }
415
416 $restored = 0;
417 $errors = 0;
418
419 foreach ( $media_ids as $media_id ) {
420 $result = imagify_get_optimization_process( $media_id, $context )->restore();
421
422 if ( is_wp_error( $result ) ) {
423 ++$errors;
424 continue;
425 }
426
427 ++$restored;
428 }
429
430 return [
431 'success' => true,
432 'message' => 'success',
433 'restored' => $restored,
434 'errors' => $errors,
435 'total' => count( $media_ids ),
436 ];
437 }
438
439 /**
440 * Runs the next-gen generation
441 *
442 * @param array $contexts An array of contexts (WP/Custom folders).
443 * @param array $formats An array of format to generate.
444 *
445 * @return array
446 */
447 public function run_generate_nextgen( array $contexts, array $formats ) {
448 if ( ! $this->can_optimize() ) {
449 return [
450 'success' => false,
451 'message' => 'over-quota',
452 ];
453 }
454
455 delete_transient( 'imagify_stat_without_next_gen' );
456
457 $medias = [];
458
459 foreach ( $contexts as $context ) {
460 foreach ( $formats as $format ) {
461 $media = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_format( $format );
462
463 if ( ! $media['ids'] && $media['errors']['no_file_path'] ) {
464 // Error.
465 return [
466 'success' => false,
467 'message' => __( 'The path to the selected files could not be retrieved.', 'imagify' ),
468 ];
469 }
470
471 if ( $media['ids'] ) {
472 $medias[ $context ] = $media['ids'];
473 }
474 }
475 }
476
477 if ( empty( $medias ) ) {
478 return [
479 'success' => false,
480 'message' => 'no-images',
481 ];
482 }
483
484 $total = 0;
485
486 foreach ( $medias as $context => $media_ids ) {
487 $total += count( $media_ids );
488
489 foreach ( $media_ids as $media_id ) {
490 try {
491 as_enqueue_async_action(
492 'imagify_convert_next_gen',
493 [
494 'id' => $media_id,
495 'context' => $context,
496 ],
497 "imagify-{$context}-convert-nextgen"
498 );
499 } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
500 // nothing to do.
501 }
502 }
503 }
504
505 set_transient( 'imagify_missing_next_gen_total', $total, HOUR_IN_SECONDS );
506
507 return [
508 'success' => true,
509 'message' => $total,
510 ];
511 }
512
513 /**
514 * Get the Bulk class name depending on a context.
515 *
516 * @since 2.1
517 *
518 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
519 * @return string The Bulk class name.
520 */
521 private function get_bulk_class_name( string $context ): string {
522 switch ( $context ) {
523 case 'wp':
524 $class_name = WP::class;
525 break;
526
527 case 'custom-folders':
528 $class_name = CustomFolders::class;
529 break;
530
531 default:
532 $class_name = Noop::class;
533 }
534
535 /**
536 * Filter the name of the class to use for bulk process.
537 *
538 * @since 1.9
539 *
540 * @param string $class_name The class name.
541 * @param string $context The context name.
542 */
543 $class_name = wpm_apply_filters_typed( 'string', 'imagify_bulk_class_name', $class_name, $context );
544
545 return '\\' . ltrim( $class_name, '\\' );
546 }
547
548 /**
549 * Get the Bulk instance depending on a context.
550 *
551 * @since 2.1
552 *
553 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
554 *
555 * @return BulkInterface The optimization process instance.
556 */
557 public function get_bulk_instance( string $context ): BulkInterface {
558 $class_name = $this->get_bulk_class_name( $context );
559 return new $class_name();
560 }
561
562 /**
563 * Optimize all files from a media, whatever this media’s previous optimization status (will be restored if needed).
564 * This is used by the bulk optimization page.
565 *
566 * @since 1.9
567 *
568 * @param int $media_id The media ID.
569 * @param string $context The context.
570 * @param int $level The optimization level.
571 *
572 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
573 */
574 private function force_optimize( int $media_id, string $context, int $level ) {
575 if ( ! $this->can_optimize() ) {
576 $this->decrease_counter( $context );
577
578 return false;
579 }
580
581 $process = imagify_get_optimization_process( $media_id, $context );
582 $data = $process->get_data();
583
584 // Restore before re-optimizing.
585 if ( $data->is_optimized() ) {
586 $result = $process->restore();
587
588 if ( is_wp_error( $result ) ) {
589 $this->decrease_counter( $context );
590
591 // Return an error message.
592 return $result;
593 }
594 }
595
596 return $process->optimize( $level, [ 'bulk' => true ] );
597 }
598
599 /**
600 * Generate next-gen images if they are missing.
601 *
602 * @since 2.1
603 *
604 * @param int $media_id Media ID.
605 * @param string $context Current context.
606 *
607 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
608 */
609 public function generate_nextgen_versions( int $media_id, string $context ) {
610 if ( ! $this->can_optimize() ) {
611 return false;
612 }
613
614 return imagify_get_optimization_process( $media_id, $context )->generate_nextgen_versions();
615 }
616
617 /**
618 * Check if the user has a valid account and has quota. Die on failure.
619 *
620 * @since 2.1
621 */
622 public function can_optimize() {
623 if ( ! \Imagify_Requirements::is_api_key_valid() ) {
624 return false;
625 }
626
627 if ( \Imagify_Requirements::is_over_quota() ) {
628 return false;
629 }
630
631 return true;
632 }
633
634 /**
635 * Get the submitted context.
636 *
637 * @since 1.9
638 *
639 * @param string $method The method used: 'GET' (default), or 'POST'.
640 * @param string $parameter The name of the parameter to look for.
641 *
642 * @return string
643 */
644 public function get_context( $method = 'GET', $parameter = 'context' ) {
645 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
646 // No context.
647 return 'noop';
648 }
649
650 $context = 'POST' === $method ? sanitize_text_field( wp_unslash( $_POST[ $parameter ] ) ) : sanitize_text_field( wp_unslash( $_GET[ $parameter ] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
651
652 return imagify_sanitize_context( $context );
653 }
654
655 /**
656 * Get the submitted optimization level.
657 *
658 * @since 1.7
659 * @since 1.9 Added $method and $parameter parameters.
660 * @author Grégory Viguier
661 *
662 * @param string $method The method used: 'GET' (default), or 'POST'.
663 * @param string $parameter The name of the parameter to look for.
664 * @return int
665 */
666 public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) {
667 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
668 $level = filter_input( $method, $parameter );
669
670 if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) {
671 if ( get_imagify_option( 'lossless' ) ) {
672 return 0;
673 }
674
675 return get_imagify_option( 'optimization_level' );
676 }
677
678 return (int) $level;
679 }
680
681 /**
682 * Launch the bulk optimization action
683 *
684 * @return void
685 */
686 public function bulk_optimize_callback() {
687 imagify_check_nonce( 'imagify-bulk-optimize' );
688
689 $context = $this->get_context();
690 $level = $this->get_optimization_level();
691
692 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
693 imagify_die();
694 }
695
696 $data = $this->run_optimize( $context, $level );
697
698 if ( false === $data['success'] ) {
699 wp_send_json_error( [ 'message' => $data['message'] ] );
700 }
701
702 wp_send_json_success( [ 'total' => $data['message'] ] );
703 }
704
705 /**
706 * Stop the running bulk optimization
707 *
708 * @since 2.3
709 *
710 * @return void
711 */
712 public function bulk_stop_callback() {
713 imagify_check_nonce( 'imagify-bulk-optimize' );
714
715 $contexts = $this->get_contexts();
716
717 foreach ( $contexts as $context ) {
718 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
719 imagify_die();
720 }
721 }
722
723 $data = $this->run_stop( $contexts );
724
725 if ( false === $data['success'] ) {
726 wp_send_json_error( [ 'message' => $data['message'] ] );
727 }
728
729 wp_send_json_success( [ 'cancelled' => $data['cancelled'] ] );
730 }
731
732 /**
733 * Launch the missing Next-gen versions generation
734 *
735 * @return void
736 */
737 public function missing_nextgen_callback() {
738 imagify_check_nonce( 'imagify-bulk-optimize' );
739
740 $contexts = $this->get_contexts();
741
742 foreach ( $contexts as $context ) {
743 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
744 imagify_die();
745 }
746 }
747
748 $formats = imagify_nextgen_images_formats();
749
750 $data = $this->run_generate_nextgen( $contexts, $formats );
751 if ( false === $data['success'] ) {
752 wp_send_json_error( [ 'message' => $data['message'] ] );
753 }
754
755 wp_send_json_success( [ 'total' => $data['message'] ] );
756 }
757
758 /**
759 * Get stats data for a specific folder type.
760 *
761 * @since 1.7
762 */
763 public function get_folder_type_data_callback() {
764 imagify_check_nonce( 'imagify-bulk-optimize' );
765
766 $context = $this->get_context();
767
768 if ( ! $context ) {
769 imagify_die( __( 'Invalid request', 'imagify' ) );
770 }
771
772 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
773 imagify_die();
774 }
775
776 $bulk = $this->get_bulk_instance( $context );
777
778 wp_send_json_success( $bulk->get_context_data() );
779 }
780
781 /**
782 * Set the "bulk info" popup state as "seen".
783 *
784 * @since 1.7
785 */
786 public function bulk_info_seen_callback() {
787 imagify_check_nonce( 'imagify-bulk-optimize' );
788
789 $context = $this->get_context();
790
791 if ( ! $context ) {
792 imagify_die( __( 'Invalid request', 'imagify' ) );
793 }
794
795 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
796 imagify_die();
797 }
798
799 set_transient( 'imagify_bulk_optimization_infos', 1, WEEK_IN_SECONDS );
800
801 wp_send_json_success();
802 }
803
804 /**
805 * Get generic stats to display in the bulk page.
806 *
807 * @since 1.7.1
808 */
809 public function bulk_get_stats_callback() {
810 imagify_check_nonce( 'imagify-bulk-optimize' );
811
812 $folder_types = filter_input( INPUT_GET, 'types', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
813 $folder_types = is_array( $folder_types ) ? $folder_types : [];
814
815 if ( ! $folder_types ) {
816 imagify_die( __( 'Invalid request', 'imagify' ) );
817 }
818
819 foreach ( $folder_types as $folder_type_data ) {
820 $context = ! empty( $folder_type_data['context'] ) ? $folder_type_data['context'] : 'noop';
821
822 if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
823 imagify_die();
824 }
825 }
826
827 wp_send_json_success( imagify_get_bulk_stats( array_flip( $folder_types ) ) );
828 }
829
830 /**
831 * Update Options callback to start bulk optimization.
832 *
833 * @since 2.2
834 *
835 * @param array $old_value The old option value.
836 * @param array $value The new option value.
837 *
838 * @return void
839 */
840 public function maybe_generate_missing_nextgen( $old_value, $value ) {
841 if ( ! isset( $old_value['optimization_format'], $value['optimization_format'] ) ) {
842 return;
843 }
844
845 if ( $old_value['optimization_format'] === $value['optimization_format'] ) {
846 // Old value = new value so do nothing.
847 return;
848 }
849
850 if ( 'off' === $value['optimization_format'] ) {
851 // No need to generate next-gen images.
852 return;
853 }
854
855 $contexts = $this->get_contexts();
856 $formats = imagify_nextgen_images_formats();
857
858 $this->run_generate_nextgen( $contexts, $formats );
859 }
860
861 /**
862 * Get the context for the bulk optimization page.
863 *
864 * @since 2.2
865 *
866 * @return array The array of unique contexts ('wp' or 'custom-folders').
867 */
868 public function get_contexts() {
869 $contexts = [];
870 $types = [];
871
872 // Library: in each site.
873 if ( ! is_network_admin() ) {
874 $types['library|wp'] = 1;
875 }
876
877 // Custom folders: in network admin only if network activated, in each site otherwise.
878 if (
879 imagify_can_optimize_custom_folders()
880 &&
881 (
882 ( imagify_is_active_for_network() && is_network_admin() )
883 ||
884 ! imagify_is_active_for_network()
885 )
886 ) {
887 $types['custom-folders|custom-folders'] = 1;
888 }
889
890 /**
891 * Filter the types to display in the bulk optimization page.
892 *
893 * @since 1.7.1
894 *
895 * @param array $types The folder types displayed on the page. If a folder type is "library", the context should be suffixed after a pipe character. They are passed as array keys.
896 */
897 $types = wpm_apply_filters_typed( 'array', 'imagify_bulk_page_types', $types );
898 $types = array_filter( (array) $types );
899
900 if ( isset( $types['library|wp'] ) ) {
901 $contexts[] = 'wp';
902 }
903
904 if ( isset( $types['custom-folders|custom-folders'] ) ) {
905 $folders_instance = \Imagify_Folders_DB::get_instance();
906
907 if ( ! $folders_instance->has_items() ) {
908 if ( ! in_array( 'wp', $contexts, true ) ) {
909 $contexts[] = 'wp';
910 }
911 } elseif ( $folders_instance->has_active_folders() ) {
912 $contexts[] = 'custom-folders';
913 }
914 }
915
916 return $contexts;
917 }
918 }
919