PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.3
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.3
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / inc / classes / class-imagify-settings.php

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

981 lines 31.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 use Imagify\Notices\Notices;
3 use Imagify\Traits\InstanceGetterTrait;
4
5 /**
6 * Class that handles the plugin settings.
7 *
8 * @since 1.7
9 */
10 class Imagify_Settings {
11 use InstanceGetterTrait;
12
13 /**
14 * Class version.
15 *
16 * @since 1.7
17 * @var string
18 */
19 const VERSION = '1.0.1';
20
21 /**
22 * The settings group.
23 *
24 * @since 1.7
25 * @var string
26 */
27 protected $settings_group;
28
29 /**
30 * The option name.
31 *
32 * @since 1.7
33 * @var string
34 */
35 protected $option_name;
36
37 /**
38 * The options instance.
39 *
40 * @since 1.7
41 * @var object
42 */
43 protected $options;
44
45 /**
46 * The constructor.
47 *
48 * @since 1.7
49 */
50 protected function __construct() {
51 $this->options = Imagify_Options::get_instance();
52 $this->option_name = $this->options->get_option_name();
53 $this->settings_group = IMAGIFY_SLUG;
54 }
55
56 /**
57 * Launch the hooks.
58 *
59 * @since 1.7
60 */
61 public function init() {
62 add_filter( 'sanitize_option_' . $this->option_name, [ $this, 'populate_values_on_save' ], 5 );
63 add_action( 'admin_init', [ $this, 'register' ] );
64 add_filter( 'option_page_capability_' . $this->settings_group, [ $this, 'get_capability' ] );
65
66 if ( imagify_is_active_for_network() ) {
67 add_filter( 'pre_update_site_option_' . $this->option_name, [ $this, 'maybe_set_redirection' ], 10, 2 );
68 add_action( 'update_site_option_' . $this->option_name, [ $this, 'after_save_network_options' ], 10, 3 );
69 add_action( 'admin_post_update', [ $this, 'update_site_option_on_network' ] );
70 } else {
71 add_filter( 'pre_update_option_' . $this->option_name, [ $this, 'maybe_set_redirection' ], 10, 2 );
72 add_action( 'update_option_' . $this->option_name, [ $this, 'after_save_options' ], 10, 2 );
73 }
74 }
75
76
77 /** ----------------------------------------------------------------------------------------- */
78 /** VARIOUS HELPERS ========================================================================= */
79 /** ----------------------------------------------------------------------------------------- */
80
81 /**
82 * Get the name of the settings group.
83 *
84 * @since 1.7
85 * @return string
86 */
87 public function get_settings_group() {
88 return $this->settings_group;
89 }
90
91 /**
92 * Get the URL to use as form action.
93 *
94 * @since 1.7
95 * @return string
96 */
97 public function get_form_action() {
98 return imagify_is_active_for_network() ? admin_url( 'admin-post.php' ) : admin_url( 'options.php' );
99 }
100
101 /**
102 * Tell if we're submitting the settings form.
103 *
104 * @since 1.7
105 * @return bool
106 */
107 public function is_form_submit() {
108 if ( ! isset( $_POST['option_page'], $_POST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
109 return false;
110 }
111
112 return sanitize_text_field( wp_unslash( $_POST['option_page'] ) ) === $this->settings_group && sanitize_text_field( wp_unslash( $_POST['action'] ) ) === 'update'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
113 }
114
115 /** ----------------------------------------------------------------------------------------- */
116 /** ON FORM SUBMIT ========================================================================== */
117 /** ----------------------------------------------------------------------------------------- */
118
119 /**
120 * On form submit, handle some specific values.
121 * This must be hooked before Imagify_Options::sanitize_and_validate_on_update().
122 *
123 * @since 1.7
124 *
125 * @param array $values The option values.
126 *
127 * @return array
128 */
129 public function populate_values_on_save( $values ) {
130 if ( ! $this->is_form_submit() ) {
131 return $values;
132 }
133
134 $values = is_array( $values ) ? $values : [];
135
136 /**
137 * Disabled thumbnail sizes.
138 */
139 $values = $this->populate_disallowed_sizes( $values );
140
141 /**
142 * Custom folders.
143 */
144 $values = $this->populate_custom_folders( $values );
145
146 /**
147 * Filter settings when saved via the settings page.
148 *
149 * @since 1.9
150 *
151 * @param array $values The option values.
152 */
153 $values = apply_filters( 'imagify_settings_on_save', $values );
154
155 return (array) $values;
156 }
157
158 /**
159 * On form submit, handle disallowed thumbnail sizes.
160 *
161 * @since 1.7
162 *
163 * @param array $values The option values.
164 *
165 * @return array
166 */
167 protected function populate_disallowed_sizes( $values ) {
168 $values['disallowed-sizes'] = [];
169
170 if ( isset( $values['disallowed-sizes-reversed'] ) && is_array( $values['disallowed-sizes-reversed'] ) ) {
171 $checked = ! empty( $values['disallowed-sizes-checked'] ) && is_array( $values['disallowed-sizes-checked'] ) ? array_flip( $values['disallowed-sizes-checked'] ) : [];
172
173 if ( ! empty( $values['disallowed-sizes-reversed'] ) ) {
174 foreach ( $values['disallowed-sizes-reversed'] as $size_key ) {
175 if ( ! isset( $checked[ $size_key ] ) ) {
176 // The checkbox is not checked: the size is disabled.
177 $values['disallowed-sizes'][ $size_key ] = 1;
178 }
179 }
180 }
181 }
182
183 unset( $values['disallowed-sizes-reversed'], $values['disallowed-sizes-checked'] );
184
185 return $values;
186 }
187
188 /**
189 * On form submit, handle the custom folders.
190 *
191 * @since 1.7
192 *
193 * @param array $values The option values.
194 *
195 * @return array
196 */
197 protected function populate_custom_folders( $values ) {
198 if ( ! imagify_can_optimize_custom_folders() ) {
199 // The databases are not ready or the user has not the permission.
200 unset( $values['custom_folders'] );
201
202 return $values;
203 }
204
205 if ( ! isset( $values['custom_folders'] ) ) {
206 // No selected folders: set them all inactive.
207 Imagify_Custom_Folders::deactivate_all_folders();
208 // Remove files that are in inactive folders and are not optimized.
209 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
210 // Remove empty inactive folders.
211 Imagify_Custom_Folders::remove_empty_inactive_folders();
212
213 return $values;
214 }
215
216 if ( ! is_array( $values['custom_folders'] ) ) {
217 // Invalid value.
218 unset( $values['custom_folders'] );
219
220 return $values;
221 }
222
223 $selected = array_filter( $values['custom_folders'] );
224 unset( $values['custom_folders'] );
225
226 if ( ! $selected ) {
227 // No selected folders: set them all inactive.
228 Imagify_Custom_Folders::deactivate_all_folders();
229 // Remove files that are in inactive folders and are not optimized.
230 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
231 // Remove empty inactive folders.
232 Imagify_Custom_Folders::remove_empty_inactive_folders();
233
234 return $values;
235 }
236
237 // Normalize the paths, remove duplicates, and remove sub-paths.
238 $selected = array_map( 'sanitize_text_field', $selected );
239 $selected = array_map( 'wp_normalize_path', $selected );
240 $selected = array_map( 'trailingslashit', $selected );
241 $selected = array_flip( array_flip( $selected ) );
242 $selected = Imagify_Custom_Folders::remove_sub_paths( $selected );
243
244 // Remove the active status from the folders that are not selected.
245 Imagify_Custom_Folders::deactivate_not_selected_folders( $selected );
246
247 // Add the active status to the folders that are selected (and already in the DB).
248 $selected = Imagify_Custom_Folders::activate_selected_folders( $selected );
249
250 // If we still have paths here, they need to be added to the DB with an active status.
251 Imagify_Custom_Folders::insert_folders( $selected );
252
253 // Remove files that are in inactive folders and are not optimized.
254 Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
255
256 // Reassign files to active folders.
257 Imagify_Custom_Folders::reassign_inactive_files();
258
259 // Remove empty inactive folders.
260 Imagify_Custom_Folders::remove_empty_inactive_folders();
261
262 return $values;
263 }
264
265
266 /** ----------------------------------------------------------------------------------------- */
267 /** SETTINGS API ============================================================================ */
268 /** ----------------------------------------------------------------------------------------- */
269
270 /**
271 * Add Imagify' settings to the settings API whitelist.
272 *
273 * @since 1.7
274 */
275 public function register() {
276 register_setting( $this->settings_group, $this->option_name );
277 }
278
279 /**
280 * Set the user capacity needed to save Imagify's main options from the settings page.
281 *
282 * @since 1.7
283 */
284 public function get_capability() {
285 return imagify_get_context( 'wp' )->get_capacity( 'manage' );
286 }
287
288 /**
289 * If the user clicked the "Save & Go to Bulk Optimizer" button, set a redirection to the bulk optimizer.
290 * We use this hook because it can be triggered even if the option value hasn't changed.
291 *
292 * @since 1.7
293 *
294 * @param mixed $value The new, unserialized option value.
295 * @param mixed $old_value The old option value.
296 *
297 * @return mixed The option value.
298 */
299 public function maybe_set_redirection( $value, $old_value ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
300 if ( isset( $_POST['submit-goto-bulk'] ) ) { // WPCS: CSRF ok.
301 $_REQUEST['_wp_http_referer'] = esc_url_raw( get_admin_url( get_current_blog_id(), 'upload.php?page=imagify-bulk-optimization' ) );
302 }
303
304 return $value;
305 }
306
307 /**
308 * Used to launch some actions after saving the network options.
309 *
310 * @since 1.7
311 *
312 * @param string $option Name of the network option.
313 * @param mixed $value Current value of the network option.
314 * @param mixed $old_value Old value of the network option.
315 */
316 public function after_save_network_options( $option, $value, $old_value ) {
317 $this->after_save_options( $old_value, $value );
318 }
319
320 /**
321 * Used to launch some actions after saving the options.
322 *
323 * @since 1.7
324 *
325 * @param mixed $old_value The old option value.
326 * @param mixed $value The new option value.
327 */
328 public function after_save_options( $old_value, $value ) {
329 $old_key = isset( $old_value['api_key'] ) ? $old_value['api_key'] : '';
330 $new_key = isset( $value['api_key'] ) ? $value['api_key'] : '';
331
332 if ( $old_key === $new_key ) {
333 return;
334 }
335
336 delete_transient( 'imagify_user_cache' );
337
338 // Handle API key validation cache and notices.
339 if ( Imagify_Requirements::is_api_key_valid( true ) ) {
340 Notices::dismiss_notice( 'wrong-api-key' );
341 } else {
342 Notices::renew_notice( 'wrong-api-key' );
343 }
344 }
345
346 /**
347 * `options.php` does not handle network options. Let's use `admin-post.php` for multisite installations.
348 *
349 * @since 1.9.11 deprecate 'whitelist_options' filter.
350 * @since 1.7
351 *
352 * @return void
353 */
354 public function update_site_option_on_network() {
355 global $wp_version;
356
357 if ( empty( $_POST['option_page'] ) || $_POST['option_page'] !== $this->settings_group ) { // WPCS: CSRF ok.
358 return;
359 }
360
361 /** This filter is documented in /wp-admin/options.php. */
362 $capability = apply_filters( 'option_page_capability_' . $this->settings_group, 'manage_network_options' );
363
364 if ( ! current_user_can( $capability ) ) {
365 imagify_die();
366
367 return;
368 }
369
370 if ( ! imagify_check_nonce( $this->settings_group . '-options' ) ) {
371 return;
372 }
373
374 if ( version_compare( $wp_version, '5.5', '>=' ) ) {
375 $allowed_options = apply_filters_deprecated(
376 'whitelist_options',
377 [ [] ],
378 '5.5.0',
379 'allowed_options',
380 __( 'Please consider writing more inclusive code.' )
381 );
382 } else {
383 $allowed_options = apply_filters( 'whitelist_options', [] );
384 }
385
386 $allowed_options = apply_filters( 'allowed_options', $allowed_options );
387
388 if ( ! isset( $allowed_options[ $this->settings_group ] ) ) {
389 imagify_die( __( '<strong>ERROR</strong>: options page not found.' ) );
390
391 return;
392 }
393
394 $options = $allowed_options[ $this->settings_group ];
395
396 if ( $options ) {
397 foreach ( $options as $option ) {
398 $option = trim( $option );
399 $value = null;
400
401 if ( isset( $_POST[ $option ] ) ) {
402 $value = wp_unslash( $_POST[ $option ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
403 if ( ! is_array( $value ) ) {
404 $value = trim( $value );
405 }
406 $value = wp_unslash( $value );
407 }
408
409 update_site_option( $option, $value );
410 }
411 }
412
413 /**
414 * Redirect back to the settings page that was submitted.
415 */
416 imagify_maybe_redirect( false, [ 'settings-updated' => 'true' ] );
417 }
418
419
420 /** ----------------------------------------------------------------------------------------- */
421 /** FIELDS ================================================================================== */
422 /** ----------------------------------------------------------------------------------------- */
423
424 /**
425 * Display a single checkbox.
426 *
427 * @since 1.7
428 *
429 * @param array $args Arguments:
430 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
431 * {label} string The label to use.
432 * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
433 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
434 * {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.
435 */
436 public function field_checkbox( $args ) {
437 $args = array_merge(
438 [
439 'option_name' => '',
440 'label' => '',
441 'info' => '',
442 'attributes' => [],
443 // To not use the plugin settings: use an integer.
444 'current_value' => null,
445 ],
446 $args
447 );
448
449 if ( ! $args['option_name'] || ! $args['label'] ) {
450 return;
451 }
452
453 if ( is_numeric( $args['current_value'] ) || is_bool( $args['current_value'] ) ) {
454 // We don't use the plugin settings.
455 $current_value = (int) (bool) $args['current_value'];
456 } else {
457 // This is a normal plugin setting.
458 $current_value = $this->options->get( $args['option_name'] );
459 }
460
461 $option_name_class = sanitize_html_class( $args['option_name'] );
462 $attributes = [
463 'name' => $this->option_name . '[' . $args['option_name'] . ']',
464 'id' => 'imagify_' . $option_name_class,
465 ];
466
467 if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
468 $attributes['aria-describedby'] = 'describe-' . $option_name_class;
469 }
470
471 $attributes = array_merge( $attributes, $args['attributes'] );
472 $args['attributes'] = self::build_attributes( $attributes );
473 ?>
474 <input type="checkbox" value="1" <?php checked( $current_value, 1 ); ?> <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
475 <!-- Empty onclick attribute to make clickable labels on iTruc & Mac -->
476 <label for="<?php echo esc_attr( $attributes['id'] ); ?>" onclick="">
477 <?php self::print_text( $args['label'] ); ?>
478 </label>
479 <?php
480 if ( ! $args['info'] ) {
481 return;
482 }
483 ?>
484 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
485 <span class="dashicons dashicons-info"></span>
486 <?php self::print_text( $args['info'] ); ?>
487 </span>
488 <?php
489 }
490
491 /**
492 * Display a checkbox group.
493 *
494 * @since 1.7
495 *
496 * @param array $args Arguments:
497 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
498 * {legend} string Label to use for the <legend> tag.
499 * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
500 * {disabled_values} array Values to be disabled. Values are the array keys.
501 * {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).
502 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
503 * {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.
504 */
505 public function field_checkbox_list( $args ) {
506 $args = array_merge(
507 [
508 'option_name' => '',
509 'legend' => '',
510 'values' => [],
511 'disabled_values' => [],
512 'reverse_check' => false,
513 'attributes' => [],
514 // To not use the plugin settings: use an array.
515 'current_values' => false,
516 ],
517 $args
518 );
519
520 if ( ! $args['option_name'] || ! $args['values'] ) {
521 return;
522 }
523
524 if ( is_array( $args['current_values'] ) ) {
525 // We don't use the plugin settings.
526 $current_values = $args['current_values'];
527 } else {
528 // This is a normal plugin setting.
529 $current_values = $this->options->get( $args['option_name'] );
530 }
531
532 $option_name_class = sanitize_html_class( $args['option_name'] );
533 $attributes = array_merge(
534 [
535 'name' => $this->option_name . '[' . $args['option_name'] . ( $args['reverse_check'] ? '-checked' : '' ) . '][]',
536 'id' => 'imagify_' . $option_name_class . '_%s',
537 'class' => 'imagify-row-check',
538 ],
539 $args['attributes']
540 );
541
542 $id_attribute = $attributes['id'];
543 unset( $attributes['id'] );
544 $args['attributes'] = self::build_attributes( $attributes );
545
546 $current_values = array_diff_key( $current_values, $args['disabled_values'] );
547 $nb_of_values = count( $args['values'] );
548 $display_check_all = $nb_of_values > 3;
549 $nb_of_checked = 0;
550 ?>
551 <fieldset class="imagify-check-group <?php echo $nb_of_values > 5 ? ' imagify-is-scrollable' : ''; ?>">
552 <?php
553 if ( $args['legend'] ) {
554 ?>
555 <legend class="screen-reader-text">
556 <?php self::print_text( $args['legend'] ); ?>
557 </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="' . esc_attr( $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 esc_attr( $input_id ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <?php checked( $checked ); ?> <?php disabled( $disabled ); ?> />
579 <label for="<?php echo esc_attr( $input_id ); ?>" onclick="">
580 <?php self::print_text( $label ); ?>
581 </label>
582 </p>
583 <?php
584 }
585 ?>
586 </fieldset>
587 <?php
588 if ( $display_check_all ) {
589 if ( $args['reverse_check'] ) {
590 $all_checked = ! array_intersect_key( $args['values'], $current_values );
591 } else {
592 $all_checked = ! array_diff_key( $args['values'], $current_values );
593 }
594 ?>
595 <p class="hide-if-no-js imagify-select-all-buttons">
596 <button type="button" class="imagify-link-like imagify-select-all <?php echo $all_checked ? ' imagify-is-inactive" aria-disabled="true' : ''; ?>" data-action="select">
597 <?php
598 esc_html_e( 'Select All', 'imagify' );
599 ?>
600 </button>
601
602 <span class="imagify-pipe"></span>
603
604 <button type="button" class="imagify-link-like imagify-select-all <?php echo $nb_of_checked ? '' : ' imagify-is-inactive" aria-disabled="true'; ?> " data-action="unselect">
605 <?php
606 esc_html_e( 'Unselect All', 'imagify' );
607 ?>
608 </button>
609 </p>
610 <?php
611 }
612 }
613
614 /**
615 * Display a radio list group.
616 *
617 * @since 1.9
618 *
619 * @param array $args {
620 * Arguments.
621 *
622 * @type string $option_name The option name. E.g. 'disallowed-sizes'. Mandatory.
623 * @type string $legend Label to use for the <legend> tag.
624 * @type string $info Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
625 * @type array $values List of values to display, in the form of 'value' => 'Label'. Mandatory.
626 * @type array $attributes A list of HTML attributes, as 'attribute' => 'value'.
627 * @type array $current_value 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.
628 * }
629 */
630 public function field_radio_list( $args ) {
631 $args = array_merge(
632 [
633 'option_name' => '',
634 'legend' => '',
635 'info' => '',
636 'values' => [],
637 'attributes' => [],
638 // To not use the plugin settings: use an array.
639 'current_value' => false,
640 ],
641 $args
642 );
643
644 if ( ! $args['option_name'] || ! $args['values'] ) {
645 return;
646 }
647
648 if ( is_array( $args['current_value'] ) ) {
649 // We don't use the plugin settings.
650 $current_value = $args['current_value'];
651 } else {
652 // This is a normal plugin setting.
653 $current_value = $this->options->get( $args['option_name'] );
654 }
655
656 $option_name_class = sanitize_html_class( $args['option_name'] );
657 $attributes = array_merge(
658 [
659 'name' => $this->option_name . '[' . $args['option_name'] . ']',
660 'id' => 'imagify_' . $option_name_class . '_%s',
661 'class' => 'imagify-row-radio',
662 ],
663 $args['attributes']
664 );
665
666 $id_attribute = $attributes['id'];
667 unset( $attributes['id'] );
668 $args['attributes'] = self::build_attributes( $attributes );
669 ?>
670 <fieldset class="imagify-radio-group">
671 <?php
672 if ( $args['legend'] ) {
673 ?>
674 <legend class="screen-reader-text">
675 <?php
676 self::print_text( $args['legend'] );
677 ?>
678 </legend>
679 <?php
680 }
681
682 foreach ( $args['values'] as $value => $label ) {
683 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
684 ?>
685 <input type="radio" value="<?php echo esc_attr( $value ); ?>" id="<?php echo esc_attr( $input_id ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <?php checked( $current_value, $value ); ?> />
686 <label for="<?php echo esc_attr( $input_id ); ?>" onclick="">
687 <?php self::print_text( $label ); ?>
688 </label>
689 <br/>
690 <?php
691 }
692 ?>
693 </fieldset>
694 <?php
695 if ( ! $args['info'] ) {
696 return;
697 }
698 ?>
699 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
700 <span class="dashicons dashicons-info"></span>
701 <?php self::print_text( $args['info'] ); ?>
702 </span>
703 <?php
704 }
705
706 /**
707 * Display styled radio list group.
708 *
709 * @param array $args Arguments:
710 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
711 * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
712 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
713 * {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.
714 *
715 * @return void
716 */
717 public function field_inline_radio_list( $args ) {
718 $args = array_merge(
719 [
720 'option_name' => '',
721 'values' => [],
722 'info' => '',
723 'attributes' => [],
724 'current_value' => false,
725 ],
726 $args
727 );
728
729 if ( ! $args['option_name'] || ! $args['values'] ) {
730 return;
731 }
732
733 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
734 $current_value = $args['current_value'];
735 } else {
736 $current_value = $this->options->get( $args['option_name'] );
737 }
738
739 $option_name_class = sanitize_html_class( $args['option_name'] );
740 $attributes = array_merge(
741 [
742 'name' => $this->option_name . '[' . $args['option_name'] . ']',
743 'id' => 'imagify_' . $option_name_class . '_%s',
744 'class' => 'imagify-row-radio',
745 ],
746 $args['attributes']
747 );
748
749 $id_attribute = $attributes['id'];
750 unset( $attributes['id'] );
751 $args['attributes'] = self::build_attributes( $attributes );
752 ?>
753 <div class="imagify-setting-optim-level">
754 <p class="imagify-inline-options imagify-inline-options-<?php echo esc_attr( $args['info_class'] ); ?>">
755 <?php
756 foreach ( $args['values'] as $value => $label ) {
757 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
758 ?>
759 <input type="radio" value="<?php echo esc_attr( $value ); ?>" id="<?php echo esc_attr( $input_id ); ?>"<?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <?php checked( $current_value, $value ); ?> />
760 <label for="<?php echo esc_attr( $input_id ); ?>" onclick=""><?php self::print_text( $label ); ?></label>
761 <?php
762 }
763 ?>
764 </p>
765 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-<?php echo esc_attr( $args['info_class'] ); ?>">
766 <span class="dashicons dashicons-info"></span>
767 <?php self::print_text( $args['info'] ); ?>
768 </span>
769 </div>
770 <?php
771 }
772
773 /**
774 * Display a text box.
775 *
776 * @since 1.9.3
777 *
778 * @param array $args Arguments:
779 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
780 * {label} string The label to use.
781 * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
782 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
783 * {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.
784 */
785 public function field_text_box( $args ) {
786 $args = array_merge(
787 [
788 'option_name' => '',
789 'label' => '',
790 'info' => '',
791 'attributes' => [],
792 // To not use the plugin settings.
793 'current_value' => null,
794 ],
795 $args
796 );
797
798 if ( ! $args['option_name'] || ! $args['label'] ) {
799 return;
800 }
801
802 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
803 // We don't use the plugin settings.
804 $current_value = $args['current_value'];
805 } else {
806 // This is a normal plugin setting.
807 $current_value = $this->options->get( $args['option_name'] );
808 }
809
810 $option_name_class = sanitize_html_class( $args['option_name'] );
811 $attributes = [
812 'name' => $this->option_name . '[' . $args['option_name'] . ']',
813 'id' => 'imagify_' . $option_name_class,
814 ];
815
816 if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
817 $attributes['aria-describedby'] = 'describe-' . $option_name_class;
818 }
819
820 $attributes = array_merge( $attributes, $args['attributes'] );
821 $args['attributes'] = self::build_attributes( $attributes );
822 ?>
823 <!-- Empty onclick attribute to make clickable labels on iTruc & Mac -->
824 <label for="<?php echo esc_attr( $attributes['id'] ); ?>" onclick="">
825 <?php self::print_text( $args['label'] ); ?>
826 </label>
827 <input type="text" value="<?php echo esc_attr( $current_value ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
828 <?php
829 if ( ! $args['info'] ) {
830 return;
831 }
832 ?>
833 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
834 <span class="dashicons dashicons-info"></span>
835 <?php self::print_text( $args['info'] ); ?>
836 </span>
837 <?php
838 }
839
840 /**
841 * Display a simple hidden input.
842 *
843 * @since 1.9.3
844 *
845 * @param array $args Arguments:
846 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
847 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
848 * {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.
849 */
850 public function field_hidden( $args ) {
851 $args = array_merge(
852 [
853 'option_name' => '',
854 'attributes' => [],
855 // To not use the plugin settings.
856 'current_value' => null,
857 ],
858 $args
859 );
860
861 if ( ! $args['option_name'] ) {
862 return;
863 }
864
865 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
866 // We don't use the plugin settings.
867 $current_value = $args['current_value'];
868 } else {
869 // This is a normal plugin setting.
870 $current_value = $this->options->get( $args['option_name'] );
871 }
872
873 $option_name_class = sanitize_html_class( $args['option_name'] );
874 $attributes = [
875 'name' => $this->option_name . '[' . $args['option_name'] . ']',
876 'id' => 'imagify_' . $option_name_class,
877 ];
878
879 $attributes = array_merge( $attributes, $args['attributes'] );
880 $args['attributes'] = self::build_attributes( $attributes );
881 ?>
882 <input type="hidden" value="<?php echo esc_attr( $current_value ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
883 <?php
884 }
885
886
887 /** ----------------------------------------------------------------------------------------- */
888 /** FIELD VALUES ============================================================================ */
889 /** ----------------------------------------------------------------------------------------- */
890
891 /**
892 * Get the thumbnail sizes.
893 *
894 * @since 1.7
895 * @return array A list of thumbnail sizes in the form of 'medium' => 'medium - 300 × 300'.
896 */
897 public static function get_thumbnail_sizes() {
898 static $sizes;
899
900 if ( isset( $sizes ) ) {
901 return $sizes;
902 }
903
904 $sizes = get_imagify_thumbnail_sizes();
905
906 foreach ( $sizes as $size_key => $size_data ) {
907 $sizes[ $size_key ] = sprintf( '%s - %d &times; %d', esc_html( stripslashes( $size_data['name'] ) ), $size_data['width'], $size_data['height'] );
908 }
909
910 return $sizes;
911 }
912
913
914 /** ----------------------------------------------------------------------------------------- */
915 /** TOOLS =================================================================================== */
916 /** ----------------------------------------------------------------------------------------- */
917
918 /**
919 * Create HTML attributes from an array.
920 *
921 * @since 1.7
922 *
923 * @param array $attributes A list of attribute pairs.
924 *
925 * @return string HTML attributes.
926 */
927 public static function build_attributes( $attributes ) {
928 if ( ! $attributes || ! is_array( $attributes ) ) {
929 return '';
930 }
931
932 $out = '';
933
934 foreach ( $attributes as $attribute => $value ) {
935 $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
936 }
937
938 return $out;
939 }
940
941 /**
942 * Print a human-readable field string, keeping the inline formatting it carries.
943 *
944 * The labels, legends and info messages of the field renderers are written as
945 * markup by their callers: a `<br>` separating two sentences, a `<code>`
946 * naming a filter, an `<em>` marking the recommended choice, a link to the
947 * documentation. Passing them through `esc_html()` printed those tags to the
948 * user instead of applying them, so they go through a narrow allow-list of
949 * inline tags.
950 *
951 * Entities survive the allow-list, so a label built with `&times;` or
952 * `&lt;picture&gt;` renders the character it stands for instead of being
953 * escaped a second time.
954 *
955 * The string is already translated by the time it arrives, which is exactly
956 * why the filtering happens here: whatever a translation introduces is held
957 * to the same allow-list as the original string.
958 *
959 * @since 2.3.3
960 *
961 * @param string $text The string to print.
962 * @return void
963 */
964 public static function print_text( $text ) {
965 echo wp_kses(
966 $text,
967 [
968 'a' => [
969 'href' => true,
970 'rel' => true,
971 'target' => true,
972 ],
973 'br' => [],
974 'code' => [],
975 'em' => [],
976 'strong' => [],
977 ]
978 );
979 }
980 }
981