PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.7
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.7
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-settings.php

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

663 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
4 /**
5 * Class that handles the plugin settings.
6 *
7 * @since 1.7
8 */
9 class Imagify_Settings {
10
11 /**
12 * Class version.
13 *
14 * @var string
15 * @since 1.7
16 */
17 const VERSION = '1.0';
18
19 /**
20 * The settings group.
21 *
22 * @var string
23 * @since 1.7
24 */
25 protected $settings_group;
26
27 /**
28 * The option name.
29 *
30 * @var string
31 * @since 1.7
32 */
33 protected $option_name;
34
35 /**
36 * The options instance.
37 *
38 * @var object
39 * @since 1.7
40 */
41 protected $options;
42
43 /**
44 * The single instance of the class.
45 *
46 * @var object
47 * @since 1.7
48 * @access protected
49 */
50 protected static $_instance;
51
52 /**
53 * The constructor.
54 *
55 * @since 1.7
56 * @author Grégory Viguier
57 * @access protected
58 */
59 protected function __construct() {
60 $this->options = Imagify_Options::get_instance();
61 $this->option_name = $this->options->get_option_name();
62 $this->settings_group = IMAGIFY_SLUG;
63 }
64
65 /**
66 * Get the main Instance.
67 *
68 * @since 1.7
69 * @author Grégory Viguier
70 * @access public
71 *
72 * @return object Main instance.
73 */
74 public static function get_instance() {
75 if ( ! isset( self::$_instance ) ) {
76 self::$_instance = new self();
77 }
78
79 return self::$_instance;
80 }
81
82 /**
83 * Launch the hooks.
84 *
85 * @since 1.7
86 * @author Grégory Viguier
87 * @access public
88 */
89 public function init() {
90 add_filter( 'sanitize_option_' . $this->option_name, array( $this, 'populate_values_on_save' ), 5 );
91 add_action( 'admin_init', array( $this, 'register' ) );
92 add_filter( 'option_page_capability_' . $this->settings_group, array( $this, 'get_capability' ) );
93
94 if ( imagify_is_active_for_network() ) {
95 add_filter( 'pre_update_site_option_' . $this->option_name, array( $this, 'maybe_set_redirection' ), 10, 2 );
96 add_action( 'update_site_option_' . $this->option_name, array( $this, 'after_save_network_options' ), 10, 3 );
97 add_action( 'admin_post_update', array( $this, 'update_site_option_on_network' ) );
98 } else {
99 add_filter( 'pre_update_option_' . $this->option_name, array( $this, 'maybe_set_redirection' ), 10, 2 );
100 add_action( 'update_option_' . $this->option_name, array( $this, 'after_save_options' ), 10, 2 );
101 }
102 }
103
104
105 /** ----------------------------------------------------------------------------------------- */
106 /** VARIOUS HELPERS ========================================================================= */
107 /** ----------------------------------------------------------------------------------------- */
108
109 /**
110 * Get the name of the settings group.
111 *
112 * @since 1.7
113 * @author Grégory Viguier
114 * @access public
115 *
116 * @return string
117 */
118 public function get_settings_group() {
119 return $this->settings_group;
120 }
121
122 /**
123 * Get the URL to use as form action.
124 *
125 * @since 1.7
126 * @author Grégory Viguier
127 * @access public
128 *
129 * @return string
130 */
131 public function get_form_action() {
132 return imagify_is_active_for_network() ? admin_url( 'admin-post.php' ) : admin_url( 'options.php' );
133 }
134
135 /**
136 * Tell if we're submitting the settings form.
137 *
138 * @since 1.7
139 * @author Grégory Viguier
140 * @access public
141 *
142 * @return bool
143 */
144 public function is_form_submit() {
145 return filter_input( INPUT_POST, 'option_page', FILTER_SANITIZE_STRING ) === $this->settings_group && filter_input( INPUT_POST, 'action', FILTER_SANITIZE_STRING ) === 'update';
146 }
147
148
149 /** ----------------------------------------------------------------------------------------- */
150 /** ON FORM SUBMIT ========================================================================== */
151 /** ----------------------------------------------------------------------------------------- */
152
153 /**
154 * On form submit, handle some specific values.
155 * This must be hooked before Imagify_Options::sanitize_and_validate_on_update().
156 *
157 * @since 1.7
158 * @author Grégory Viguier
159 * @access public
160 *
161 * @param array $values The option values.
162 * @return array
163 */
164 public function populate_values_on_save( $values ) {
165 if ( ! $this->is_form_submit() ) {
166 return $values;
167 }
168
169 $values = is_array( $values ) ? $values : array();
170
171 /**
172 * Disabled thumbnail sizes.
173 */
174 $values = $this->populate_disallowed_sizes( $values );
175
176 /**
177 * Custom folders.
178 */
179 $values = $this->populate_custom_folders( $values );
180
181 return $values;
182 }
183
184 /**
185 * On form submit, handle disallowed thumbnail sizes.
186 *
187 * @since 1.7
188 * @access protected
189 * @author Grégory Viguier
190 *
191 * @param array $values The option values.
192 * @return array
193 */
194 protected function populate_disallowed_sizes( $values ) {
195 $values['disallowed-sizes'] = array();
196
197 if ( isset( $values['disallowed-sizes-reversed'] ) && is_array( $values['disallowed-sizes-reversed'] ) ) {
198 $checked = ! empty( $values['disallowed-sizes-checked'] ) && is_array( $values['disallowed-sizes-checked'] ) ? array_flip( $values['disallowed-sizes-checked'] ) : array();
199
200 if ( ! empty( $values['disallowed-sizes-reversed'] ) ) {
201 foreach ( $values['disallowed-sizes-reversed'] as $size_key ) {
202 if ( ! isset( $checked[ $size_key ] ) ) {
203 // The checkbox is not checked: the size is disabled.
204 $values['disallowed-sizes'][ $size_key ] = 1;
205 }
206 }
207 }
208 }
209
210 unset( $values['disallowed-sizes-reversed'], $values['disallowed-sizes-checked'] );
211
212 return $values;
213 }
214
215 /**
216 * On form submit, handle the custom folders.
217 *
218 * @since 1.7
219 * @access protected
220 * @author Grégory Viguier
221 *
222 * @param array $values The option values.
223 * @return array
224 */
225 protected function populate_custom_folders( $values ) {
226 if ( ! imagify_can_optimize_custom_folders() ) {
227 // The databases are not ready or the user has not the permission.
228 unset( $values['custom_folders'] );
229 return $values;
230 }
231
232 if ( ! isset( $values['custom_folders'] ) ) {
233 // No selected folders: set them all inactive.
234 Imagify_Custom_Folders::deactivate_all_folders();
235 // Remove files that are in inactive folders and are not optimized.
236 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
237 // Remove empty inactive folders.
238 Imagify_Custom_Folders::remove_empty_inactive_folders();
239
240 return $values;
241 }
242
243 if ( ! is_array( $values['custom_folders'] ) ) {
244 // Invalid value.
245 unset( $values['custom_folders'] );
246 return $values;
247 }
248
249 $selected = array_filter( $values['custom_folders'] );
250 unset( $values['custom_folders'] );
251
252 if ( ! $selected ) {
253 // No selected folders: set them all inactive.
254 Imagify_Custom_Folders::deactivate_all_folders();
255 // Remove files that are in inactive folders and are not optimized.
256 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
257 // Remove empty inactive folders.
258 Imagify_Custom_Folders::remove_empty_inactive_folders();
259
260 return $values;
261 }
262
263 // Normalize the paths, remove duplicates, and remove sub-paths.
264 $selected = array_map( 'sanitize_text_field', $selected );
265 $selected = array_map( 'wp_normalize_path', $selected );
266 $selected = array_map( 'trailingslashit', $selected );
267 $selected = array_flip( array_flip( $selected ) );
268 $selected = Imagify_Custom_Folders::remove_sub_paths( $selected );
269
270 // Remove the active status from the folders that are not selected.
271 Imagify_Custom_Folders::deactivate_not_selected_folders( $selected );
272
273 // Add the active status to the folders that are selected (and already in the DB).
274 $selected = Imagify_Custom_Folders::activate_selected_folders( $selected );
275
276 // If we still have paths here, they need to be added to the DB with an active status.
277 Imagify_Custom_Folders::insert_folders( $selected );
278
279 // Remove files that are in inactive folders and are not optimized.
280 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
281
282 // Reassign files to active folders.
283 Imagify_Custom_Folders::reassign_inactive_files();
284
285 // Remove empty inactive folders.
286 Imagify_Custom_Folders::remove_empty_inactive_folders();
287
288 return $values;
289 }
290
291
292 /** ----------------------------------------------------------------------------------------- */
293 /** SETTINGS API ============================================================================ */
294 /** ----------------------------------------------------------------------------------------- */
295
296 /**
297 * Add Imagify' settings to the settings API whitelist.
298 *
299 * @since 1.7
300 * @author Grégory Viguier
301 * @access public
302 */
303 public function register() {
304 register_setting( $this->settings_group, $this->option_name );
305 }
306
307 /**
308 * Set the user capacity needed to save Imagify's main options from the settings page.
309 *
310 * @since 1.7
311 * @author Grégory Viguier
312 * @access public
313 */
314 public function get_capability() {
315 return imagify_get_capacity();
316 }
317
318 /**
319 * If the user clicked the "Save & Go to Bulk Optimizer" button, set a redirection to the bulk optimizer.
320 * We use this hook because it can be triggered even if the option value hasn't changed.
321 *
322 * @since 1.7
323 * @author Grégory Viguier
324 * @access public
325 *
326 * @param mixed $value The new, unserialized option value.
327 * @param mixed $old_value The old option value.
328 * @return mixed The option value.
329 */
330 public function maybe_set_redirection( $value, $old_value ) {
331 if ( isset( $_POST['submit-goto-bulk'] ) ) { // WPCS: CSRF ok.
332 $_REQUEST['_wp_http_referer'] = esc_url_raw( get_admin_url( get_current_blog_id(), 'upload.php?page=imagify-bulk-optimization' ) );
333 }
334
335 return $value;
336 }
337
338 /**
339 * Used to launch some actions after saving the network options.
340 *
341 * @since 1.7
342 * @author Grégory Viguier
343 * @access public
344 *
345 * @param string $option Name of the network option.
346 * @param mixed $value Current value of the network option.
347 * @param mixed $old_value Old value of the network option.
348 */
349 public function after_save_network_options( $option, $value, $old_value ) {
350 $this->after_save_options( $old_value, $value );
351 }
352
353 /**
354 * Used to launch some actions after saving the options.
355 *
356 * @since 1.7
357 * @author Grégory Viguier
358 * @access public
359 *
360 * @param mixed $old_value The old option value.
361 * @param mixed $value The new option value.
362 */
363 public function after_save_options( $old_value, $value ) {
364 if ( ! $value || isset( $old_value['api_key'], $value['api_key'] ) && $old_value['api_key'] === $value['api_key'] ) {
365 return;
366 }
367
368 if ( is_wp_error( get_imagify_user() ) ) {
369 Imagify_Notices::renew_notice( 'wrong-api-key' );
370 delete_site_transient( 'imagify_check_licence_1' );
371 } else {
372 Imagify_Notices::dismiss_notice( 'wrong-api-key' );
373 }
374 }
375
376 /**
377 * `options.php` does not handle network options. Let's use `admin-post.php` for multisite installations.
378 *
379 * @since 1.7
380 * @author Grégory Viguier
381 * @access public
382 */
383 public function update_site_option_on_network() {
384 if ( empty( $_POST['option_page'] ) || $_POST['option_page'] !== $this->settings_group ) { // WPCS: CSRF ok.
385 return;
386 }
387
388 $capability = apply_filters( 'option_page_capability_' . $this->settings_group, 'manage_network_options' );
389
390 if ( ! current_user_can( $capability ) ) {
391 imagify_die();
392 }
393
394 imagify_check_nonce( $this->settings_group . '-options' );
395
396 $whitelist_options = apply_filters( 'whitelist_options', array() );
397
398 if ( ! isset( $whitelist_options[ $this->settings_group ] ) ) {
399 imagify_die( __( '<strong>ERROR</strong>: options page not found.' ) );
400 }
401
402 $options = $whitelist_options[ $this->settings_group ];
403
404 if ( $options ) {
405 foreach ( $options as $option ) {
406 $option = trim( $option );
407 $value = null;
408
409 if ( isset( $_POST[ $option ] ) ) {
410 $value = $_POST[ $option ];
411 if ( ! is_array( $value ) ) {
412 $value = trim( $value );
413 }
414 $value = wp_unslash( $value );
415 }
416
417 update_site_option( $option, $value );
418 }
419 }
420
421 /**
422 * Redirect back to the settings page that was submitted.
423 */
424 imagify_maybe_redirect( false, array( 'settings-updated' => 'true' ) );
425 }
426
427
428 /** ----------------------------------------------------------------------------------------- */
429 /** FIELDS ================================================================================== */
430 /** ----------------------------------------------------------------------------------------- */
431
432 /**
433 * Display a single checkbox.
434 *
435 * @since 1.7
436 * @author Grégory Viguier
437 * @access public
438 *
439 * @param array $args Arguments:
440 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
441 * {label} string The label to use.
442 * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
443 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
444 * {current_value} int|bool USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
445 */
446 public function field_checkbox( $args ) {
447 $args = array_merge( array(
448 'option_name' => '',
449 'label' => '',
450 'info' => '',
451 'attributes' => array(),
452 // To not use the plugin settings: use an integer.
453 'current_value' => null,
454 ), $args );
455
456 if ( ! $args['option_name'] || ! $args['label'] ) {
457 return;
458 }
459
460 if ( is_numeric( $args['current_value'] ) || is_bool( $args['current_value'] ) ) {
461 // We don't use the plugin settings.
462 $current_value = (int) (bool) $args['current_values'];
463 } else {
464 // This is a normal plugin setting.
465 $current_value = $this->options->get( $args['option_name'] );
466 }
467
468 $option_name_class = sanitize_html_class( $args['option_name'] );
469 $attributes = array(
470 'name' => $this->option_name . '[' . $args['option_name'] . ']',
471 'id' => 'imagify_' . $option_name_class,
472 );
473
474 if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
475 $attributes['aria-describedby'] = 'describe-' . $option_name_class;
476 }
477
478 $attributes = array_merge( $attributes, $args['attributes'] );
479 $args['attributes'] = self::build_attributes( $attributes );
480 ?>
481 <input type="checkbox" value="1" <?php checked( $current_value, 1 ); ?><?php echo $args['attributes']; ?> />
482 <!-- Empty onclick attribute to make clickable labels on iTruc & Mac -->
483 <label for="<?php echo $attributes['id']; ?>" onclick=""><?php echo $args['label']; ?></label>
484 <?php
485 if ( ! $args['info'] ) {
486 return;
487 }
488 ?>
489 <span id="<?php echo $attributes['aria-describedby']; ?>" class="imagify-info">
490 <span class="dashicons dashicons-info"></span>
491 <?php echo $args['info']; ?>
492 </span>
493 <?php
494 }
495
496 /**
497 * Display a checkbox group.
498 *
499 * @since 1.7
500 * @author Grégory Viguier
501 * @access public
502 *
503 * @param array $args Arguments:
504 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
505 * {legend} string Label to use for the <legend> tag.
506 * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
507 * {disabled_values} array Values to be disabled. Values are the array keys.
508 * {reverse_check} bool If true, the values that will be stored in the option are the ones that are unchecked. It requires special treatment when saving (detect what values are unchecked).
509 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
510 * {current_values} array USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
511 */
512 public function field_checkbox_list( $args ) {
513
514 $args = array_merge( array(
515 'option_name' => '',
516 'legend' => '',
517 'values' => array(),
518 'disabled_values' => array(),
519 'reverse_check' => false,
520 'attributes' => array(),
521 // To not use the plugin settings: use an array.
522 'current_values' => false,
523 ), $args );
524
525 if ( ! $args['option_name'] || ! $args['values'] ) {
526 return;
527 }
528
529 if ( is_array( $args['current_values'] ) ) {
530 // We don't use the plugin settings.
531 $current_values = $args['current_values'];
532 } else {
533 // This is a normal plugin setting.
534 $current_values = $this->options->get( $args['option_name'] );
535 }
536
537 $option_name_class = sanitize_html_class( $args['option_name'] );
538 $attributes = array_merge( array(
539 'name' => $this->option_name . '[' . $args['option_name'] . ( $args['reverse_check'] ? '-checked' : '' ) . '][]',
540 'id' => 'imagify_' . $option_name_class . '_%s',
541 'class' => 'imagify-row-check',
542 ), $args['attributes'] );
543
544 $id_attribute = $attributes['id'];
545 unset( $attributes['id'] );
546 $args['attributes'] = self::build_attributes( $attributes );
547
548 $current_values = array_diff_key( $current_values, $args['disabled_values'] );
549 $nb_of_values = count( $args['values'] );
550 $display_check_all = $nb_of_values > 3;
551 $nb_of_checked = 0;
552 ?>
553 <fieldset class="imagify-check-group<?php echo $nb_of_values > 5 ? ' imagify-is-scrollable' : ''; ?>">
554 <?php
555 if ( $args['legend'] ) {
556 ?>
557 <legend class="screen-reader-text"><?php echo $args['legend']; ?></legend>
558 <?php
559 }
560
561 foreach ( $args['values'] as $value => $label ) {
562 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
563 $disabled = isset( $args['disabled_values'][ $value ] );
564
565 if ( $args['reverse_check'] ) {
566 $checked = ! $disabled && ! isset( $current_values[ $value ] );
567 } else {
568 $checked = ! $disabled && isset( $current_values[ $value ] );
569 }
570
571 $nb_of_checked = $checked ? $nb_of_checked + 1 : $nb_of_checked;
572
573 if ( $args['reverse_check'] ) {
574 echo '<input type="hidden" name="' . $this->option_name . '[' . $args['option_name'] . '-reversed][]" value="' . esc_attr( $value ) . '" />';
575 }
576 ?>
577 <p>
578 <input type="checkbox" value="<?php echo esc_attr( $value ); ?>" id="<?php echo $input_id; ?>"<?php echo $args['attributes']; ?> <?php checked( $checked ); ?> <?php disabled( $disabled ); ?>/>
579 <label for="<?php echo $input_id; ?>" onclick=""><?php echo $label; ?></label>
580 </p>
581 <?php
582 }
583 ?>
584 </fieldset>
585 <?php
586 if ( $display_check_all ) {
587 if ( $args['reverse_check'] ) {
588 $all_checked = ! array_intersect_key( $args['values'], $current_values );
589 } else {
590 $all_checked = ! array_diff_key( $args['values'], $current_values );
591 }
592 ?>
593 <p class="hide-if-no-js imagify-select-all-buttons">
594 <button type="button" class="imagify-link-like imagify-select-all<?php echo $all_checked ? ' imagify-is-inactive" aria-disabled="true' : ''; ?>" data-action="select"><?php _e( 'Select All', 'imagify' ); ?></button>
595
596 <span class="imagify-pipe"></span>
597
598 <button type="button" class="imagify-link-like imagify-select-all<?php echo $nb_of_checked ? '' : ' imagify-is-inactive" aria-disabled="true'; ?>" data-action="unselect"><?php _e( 'Unselect All', 'imagify' ); ?></button>
599 </p>
600 <?php
601 }
602 }
603
604
605 /** ----------------------------------------------------------------------------------------- */
606 /** FIELD VALUES ============================================================================ */
607 /** ----------------------------------------------------------------------------------------- */
608
609 /**
610 * Get the thumbnail sizes.
611 *
612 * @since 1.7
613 * @author Grégory Viguier
614 * @access public
615 *
616 * @return array A list of thumbnail sizes in the form of 'medium' => 'medium - 300 × 300'.
617 */
618 public static function get_thumbnail_sizes() {
619 static $sizes;
620
621 if ( isset( $sizes ) ) {
622 return $sizes;
623 }
624
625 $sizes = get_imagify_thumbnail_sizes();
626
627 foreach ( $sizes as $size_key => $size_data ) {
628 $sizes[ $size_key ] = sprintf( '%s - %d &times; %d', esc_html( stripslashes( $size_data['name'] ) ), $size_data['width'], $size_data['height'] );
629 }
630
631 return $sizes;
632 }
633
634
635 /** ----------------------------------------------------------------------------------------- */
636 /** TOOLS =================================================================================== */
637 /** ----------------------------------------------------------------------------------------- */
638
639 /**
640 * Create HTML attributes from an array.
641 *
642 * @since 1.7
643 * @access public
644 * @author Grégory Viguier
645 *
646 * @param array $attributes A list of attribute pairs.
647 * @return string HTML attributes.
648 */
649 public static function build_attributes( $attributes ) {
650 if ( ! $attributes || ! is_array( $attributes ) ) {
651 return '';
652 }
653
654 $out = '';
655
656 foreach ( $attributes as $attribute => $value ) {
657 $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
658 }
659
660 return $out;
661 }
662 }
663