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

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