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

1,366 lines 37.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 */
829 public function imagify_check_api_key_validity_callback() {
830 imagify_check_nonce( 'imagify-check-api-key', 'imagifycheckapikeynonce' );
831
832 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
833 imagify_die();
834 }
835
836 if ( empty( $_GET['api_key'] ) ) {
837 imagify_die( __( 'Empty API key.', 'imagify' ) );
838 }
839
840 $api_key = sanitize_key( wp_unslash( $_GET['api_key'] ) );
841 $response = get_imagify_status( $api_key );
842
843 if ( is_wp_error( $response ) ) {
844 imagify_die( $response );
845 }
846
847 update_imagify_option( 'api_key', $api_key );
848
849 delete_transient( 'imagify_user_cache' );
850
851 wp_send_json_success();
852 }
853
854 /**
855 * Get pricings from API for Onetime and Plans at the same time.
856 *
857 * @since 1.6.11
858 */
859 public function imagify_get_prices_callback() {
860 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
861
862 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
863 imagify_die();
864 }
865
866 $prices_all = get_imagify_all_prices();
867
868 if ( is_wp_error( $prices_all ) ) {
869 imagify_die( $prices_all );
870 }
871
872 if ( ! is_object( $prices_all ) ) {
873 imagify_die( __( 'Wrongly formatted response from our server.', 'imagify' ) );
874 }
875
876 wp_send_json_success(
877 [
878 'monthlies' => $prices_all->Plans,
879 ]
880 );
881 }
882
883 /**
884 * Check Coupon code on modal popin.
885 *
886 * @since 1.6.11
887 */
888 public function imagify_check_coupon_callback() {
889 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
890
891 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
892 imagify_die();
893 }
894
895 if ( empty( $_POST['coupon'] ) ) {
896 wp_send_json_success(
897 [
898 'success' => false,
899 'detail' => __( 'Coupon is empty.', 'imagify' ),
900 ]
901 );
902 }
903
904 $coupon = sanitize_text_field( wp_unslash( $_POST['coupon'] ) );
905 $coupon = check_imagify_coupon_code( $coupon );
906
907 if ( is_wp_error( $coupon ) ) {
908 imagify_die( $coupon );
909 }
910
911 wp_send_json_success( imagify_translate_api_message( $coupon ) );
912 }
913
914 /**
915 * Get current discount promotion to display information on payment modal.
916 *
917 * @since 1.6.11
918 */
919 public function imagify_get_discount_callback() {
920 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
921
922 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
923 imagify_die();
924 }
925
926 wp_send_json_success( imagify_translate_api_message( check_imagify_discount() ) );
927 }
928
929 /**
930 * Get estimated sizes from the WordPress library.
931 *
932 * @since 1.6.11
933 */
934 public function imagify_get_images_counts_callback() {
935 imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
936
937 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
938 imagify_die();
939 }
940
941 $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
942 $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
943
944 Imagify_Data::get_instance()->set(
945 [
946 'total_size_images_library' => $raw_total_size_in_library,
947 'average_size_images_per_month' => $raw_average_per_month,
948 ]
949 );
950
951 wp_send_json_success(
952 [
953 'total_library_size' => [
954 'raw' => $raw_total_size_in_library,
955 'human' => imagify_size_format( $raw_total_size_in_library ),
956 ],
957 'average_month_size' => [
958 'raw' => $raw_average_per_month,
959 'human' => imagify_size_format( $raw_average_per_month ),
960 ],
961 ]
962 );
963 }
964
965 /**
966 * Estimate sizes and update the options values for them.
967 *
968 * @since 1.6.11
969 */
970 public function imagify_update_estimate_sizes_callback() {
971 imagify_check_nonce( 'update_estimate_sizes' );
972
973 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
974 imagify_die();
975 }
976
977 $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
978 $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
979
980 Imagify_Data::get_instance()->set(
981 [
982 'total_size_images_library' => $raw_total_size_in_library,
983 'average_size_images_per_month' => $raw_average_per_month,
984 ]
985 );
986
987 die( 1 );
988 }
989
990 /**
991 * Get the Imagify User data.
992 *
993 * @since 1.7
994 */
995 public function imagify_get_user_data_callback() {
996 imagify_check_nonce( 'imagify_get_user_data' );
997
998 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
999 imagify_die();
1000 }
1001
1002 $user = imagify_cache_user();
1003
1004 if ( ! $user || ! $user->id ) {
1005 imagify_die( __( 'Couldn\'t get user data.', 'imagify' ) );
1006 }
1007
1008 // Remove useless sensitive data.
1009 unset( $user->email );
1010
1011 if ( ! $user->get_percent_unconsumed_quota ) {
1012 $user->best_plan_title = __( 'Oops, It\'s Over!', 'imagify' );
1013 } elseif ( $user->get_percent_unconsumed_quota <= 20 ) {
1014 $user->best_plan_title = __( 'Oops, It\'s almost over!', 'imagify' );
1015 } else {
1016 $user->best_plan_title = __( 'Unlock Imagify\'s full potential', 'imagify' );
1017 }
1018
1019 wp_send_json_success( $user );
1020 }
1021
1022 /**
1023 * Delete the Imagify User data cache.
1024 *
1025 * @since 1.9.5
1026 */
1027 public function imagify_delete_user_data_cache_callback() {
1028 imagify_check_nonce( 'imagify_delete_user_data_cache' );
1029
1030 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
1031 imagify_die();
1032 }
1033
1034 imagify_delete_cached_user();
1035
1036 wp_send_json_success();
1037 }
1038
1039
1040 /** ----------------------------------------------------------------------------------------- */
1041 /** VARIOUS CALLBACKS ======================================================================= */
1042 /** ----------------------------------------------------------------------------------------- */
1043
1044 /**
1045 * Bridge between XML-RPC and actions triggered by imagify_do_async_job().
1046 * 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.
1047 *
1048 * @since 1.6.11
1049 * @see imagify_do_async_job()
1050 */
1051 public function nopriv_imagify_rpc_callback() {
1052 if ( empty( $_POST['imagify_rpc_action'] ) || empty( $_POST['imagify_rpc_id'] ) ) {
1053 imagify_die( __( 'Invalid request', 'imagify' ) );
1054 }
1055
1056 $action = sanitize_text_field( wp_unslash( $_POST['imagify_rpc_action'] ) );
1057
1058 if ( 32 !== strlen( $action ) ) {
1059 imagify_die( __( 'Invalid request', 'imagify' ) );
1060 }
1061
1062 // Not necessary but just in case, whitelist the original action.
1063 $actions = array_flip( $this->ajax_only_actions );
1064 unset( $actions['nopriv_imagify_rpc'] );
1065
1066 if ( ! isset( $actions[ $action ] ) ) {
1067 imagify_die( __( 'Invalid request', 'imagify' ) );
1068 }
1069
1070 // Get the user ID.
1071 $rpc_id = sanitize_key( $_POST['imagify_rpc_id'] );
1072 $user_id = absint( get_transient( 'imagify_rpc_' . $rpc_id ) );
1073 $user = $user_id ? get_userdata( $user_id ) : false;
1074
1075 delete_transient( 'imagify_rpc_' . $rpc_id );
1076
1077 if ( ! $user || ! $user->exists() ) {
1078 imagify_die( __( 'Invalid request', 'imagify' ) );
1079 }
1080
1081 // The current user must be set before verifying the nonce.
1082 wp_set_current_user( $user_id );
1083
1084 imagify_check_nonce( 'imagify_rpc_' . $rpc_id, 'imagify_rpc_nonce' );
1085
1086 // Trigger the action we originally wanted.
1087 $_POST['action'] = $action;
1088 unset( $_POST['imagify_rpc_action'], $_POST['imagify_rpc_id'], $_POST['imagify_rpc_nonce'] );
1089
1090 /** This hook is documented in wp-admin/admin-ajax.php. */
1091 do_action( 'wp_ajax_' . $action );
1092 }
1093
1094 /**
1095 * Store the "closed" status of the ads.
1096 *
1097 * @since 1.7
1098 */
1099 public function imagify_dismiss_ad_callback() {
1100 imagify_check_nonce( 'imagify-dismiss-ad' );
1101
1102 if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
1103 imagify_die();
1104 }
1105
1106 if ( empty( $_GET['ad'] ) ) {
1107 imagify_maybe_redirect();
1108 wp_send_json_error();
1109 }
1110
1111 $notice = sanitize_text_field( wp_unslash( $_GET['ad'] ) );
1112
1113 if ( ! $notice ) {
1114 imagify_maybe_redirect();
1115 wp_send_json_error();
1116 }
1117
1118 $user_id = get_current_user_id();
1119 $notices = get_user_meta( $user_id, '_imagify_ignore_ads', true );
1120 $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : [];
1121
1122 if ( isset( $notices[ $notice ] ) ) {
1123 imagify_maybe_redirect();
1124 wp_send_json_success();
1125 }
1126
1127 $notices = array_flip( $notices );
1128 $notices[] = $notice;
1129 $notices = array_filter( $notices );
1130 $notices = array_values( $notices );
1131
1132 update_user_meta( $user_id, '_imagify_ignore_ads', $notices );
1133
1134 imagify_maybe_redirect();
1135 wp_send_json_success();
1136 }
1137
1138
1139 /** ----------------------------------------------------------------------------------------- */
1140 /** VARIOUS HELPERS ========================================================================= */
1141 /** ----------------------------------------------------------------------------------------- */
1142
1143 /**
1144 * Get the submitted optimization level.
1145 *
1146 * @since 1.7
1147 * @since 1.9 Added $method and $parameter parameters.
1148 *
1149 * @param string $method The method used: 'GET' (default), or 'POST'.
1150 * @param string $parameter The name of the parameter to look for.
1151 * @return int
1152 */
1153 public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) {
1154 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
1155 $level = filter_input( $method, $parameter );
1156
1157 if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) {
1158 if ( get_imagify_option( 'lossless' ) ) {
1159 return 0;
1160 }
1161
1162 return get_imagify_option( 'optimization_level' );
1163 }
1164
1165 return (int) $level;
1166 }
1167
1168 /**
1169 * Get the submitted context.
1170 *
1171 * @since 1.9
1172 *
1173 * @param string $method The method used: 'GET' (default), or 'POST'.
1174 * @param string $parameter The name of the parameter to look for.
1175 * @return string
1176 */
1177 public function get_context( $method = 'GET', $parameter = 'context' ) {
1178 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1179 // No context.
1180 return 'noop';
1181 }
1182
1183 $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
1184
1185 return imagify_sanitize_context( $context );
1186 }
1187
1188 /**
1189 * Get the submitted media ID.
1190 *
1191 * @since 1.9
1192 *
1193 * @param string $method The method used: 'GET' (default), or 'POST'.
1194 * @param string $parameter The name of the parameter to look for.
1195 * @return int
1196 */
1197 public function get_media_id( $method = 'GET', $parameter = 'attachment_id' ) {
1198 $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
1199 $media_id = filter_input( $method, $parameter );
1200
1201 if ( ! is_numeric( $media_id ) || $media_id < 0 ) {
1202 return 0;
1203 }
1204
1205 return (int) $media_id;
1206 }
1207
1208 /**
1209 * Get the submitted folder_type.
1210 *
1211 * @since 1.9
1212 *
1213 * @param string $method The method used: 'GET' (default), or 'POST'.
1214 * @param string $parameter The name of the parameter to look for.
1215 *
1216 * @return string
1217 */
1218 public function get_folder_type( $method = 'GET', $parameter = 'folder_type' ) {
1219 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1220 // No folder type.
1221 return 'noop';
1222 }
1223
1224 $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
1225
1226 return $folder_type;
1227 }
1228
1229 /**
1230 * Get the submitted imagify action.
1231 *
1232 * @since 1.9
1233 *
1234 * @param string $method The method used: 'GET' (default), or 'POST'.
1235 * @param string $parameter The name of the parameter to look for.
1236 *
1237 * @return string
1238 */
1239 public function get_imagify_action( $method = 'GET', $parameter = 'imagify_action' ) {
1240 if ( empty( $_POST[ $parameter ] ) && empty( $_GET[ $parameter ] ) ) {
1241 // No action.
1242 return 'optimize';
1243 }
1244
1245 $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
1246
1247 return $action ? $action : 'optimize';
1248 }
1249
1250 /**
1251 * Get the Bulk class name depending on a context.
1252 *
1253 * @since 1.9
1254 *
1255 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
1256 * @return string The Bulk class name.
1257 */
1258 public function get_bulk_class_name( $context ) {
1259 switch ( $context ) {
1260 case 'wp':
1261 $class_name = '\\Imagify\\Bulk\\WP';
1262 break;
1263
1264 case 'custom-folders':
1265 $class_name = '\\Imagify\\Bulk\\CustomFolders';
1266 break;
1267
1268 default:
1269 $class_name = '\\Imagify\\Bulk\\Noop';
1270 }
1271
1272 /**
1273 * Filter the name of the class to use for bulk process.
1274 *
1275 * @since 1.9
1276 *
1277 * @param int $class_name The class name.
1278 * @param string $context The context name.
1279 */
1280 $class_name = apply_filters( 'imagify_bulk_class_name', $class_name, $context );
1281
1282 return '\\' . ltrim( $class_name, '\\' );
1283 }
1284
1285 /**
1286 * Get the Bulk instance depending on a context.
1287 *
1288 * @since 1.9
1289 *
1290 * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
1291 * @return BulkInterface The optimization process instance.
1292 */
1293 public function get_bulk_instance( $context ) {
1294 $class_name = $this->get_bulk_class_name( $context );
1295 return new $class_name();
1296 }
1297
1298 /**
1299 * Check if the user has a valid account and has quota. Die on failure.
1300 *
1301 * @since 1.7
1302 */
1303 public function check_can_optimize() {
1304 if ( ! Imagify_Requirements::is_api_key_valid() ) {
1305 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1306 wp_send_json_error( [ 'message' => 'invalid-api-key' ] );
1307 }
1308
1309 imagify_die( __( 'Your API key is not valid!', 'imagify' ) );
1310 }
1311
1312 if ( Imagify_Requirements::is_over_quota() ) {
1313 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1314 wp_send_json_error( [ 'message' => 'over-quota' ] );
1315 }
1316
1317 imagify_die( __( 'You have used all your credits!', 'imagify' ) );
1318 }
1319 }
1320
1321 /**
1322 * Get a media columns for the "Other Media" page.
1323 *
1324 * @since 1.9
1325 *
1326 * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
1327 * @param object $list_table A Imagify_Files_List_Table object.
1328 * @return array An array of HTML, keyed by column name.
1329 */
1330 public function get_media_columns( $process, $list_table ) {
1331 $item = (object) [ 'process' => $process ];
1332
1333 return [
1334 'folder' => $list_table->get_column( 'folder', $item ),
1335 'optimization' => $list_table->get_column( 'optimization', $item ),
1336 'status' => $list_table->get_column( 'status', $item ),
1337 'optimization_level' => $list_table->get_column( 'optimization_level', $item ),
1338 'actions' => $list_table->get_column( 'actions', $item ),
1339 '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.
1340 ];
1341 }
1342
1343 /**
1344 * After a file optimization, restore, or whatever, redirect the user or output HTML for ajax.
1345 *
1346 * @since 1.7
1347 * @since 1.9 Removed parameter $result.
1348 * @since 1.9 Added $folder in the returned JSON.
1349 *
1350 * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
1351 */
1352 protected function file_optimization_output( $process ) {
1353 $list_table = new Imagify_Files_List_Table(
1354 [
1355 'screen' => 'imagify-files',
1356 ]
1357 );
1358
1359 wp_send_json_success(
1360 [
1361 'columns' => $this->get_media_columns( $process, $list_table ),
1362 ]
1363 );
1364 }
1365 }
1366