PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.3
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.3
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 / inc / classes / class-imagify-admin-ajax-post.php

class-imagify-admin-ajax-post.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.3.3, at inc/classes/class-imagify-admin-ajax-post.php

1,388 lines 39.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Imagify\Traits\InstanceGetterTrait;
4
5 /**
6 * Class that handles admin ajax/post callbacks.
7 *
8 * @since 1.6.11
9 */
10 class Imagify_Admin_Ajax_Post extends Imagify_Admin_Ajax_Post_Deprecated {
11 use InstanceGetterTrait;
12
13 /**
14 * Class version.
15 *
16 * @var string
17 */
18 const VERSION = '1.1';
19
20 /**
21 * Actions to be triggered on admin ajax and admin post.
22 *
23 * @var array
24 */
25 protected $ajax_post_actions = [
26 // WP optimization.
27 'imagify_manual_optimize',
28 'imagify_manual_reoptimize',
29 'imagify_optimize_missing_sizes',
30 'imagify_generate_nextgen_versions',
31 'imagify_delete_nextgen_versions',
32 'imagify_restore',
33 // Custom folders optimization.
34 'imagify_optimize_file',
35 'imagify_reoptimize_file',
36 'imagify_restore_file',
37 'imagify_refresh_file_modified',
38 ];
39
40 /**
41 * Actions to be triggered only on admin ajax.
42 *
43 * @var array
44 */
45 protected $ajax_only_actions = [
46 // Settings page.
47 'imagify_check_backup_dir_is_writable',
48 'imagify_get_files_tree',
49 // Account.
50 'imagify_signup',
51 'imagify_check_api_key_validity',
52 'imagify_get_prices',
53 'imagify_check_coupon',
54 'imagify_get_discount',
55 'imagify_get_images_counts',
56 'imagify_update_estimate_sizes',
57 'imagify_get_user_data',
58 'imagify_delete_user_data_cache',
59 // Various.
60 'nopriv_imagify_rpc',
61 ];
62
63 /**
64 * Actions to be triggered only on admin post.
65 *
66 * @var array
67 */
68 protected $post_only_actions = [
69 // Custom folders optimization.
70 'imagify_scan_custom_folders',
71 // Various.
72 'imagify_dismiss_ad',
73 ];
74
75 /**
76 * Filesystem object.
77 *
78 * @var Imagify_Filesystem
79 */
80 protected $filesystem;
81
82
83 /** ----------------------------------------------------------------------------------------- */
84 /** INIT ==================================================================================== */
85 /** ----------------------------------------------------------------------------------------- */
86
87 /**
88 * The constructor.
89 */
90 public function __construct() {
91 $this->filesystem = Imagify_Filesystem::get_instance();
92 }
93
94 /**
95 * Launch the hooks.
96 *
97 * @since 1.6.11
98 */
99 public function init() {
100 $doing_ajax = wp_doing_ajax();
101
102 foreach ( $this->ajax_post_actions as $action ) {
103 $action_callback = "{$action}_callback";
104 if ( $doing_ajax ) {
105 add_action( 'wp_ajax_' . $action, [ $this, $action_callback ] );
106 }
107 add_action( 'admin_post_' . $action, [ $this, $action_callback ] );
108 }
109
110 // Actions triggered only on admin ajax.
111 if ( $doing_ajax ) {
112 foreach ( $this->ajax_only_actions as $action ) {
113 add_action( 'wp_ajax_' . $action, [ $this, $action . '_callback' ] );
114 }
115 }
116
117 // Actions triggered on admin post.
118 foreach ( $this->post_only_actions as $action ) {
119 add_action( 'admin_post_' . $action, [ $this, $action . '_callback' ] );
120 }
121 }
122
123
124 /** ----------------------------------------------------------------------------------------- */
125 /** OPTIMIZATION PROCESSES ================================================================== */
126 /** ----------------------------------------------------------------------------------------- */
127
128 /**
129 * Optimize one media.
130 *
131 * @since 1.9
132 *
133 * @param int $media_id The media ID.
134 * @param string $context The context.
135 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
136 */
137 protected function optimize_media( $media_id, $context ) {
138 // A manual click is an explicit, single-image user action: flag it as priority so it
139 // jumps ahead of any bulk optimization queue.
140 return imagify_get_optimization_process( $media_id, $context )->optimize( null, [ 'priority' => true ] );
141 }
142
143 /**
144 * Re-optimize a media to a different optimization level.
145 *
146 * @since 1.9
147 *
148 * @param int $media_id The media ID.
149 * @param string $context The context.
150 * @param int $level The optimization level.
151 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
152 */
153 protected function reoptimize_media( $media_id, $context, $level ) {
154 // A manual click is an explicit, single-image user action: flag it as priority so it
155 // jumps ahead of any bulk optimization queue.
156 return imagify_get_optimization_process( $media_id, $context )->reoptimize( $level, [ 'priority' => true ] );
157 }
158
159 /**
160 * Optimize all files from a media, whatever this media’s previous optimization status (will be restored if needed).
161 * This is used by the bulk optimization page.
162 *
163 * @since 1.9
164 *
165 * @param int $media_id The media ID.
166 * @param string $context The context.
167 * @param int $level The optimization level.
168 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
169 */
170 protected function force_optimize( $media_id, $context, $level ) {
171 $process = imagify_get_optimization_process( $media_id, $context );
172 $data = $process->get_data();
173
174 // Restore before re-optimizing.
175 if ( $data->is_optimized() ) {
176 $result = $process->restore();
177
178 if ( is_wp_error( $result ) ) {
179 // Return an error message.
180 return $result;
181 }
182 }
183
184 return $process->optimize( $level );
185 }
186
187 /**
188 * Optimize one or some thumbnails that are not optimized yet.
189 *
190 * @since 1.9
191 *
192 * @param int $media_id The media ID.
193 * @param string $context The context.
194 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
195 */
196 protected function optimize_missing_sizes( $media_id, $context ) {
197 return imagify_get_optimization_process( $media_id, $context )->optimize_missing_thumbnails();
198 }
199
200 /**
201 * Generate next-gen images if they are missing.
202 *
203 * @since 1.9
204 *
205 * @param int $media_id The media ID.
206 * @param string $context The context.
207 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
208 */
209 protected function generate_nextgen_versions( $media_id, $context ) {
210 return imagify_get_optimization_process( $media_id, $context )->generate_nextgen_versions();
211 }
212
213 /**
214 * Delete Next gen images for media that are "already_optimize".
215 *
216 * @since 1.9.6
217 *
218 * @param int $media_id The media ID.
219 * @param string $context The context.
220 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
221 */
222 protected function delete_nextgen_versions( $media_id, $context ) {
223 $process = imagify_get_optimization_process( $media_id, $context );
224
225 if ( ! $process->is_valid() ) {
226 return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
227 }
228
229 $data = $process->get_data();
230
231 if ( ! $data->is_already_optimized() ) {
232 return new WP_Error( 'not_already_optimized', __( 'This media does not have the right optimization status.', 'imagify' ) );
233 }
234
235 if ( ! $process->has_next_gen() ) {
236 return true;
237 }
238
239 $data->delete_optimization_data();
240 $deleted = $process->delete_nextgen_files( false, true );
241
242 if ( is_wp_error( $deleted ) ) {
243 return new WP_Error( 'nextgen_not_deleted', __( 'Previous next-gen files could not be deleted.', 'imagify' ) );
244 }
245
246 return true;
247 }
248
249 /**
250 * Restore a media.
251 *
252 * @since 1.9
253 *
254 * @param int $media_id The media ID.
255 * @param string $context The context.
256 * @return bool|WP_Error True on success. A \WP_Error instance on failure.
257 */
258 protected function restore_media( $media_id, $context ) {
259 return imagify_get_optimization_process( $media_id, $context )->restore();
260 }
261
262 /** ----------------------------------------------------------------------------------------- */
263 /** WP OPTIMIZATION CALLBACKS =============================================================== */
264 /** ----------------------------------------------------------------------------------------- */
265
266 /**
267 * Optimize all thumbnails of a specific image with the manual method.
268 *
269 * @since 1.6.11
270 */
271 public function imagify_manual_optimize_callback() {
272 $context = $this->get_context();
273 $media_id = $this->get_media_id();
274
275 if ( ! $media_id || ! $context ) {
276 imagify_die( __( 'Invalid request', 'imagify' ) );
277 }
278
279 imagify_check_nonce( 'imagify-optimize-' . $media_id . '-' . $context );
280
281 if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
282 imagify_die();
283 }
284
285 $result = $this->optimize_media( $media_id, $context );
286
287 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
288
289 if ( is_wp_error( $result ) ) {
290 // Return an error message.
291 $output = $result->get_error_message();
292
293 wp_send_json_error( [ 'html' => $output ] );
294 }
295
296 wp_send_json_success();
297 }
298
299 /**
300 * Optimize all thumbnails of a specific image with a different optimization level.
301 *
302 * @since 1.6.11
303 */
304 public function imagify_manual_reoptimize_callback() {
305 $context = $this->get_context();
306 $media_id = $this->get_media_id();
307
308 if ( ! $media_id || ! $context ) {
309 imagify_die( __( 'Invalid request', 'imagify' ) );
310 }
311
312 imagify_check_nonce( 'imagify-manual-reoptimize-' . $media_id . '-' . $context );
313
314 if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
315 imagify_die();
316 }
317
318 $result = $this->reoptimize_media( $media_id, $context, $this->get_optimization_level() );
319
320 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
321
322 if ( is_wp_error( $result ) ) {
323 // Return an error message.
324 $output = $result->get_error_message();
325
326 wp_send_json_error( [ 'html' => $output ] );
327 }
328
329 wp_send_json_success();
330 }
331
332 /**
333 * Optimize one or some thumbnails that are not optimized yet.
334 *
335 * @since 1.6.11
336 */
337 public function imagify_optimize_missing_sizes_callback() {
338 $context = $this->get_context();
339 $media_id = $this->get_media_id();
340
341 if ( ! $media_id || ! $context ) {
342 imagify_die( __( 'Invalid request', 'imagify' ) );
343 }
344
345 imagify_check_nonce( 'imagify-optimize-missing-sizes-' . $media_id . '-' . $context );
346
347 if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
348 imagify_die();
349 }
350
351 $result = $this->optimize_missing_sizes( $media_id, $context );
352
353 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
354
355 if ( is_wp_error( $result ) ) {
356 // Return an error message.
357 $output = $result->get_error_message();
358
359 wp_send_json_error( [ 'html' => $output ] );
360 }
361
362 wp_send_json_success();
363 }
364
365 /**
366 * Generate next-gen images if they are missing.
367 *
368 * @since 1.9
369 */
370 public function imagify_generate_nextgen_versions_callback() {
371 $context = $this->get_context();
372 $media_id = $this->get_media_id();
373
374 if ( ! $media_id || ! $context ) {
375 imagify_die( __( 'Invalid request', 'imagify' ) );
376 }
377
378 imagify_check_nonce( 'imagify-generate-nextgen-versions-' . $media_id . '-' . $context );
379
380 if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
381 imagify_die();
382 }
383
384 $result = $this->generate_nextgen_versions( $media_id, $context );
385
386 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
387
388 if ( is_wp_error( $result ) ) {
389 // Return an error message.
390 $output = $result->get_error_message();
391
392 wp_send_json_error( [ 'html' => $output ] );
393 }
394
395 wp_send_json_success();
396 }
397
398 /**
399 * Generate next-gen images if they are missing.
400 *
401 * @since 1.9.6
402 */
403 public function imagify_delete_nextgen_versions_callback() {
404 $context = $this->get_context();
405 $media_id = $this->get_media_id();
406
407 if ( ! $media_id || ! $context ) {
408 imagify_die( __( 'Invalid request', 'imagify' ) );
409 }
410
411 imagify_check_nonce( 'imagify-delete-nextgen-versions-' . $media_id . '-' . $context );
412
413 if ( ! imagify_get_context( $context )->current_user_can( 'manual-restore', $media_id ) ) {
414 imagify_die();
415 }
416
417 $result = $this->delete_nextgen_versions( $media_id, $context );
418
419 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
420
421 if ( is_wp_error( $result ) ) {
422 // Return an error message.
423 $output = $result->get_error_message();
424
425 wp_send_json_error( [ 'html' => $output ] );
426 }
427
428 wp_send_json_success();
429 }
430
431 /**
432 * Process a restoration to the original attachment.
433 *
434 * @since 1.6.11
435 */
436 public function imagify_restore_callback() {
437 $context = $this->get_context();
438 $media_id = $this->get_media_id();
439
440 if ( ! $media_id || ! $context ) {
441 imagify_die( __( 'Invalid request', 'imagify' ) );
442 }
443
444 imagify_check_nonce( 'imagify-restore-' . $media_id . '-' . $context );
445
446 if ( ! imagify_get_context( $context )->current_user_can( 'manual-restore', $media_id ) ) {
447 imagify_die();
448 }
449
450 $result = $this->restore_media( $media_id, $context );
451
452 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
453
454 if ( is_wp_error( $result ) ) {
455 // Return an error message.
456 $output = $result->get_error_message();
457
458 wp_send_json_error( [ 'html' => $output ] );
459 }
460
461 // Return the optimization button.
462 $output = Imagify_Views::get_instance()->get_template(
463 'button/optimize',
464 [
465 'url' => get_imagify_admin_url(
466 'optimize',
467 [
468 'attachment_id' => $media_id,
469 'context' => $context,
470 ]
471 ),
472 ]
473 );
474
475 wp_send_json_success( [ 'html' => $output ] );
476 }
477
478
479 /** ----------------------------------------------------------------------------------------- */
480 /** CUSTOM FOLDERS OPTIMIZATION CALLBACKS =================================================== */
481 /** ----------------------------------------------------------------------------------------- */
482
483 /**
484 * Optimize a file.
485 *
486 * @since 1.7
487 */
488 public function imagify_optimize_file_callback() {
489 imagify_check_nonce( 'imagify_optimize_file' );
490
491 $media_id = $this->get_media_id( 'GET', 'id' );
492
493 if ( ! $media_id ) {
494 imagify_die( __( 'Invalid request', 'imagify' ) );
495 }
496
497 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
498 imagify_die();
499 }
500
501 $result = $this->optimize_media( $media_id, 'custom-folders' );
502
503 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
504
505 if ( is_wp_error( $result ) ) {
506 // Return an error message.
507 wp_send_json_error( $result->get_error_message() );
508 }
509
510 wp_send_json_success();
511 }
512
513 /**
514 * Re-optimize a file.
515 *
516 * @since 1.7
517 */
518 public function imagify_reoptimize_file_callback() {
519 imagify_check_nonce( 'imagify_reoptimize_file' );
520
521 $media_id = $this->get_media_id( 'GET', 'id' );
522
523 if ( ! $media_id ) {
524 imagify_die( __( 'Invalid request', 'imagify' ) );
525 }
526
527 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
528 imagify_die();
529 }
530
531 $level = $this->get_optimization_level( 'GET', 'level' );
532
533 $result = $this->reoptimize_media( $media_id, 'custom-folders', $level );
534
535 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
536
537 if ( is_wp_error( $result ) ) {
538 // Return an error message.
539 wp_send_json_error( $result->get_error_message() );
540 }
541
542 wp_send_json_success();
543 }
544
545 /**
546 * Restore a file.
547 *
548 * @since 1.7
549 */
550 public function imagify_restore_file_callback() {
551 imagify_check_nonce( 'imagify_restore_file' );
552
553 $media_id = $this->get_media_id( 'GET', 'id' );
554
555 if ( ! $media_id ) {
556 imagify_die( __( 'Invalid request', 'imagify' ) );
557 }
558
559 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-restore', $media_id ) ) {
560 imagify_die();
561 }
562
563 $result = $this->restore_media( $media_id, 'custom-folders' );
564
565 imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
566
567 if ( is_wp_error( $result ) ) {
568 // Return an error message.
569 wp_send_json_error( $result->get_error_message() );
570 }
571
572 $process = imagify_get_optimization_process( $media_id, 'custom-folders' );
573 $this->file_optimization_output( $process );
574 }
575
576 /**
577 * Check if a file has been modified, and update the database accordingly.
578 *
579 * @since 1.7
580 */
581 public function imagify_refresh_file_modified_callback() {
582 imagify_check_nonce( 'imagify_refresh_file_modified' );
583
584 $media_id = $this->get_media_id( 'GET', 'id' );
585
586 if ( ! $media_id ) {
587 imagify_die( __( 'Invalid request', 'imagify' ) );
588 }
589
590 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
591 imagify_die();
592 }
593
594 $process = imagify_get_optimization_process( $media_id, 'custom-folders' );
595 $result = Imagify_Custom_Folders::refresh_file( $process );
596
597 if ( is_wp_error( $result ) ) {
598 // The media is not valid or has been removed from the database.
599 $message = $result->get_error_message();
600
601 imagify_maybe_redirect( $message );
602
603 wp_send_json_error(
604 [
605 'row' => $message,
606 ]
607 );
608 }
609
610 imagify_maybe_redirect();
611
612 // Return some HTML to the ajax call.
613 $this->file_optimization_output( $process );
614 }
615
616 /**
617 * Look for new files in custom folders.
618 *
619 * @since 1.7
620 */
621 public function imagify_scan_custom_folders_callback() {
622 imagify_check_nonce( 'imagify_scan_custom_folders' );
623
624 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'optimize' ) ) {
625 imagify_die();
626 }
627
628 $folder = (int) filter_input( INPUT_GET, 'folder', FILTER_VALIDATE_INT );
629
630 if ( $folder > 0 ) {
631 // A specific custom folder (selected or not).
632 $folders_db = Imagify_Folders_DB::get_instance();
633 $folders_key = $folders_db->get_primary_key();
634 $folder = $folders_db->get( $folder );
635
636 if ( ! $folder ) {
637 // This should not happen.
638 imagify_maybe_redirect( __( 'This folder is not in the database.', 'imagify' ) );
639 }
640
641 $folder['folder_path'] = Imagify_Files_Scan::remove_placeholder( $folder['path'] );
642
643 $folders = [
644 $folder[ $folders_key ] => $folder,
645 ];
646
647 Imagify_Custom_Folders::get_files_from_folders(
648 $folders,
649 [
650 'add_inactive_folder_files' => true,
651 ]
652 );
653
654 imagify_maybe_redirect();
655 }
656
657 // All selected custom folders.
658 $folders = Imagify_Custom_Folders::get_folders(
659 [
660 'active' => true,
661 ]
662 );
663 Imagify_Custom_Folders::get_files_from_folders( $folders );
664
665 imagify_maybe_redirect();
666 }
667
668
669 /** ----------------------------------------------------------------------------------------- */
670 /** SETTINGS PAGE CALLBACKS ================================================================= */
671 /** ----------------------------------------------------------------------------------------- */
672
673 /**
674 * Check if the backup directory is writable.
675 * This is used to display an error message in the plugin's settings page.
676 *
677 * @since 1.6.11
678 */
679 public function imagify_check_backup_dir_is_writable_callback() {
680 imagify_check_nonce( 'imagify_check_backup_dir_is_writable' );
681
682 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
683 imagify_die();
684 }
685
686 wp_send_json_success(
687 [
688 'is_writable' => (int) Imagify_Requirements::attachments_backup_dir_is_writable(),
689 ]
690 );
691 }
692
693 /**
694 * Get files and folders that are direct children of a given folder.
695 *
696 * @since 1.7
697 */
698 public function imagify_get_files_tree_callback() {
699 imagify_check_nonce( 'get-files-tree' );
700
701 if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manage' ) ) {
702 imagify_die();
703 }
704
705 if ( ! isset( $_POST['folder'] ) || '' === $_POST['folder'] ) {
706 imagify_die( __( 'Invalid request', 'imagify' ) );
707 }
708
709 $folder = sanitize_text_field( wp_unslash( $_POST['folder'] ) );
710 $folder = trailingslashit( $folder );
711 $folder = realpath( $this->filesystem->get_site_root() . ltrim( $folder, '/' ) );
712
713 if ( ! $folder ) {
714 imagify_die( __( 'This folder doesn\'t exist.', 'imagify' ) );
715 }
716
717 if ( ! $this->filesystem->is_dir( $folder ) ) {
718 imagify_die( __( 'This file is not a folder.', 'imagify' ) );
719 }
720
721 $folder = $this->filesystem->normalize_dir_path( $folder );
722
723 if ( Imagify_Files_Scan::is_path_forbidden( $folder ) ) {
724 imagify_die( __( 'This folder is not allowed.', 'imagify' ) );
725 }
726
727 // Finally we made all our validations.
728 $selected = ! empty( $_POST['selected'] ) && is_array( $_POST['selected'] ) ? array_flip( array_map( 'sanitize_text_field', wp_unslash( $_POST['selected'] ) ) ) : [];
729 $views = Imagify_Views::get_instance();
730 $output = '';
731
732 if ( $this->filesystem->is_site_root( $folder ) ) {
733 $output .= $views->get_template(
734 'part-settings-files-tree-row',
735 [
736 'relative_path' => '/',
737 // Value #///# Label.
738 'checkbox_value' => '{{ROOT}}/#///#' . esc_attr__( 'Site\'s root', 'imagify' ),
739 'checkbox_id' => 'ABSPATH',
740 'checkbox_selected' => isset( $selected['{{ROOT}}/'] ),
741 'label' => __( 'Site\'s root', 'imagify' ),
742 'no_button' => true,
743 ]
744 );
745 }
746
747 $dir = new DirectoryIterator( $folder );
748 $dir = new Imagify_Files_Iterator( $dir );
749 $images = 0;
750
751 foreach ( new IteratorIterator( $dir ) as $file ) {
752 if ( ! $file->isDir() ) {
753 ++$images;
754 continue;
755 }
756
757 $folder_path = trailingslashit( $file->getPathname() );
758 $relative_path = $this->filesystem->make_path_relative( $folder_path );
759 $placeholder = Imagify_Files_Scan::add_placeholder( $folder_path );
760
761 $output .= $views->get_template(
762 'part-settings-files-tree-row',
763 [
764 'relative_path' => esc_attr( $relative_path ),
765 // Value #///# Label.
766 'checkbox_value' => esc_attr( $placeholder ) . '#///#' . esc_attr( $relative_path ),
767 'checkbox_id' => sanitize_html_class( $placeholder ),
768 'checkbox_selected' => isset( $selected[ $placeholder ] ),
769 'label' => $this->filesystem->file_name( $folder_path ),
770 ]
771 );
772 }
773
774 if ( $images ) {
775 /* translators: %s is a formatted number, dont use %d. */
776 $output .= '<li class="imagify-number-of-images-in-folder"><em><span class="dashicons dashicons-images-alt"></span> ' . sprintf( _n( '%s Media File', '%s Media Files', $images, 'imagify' ), number_format_i18n( $images ) ) . '</em></li>';
777 }
778
779 if ( ! $output ) {
780 $output .= '<li class="imagify-empty-folder"><em>' . __( 'No optimizable files', 'imagify' ) . '</em></li>';
781 }
782
783 wp_send_json_success( $output );
784 }
785
786
787 /** ----------------------------------------------------------------------------------------- */
788 /** IMAGIFY ACCOUNT CALLBACKS =============================================================== */
789 /** ----------------------------------------------------------------------------------------- */
790
791 /**
792 * Create a new Imagify account.
793 *
794 * @since 1.6.11
795 */
796 public function imagify_signup_callback() {
797 imagify_check_nonce( 'imagify-signup', 'imagifysignupnonce' );
798
799 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
800 imagify_die();
801 }
802
803 if ( empty( $_GET['email'] ) ) {
804 imagify_die( __( 'Empty email address.', 'imagify' ) );
805 }
806
807 $email = sanitize_email( wp_unslash( $_GET['email'] ) );
808
809 if ( ! is_email( $email ) ) {
810 imagify_die( __( 'Not a valid email address.', 'imagify' ) );
811 }
812
813 $data = [
814 'email' => $email,
815 'password' => wp_generate_password( 12, false ),
816 'lang' => imagify_get_locale(),
817 ];
818
819 $response = add_imagify_user( $data );
820
821 if ( is_wp_error( $response ) ) {
822 imagify_die( $response );
823 }
824
825 wp_send_json_success();
826 }
827
828 /**
829 * Check the API key validity.
830 *
831 * @since 1.6.11
832 * @since 2.3.1 Ignore user abort while saving the key, so the option and its
833 * related validity cache/notice are never left in a partial state
834 * when the browser cancels the request (e.g. the user clicks away
835 * or navigates while the check is still running).
836 */
837 public function imagify_check_api_key_validity_callback() {
838 imagify_check_nonce( 'imagify-check-api-key', 'imagifycheckapikeynonce' );
839
840 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
841 imagify_die();
842 }
843
844 if ( empty( $_GET['api_key'] ) ) {
845 imagify_die( __( 'Empty API key.', 'imagify' ) );
846 }
847
848 $api_key = sanitize_key( wp_unslash( $_GET['api_key'] ) );
849 $response = get_imagify_status( $api_key );
850
851 if ( is_wp_error( $response ) ) {
852 imagify_die( $response );
853 }
854
855 /*
856 * From this point, the key is known to be valid. Saving the option triggers
857 * `after_save_options()`, which performs its own request to the Imagify API
858 * to (re)set the "valid key" cache and dismiss/renew the "wrong-api-key" notice.
859 * If the client disconnects (e.g. the user clicks elsewhere or navigates away
860 * while the spinner is still showing), PHP would otherwise abort mid-way,
861 * leaving the key saved but the validity cache/notice stale or missing.
862 * Ignoring user abort for this short, bounded sequence guarantees the option
863 * and its validity state are always saved consistently together.
864 */
865 $ignore_user_abort = ignore_user_abort( true );
866
867 update_imagify_option( 'api_key', $api_key );
868
869 delete_transient( 'imagify_user_cache' );
870
871 ignore_user_abort( $ignore_user_abort );
872
873 wp_send_json_success();
874 }
875
876 /**
877 * Get pricings from API for Onetime and Plans at the same time.
878 *
879 * @since 1.6.11
880 */
881 public function imagify_get_prices_callback() {
882 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
883
884 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
885 imagify_die();
886 }
887
888 $prices_all = get_imagify_all_prices();
889
890 if ( is_wp_error( $prices_all ) ) {
891 imagify_die( $prices_all );
892 }
893
894 if ( ! is_object( $prices_all ) ) {
895 imagify_die( __( 'Wrongly formatted response from our server.', 'imagify' ) );
896 }
897
898 wp_send_json_success(
899 [
900 'monthlies' => $prices_all->Plans,
901 ]
902 );
903 }
904
905 /**
906 * Check Coupon code on modal popin.
907 *
908 * @since 1.6.11
909 */
910 public function imagify_check_coupon_callback() {
911 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
912
913 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
914 imagify_die();
915 }
916
917 if ( empty( $_POST['coupon'] ) ) {
918 wp_send_json_success(
919 [
920 'success' => false,
921 'detail' => __( 'Coupon is empty.', 'imagify' ),
922 ]
923 );
924 }
925
926 $coupon = sanitize_text_field( wp_unslash( $_POST['coupon'] ) );
927 $coupon = check_imagify_coupon_code( $coupon );
928
929 if ( is_wp_error( $coupon ) ) {
930 imagify_die( $coupon );
931 }
932
933 wp_send_json_success( imagify_translate_api_message( $coupon ) );
934 }
935
936 /**
937 * Get current discount promotion to display information on payment modal.
938 *
939 * @since 1.6.11
940 */
941 public function imagify_get_discount_callback() {
942 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
943
944 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
945 imagify_die();
946 }
947
948 wp_send_json_success( imagify_translate_api_message( check_imagify_discount() ) );
949 }
950
951 /**
952 * Get estimated sizes from the WordPress library.
953 *
954 * @since 1.6.11
955 */
956 public function imagify_get_images_counts_callback() {
957 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
958
959 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
960 imagify_die();
961 }
962
963 $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
964 $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
965
966 Imagify_Data::get_instance()->set(
967 [
968 'total_size_images_library' => $raw_total_size_in_library,
969 'average_size_images_per_month' => $raw_average_per_month,
970 ]
971 );
972
973 wp_send_json_success(
974 [
975 'total_library_size' => [
976 'raw' => $raw_total_size_in_library,
977 'human' => imagify_size_format( $raw_total_size_in_library ),
978 ],
979 'average_month_size' => [
980 'raw' => $raw_average_per_month,
981 'human' => imagify_size_format( $raw_average_per_month ),
982 ],
983 ]
984 );
985 }
986
987 /**
988 * Estimate sizes and update the options values for them.
989 *
990 * @since 1.6.11
991 */
992 public function imagify_update_estimate_sizes_callback() {
993 imagify_check_nonce( 'update_estimate_sizes' );
994
995 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
996 imagify_die();
997 }
998
999 $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
1000 $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
1001
1002 Imagify_Data::get_instance()->set(
1003 [
1004 'total_size_images_library' => $raw_total_size_in_library,
1005 'average_size_images_per_month' => $raw_average_per_month,
1006 ]
1007 );
1008
1009 die( 1 );
1010 }
1011
1012 /**
1013 * Get the Imagify User data.
1014 *
1015 * @since 1.7
1016 */
1017 public function imagify_get_user_data_callback() {
1018 imagify_check_nonce( 'imagify_get_user_data' );
1019
1020 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
1021 imagify_die();
1022 }
1023
1024 $user = imagify_cache_user();
1025
1026 if ( ! $user || ! $user->id ) {
1027 imagify_die( __( 'Couldn\'t get user data.', 'imagify' ) );
1028 }
1029
1030 // Remove useless sensitive data.
1031 unset( $user->email );
1032
1033 if ( ! $user->get_percent_unconsumed_quota ) {
1034 $user->best_plan_title = __( 'Oops, It\'s Over!', 'imagify' );
1035 } elseif ( $user->get_percent_unconsumed_quota <= 20 ) {
1036 $user->best_plan_title = __( 'Oops, It\'s almost over!', 'imagify' );
1037 } else {
1038 $user->best_plan_title = __( 'Unlock Imagify\'s full potential', 'imagify' );
1039 }
1040
1041 wp_send_json_success( $user );
1042 }
1043
1044 /**
1045 * Delete the Imagify User data cache.
1046 *
1047 * @since 1.9.5
1048 */
1049 public function imagify_delete_user_data_cache_callback() {
1050 imagify_check_nonce( 'imagify_delete_user_data_cache' );
1051
1052 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
1053 imagify_die();
1054 }
1055
1056 imagify_delete_cached_user();
1057
1058 wp_send_json_success();
1059 }
1060
1061
1062 /** ----------------------------------------------------------------------------------------- */
1063 /** VARIOUS CALLBACKS ======================================================================= */
1064 /** ----------------------------------------------------------------------------------------- */
1065
1066 /**
1067 * Bridge between XML-RPC and actions triggered by imagify_do_async_job().
1068 * When XML-RPC is used, a current user is set, but no cookies are set, so they cannot be sent with the request. Instead we stored the user ID in a transient.
1069 *
1070 * @since 1.6.11
1071 * @see imagify_do_async_job()
1072 */
1073 public function nopriv_imagify_rpc_callback() {
1074 if ( empty( $_POST['imagify_rpc_action'] ) || empty( $_POST['imagify_rpc_id'] ) ) {
1075 imagify_die( __( 'Invalid request', 'imagify' ) );
1076 }
1077
1078 $action = sanitize_text_field( wp_unslash( $_POST['imagify_rpc_action'] ) );
1079
1080 if ( 32 !== strlen( $action ) ) {
1081 imagify_die( __( 'Invalid request', 'imagify' ) );
1082 }
1083
1084 // Not necessary but just in case, whitelist the original action.
1085 $actions = array_flip( $this->ajax_only_actions );
1086 unset( $actions['nopriv_imagify_rpc'] );
1087
1088 if ( ! isset( $actions[ $action ] ) ) {
1089 imagify_die( __( 'Invalid request', 'imagify' ) );
1090 }
1091
1092 // Get the user ID.
1093 $rpc_id = sanitize_key( $_POST['imagify_rpc_id'] );
1094 $user_id = absint( get_transient( 'imagify_rpc_' . $rpc_id ) );
1095 $user = $user_id ? get_userdata( $user_id ) : false;
1096
1097 delete_transient( 'imagify_rpc_' . $rpc_id );
1098
1099 if ( ! $user || ! $user->exists() ) {
1100 imagify_die( __( 'Invalid request', 'imagify' ) );
1101 }
1102
1103 // The current user must be set before verifying the nonce.
1104 wp_set_current_user( $user_id );
1105
1106 imagify_check_nonce( 'imagify_rpc_' . $rpc_id, 'imagify_rpc_nonce' );
1107
1108 // Trigger the action we originally wanted.
1109 $_POST['action'] = $action;
1110 unset( $_POST['imagify_rpc_action'], $_POST['imagify_rpc_id'], $_POST['imagify_rpc_nonce'] );
1111
1112 /** This hook is documented in wp-admin/admin-ajax.php. */
1113 do_action( 'wp_ajax_' . $action );
1114 }
1115
1116 /**
1117 * Store the "closed" status of the ads.
1118 *
1119 * @since 1.7
1120 */
1121 public function imagify_dismiss_ad_callback() {
1122 imagify_check_nonce( 'imagify-dismiss-ad' );
1123
1124 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
1125 imagify_die();
1126 }
1127
1128 if ( empty( $_GET['ad'] ) ) {
1129 imagify_maybe_redirect();
1130 wp_send_json_error();
1131 }
1132
1133 $notice = sanitize_text_field( wp_unslash( $_GET['ad'] ) );
1134
1135 if ( ! $notice ) {
1136 imagify_maybe_redirect();
1137 wp_send_json_error();
1138 }
1139
1140 $user_id = get_current_user_id();
1141 $notices = get_user_meta( $user_id, '_imagify_ignore_ads', true );
1142 $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : [];
1143
1144 if ( isset( $notices[ $notice ] ) ) {
1145 imagify_maybe_redirect();
1146 wp_send_json_success();
1147 }
1148
1149 $notices = array_flip( $notices );
1150 $notices[] = $notice;
1151 $notices = array_filter( $notices );
1152 $notices = array_values( $notices );
1153
1154 update_user_meta( $user_id, '_imagify_ignore_ads', $notices );
1155
1156 imagify_maybe_redirect();
1157 wp_send_json_success();
1158 }
1159
1160
1161 /** ----------------------------------------------------------------------------------------- */
1162 /** VARIOUS HELPERS ========================================================================= */
1163 /** ----------------------------------------------------------------------------------------- */
1164
1165 /**
1166 * Get the submitted optimization level.
1167 *
1168 * @since 1.7
1169 * @since 1.9 Added $method and $parameter parameters.
1170 *
1171 * @param string $method The method used: 'GET' (default), or 'POST'.
1172 * @param string $parameter The name of the parameter to look for.
1173 * @return int
1174 */
1175 public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) {
1176 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
1177 $level = filter_input( $method, $parameter );
1178
1179 if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) {
1180 if ( get_imagify_option( 'lossless' ) ) {
1181 return 0;
1182 }
1183
1184 return get_imagify_option( 'optimization_level' );
1185 }
1186
1187 return (int) $level;
1188 }
1189
1190 /**
1191 * Get the submitted context.
1192 *
1193 * @since 1.9
1194 *
1195 * @param string $method The method used: 'GET' (default), or 'POST'.
1196 * @param string $parameter The name of the parameter to look for.
1197 * @return string
1198 */
1199 public function get_context( $method = 'GET', $parameter = 'context' ) {
1200 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1201 // No context.
1202 return 'noop';
1203 }
1204
1205 $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
1206
1207 return imagify_sanitize_context( $context );
1208 }
1209
1210 /**
1211 * Get the submitted media ID.
1212 *
1213 * @since 1.9
1214 *
1215 * @param string $method The method used: 'GET' (default), or 'POST'.
1216 * @param string $parameter The name of the parameter to look for.
1217 * @return int
1218 */
1219 public function get_media_id( $method = 'GET', $parameter = 'attachment_id' ) {
1220 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
1221 $media_id = filter_input( $method, $parameter );
1222
1223 if ( ! is_numeric( $media_id ) || $media_id < 0 ) {
1224 return 0;
1225 }
1226
1227 return (int) $media_id;
1228 }
1229
1230 /**
1231 * Get the submitted folder_type.
1232 *
1233 * @since 1.9
1234 *
1235 * @param string $method The method used: 'GET' (default), or 'POST'.
1236 * @param string $parameter The name of the parameter to look for.
1237 *
1238 * @return string
1239 */
1240 public function get_folder_type( $method = 'GET', $parameter = 'folder_type' ) {
1241 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1242 // No folder type.
1243 return 'noop';
1244 }
1245
1246 $folder_type = '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
1247
1248 return $folder_type;
1249 }
1250
1251 /**
1252 * Get the submitted imagify action.
1253 *
1254 * @since 1.9
1255 *
1256 * @param string $method The method used: 'GET' (default), or 'POST'.
1257 * @param string $parameter The name of the parameter to look for.
1258 *
1259 * @return string
1260 */
1261 public function get_imagify_action( $method = 'GET', $parameter = 'imagify_action' ) {
1262 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1263 // No action.
1264 return 'optimize';
1265 }
1266
1267 $action = '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
1268
1269 return $action ? $action : 'optimize';
1270 }
1271
1272 /**
1273 * Get the Bulk class name depending on a context.
1274 *
1275 * @since 1.9
1276 *
1277 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
1278 * @return string The Bulk class name.
1279 */
1280 public function get_bulk_class_name( $context ) {
1281 switch ( $context ) {
1282 case 'wp':
1283 $class_name = '\\Imagify\\Bulk\\WP';
1284 break;
1285
1286 case 'custom-folders':
1287 $class_name = '\\Imagify\\Bulk\\CustomFolders';
1288 break;
1289
1290 default:
1291 $class_name = '\\Imagify\\Bulk\\Noop';
1292 }
1293
1294 /**
1295 * Filter the name of the class to use for bulk process.
1296 *
1297 * @since 1.9
1298 *
1299 * @param int $class_name The class name.
1300 * @param string $context The context name.
1301 */
1302 $class_name = apply_filters( 'imagify_bulk_class_name', $class_name, $context );
1303
1304 return '\\' . ltrim( $class_name, '\\' );
1305 }
1306
1307 /**
1308 * Get the Bulk instance depending on a context.
1309 *
1310 * @since 1.9
1311 *
1312 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
1313 * @return BulkInterface The optimization process instance.
1314 */
1315 public function get_bulk_instance( $context ) {
1316 $class_name = $this->get_bulk_class_name( $context );
1317 return new $class_name();
1318 }
1319
1320 /**
1321 * Check if the user has a valid account and has quota. Die on failure.
1322 *
1323 * @since 1.7
1324 */
1325 public function check_can_optimize() {
1326 if ( ! Imagify_Requirements::is_api_key_valid() ) {
1327 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1328 wp_send_json_error( [ 'message' => 'invalid-api-key' ] );
1329 }
1330
1331 imagify_die( __( 'Your API key is not valid!', 'imagify' ) );
1332 }
1333
1334 if ( Imagify_Requirements::is_over_quota() ) {
1335 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1336 wp_send_json_error( [ 'message' => 'over-quota' ] );
1337 }
1338
1339 imagify_die( __( 'You have used all your credits!', 'imagify' ) );
1340 }
1341 }
1342
1343 /**
1344 * Get a media columns for the "Other Media" page.
1345 *
1346 * @since 1.9
1347 *
1348 * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
1349 * @param object $list_table A Imagify_Files_List_Table object.
1350 * @return array An array of HTML, keyed by column name.
1351 */
1352 public function get_media_columns( $process, $list_table ) {
1353 $item = (object) [ 'process' => $process ];
1354
1355 return [
1356 'folder' => $list_table->get_column( 'folder', $item ),
1357 'optimization' => $list_table->get_column( 'optimization', $item ),
1358 'status' => $list_table->get_column( 'status', $item ),
1359 'optimization_level' => $list_table->get_column( 'optimization_level', $item ),
1360 'actions' => $list_table->get_column( 'actions', $item ),
1361 'title' => $list_table->get_column( 'title', $item ), // This one must remain after the "optimization" column, otherwize the data for the comparison tool won't be up-to-date.
1362 ];
1363 }
1364
1365 /**
1366 * After a file optimization, restore, or whatever, redirect the user or output HTML for ajax.
1367 *
1368 * @since 1.7
1369 * @since 1.9 Removed parameter $result.
1370 * @since 1.9 Added $folder in the returned JSON.
1371 *
1372 * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
1373 */
1374 protected function file_optimization_output( $process ) {
1375 $list_table = new Imagify_Files_List_Table(
1376 [
1377 'screen' => 'imagify-files',
1378 ]
1379 );
1380
1381 wp_send_json_success(
1382 [
1383 'columns' => $this->get_media_columns( $process, $list_table ),
1384 ]
1385 );
1386 }
1387 }
1388