PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
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 trunk, at inc/classes/class-imagify-settings.php

995 lines 31.8 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 * `options.php` is what registers the "Settings saved." notice and stores it in the
415 * `settings_errors` transient. We bypass it on network installations, so do it here.
416 *
417 * The guard mirrors core: a success notice is only queued when nothing else has
418 * reported a problem during this request, so a real error from another plugin is
419 * never contradicted by a "Settings saved." underneath it.
420 */
421 if ( ! count( get_settings_errors() ) ) {
422 add_settings_error( 'general', 'settings_updated', __( 'Settings saved.' ), 'success' );
423 }
424
425 set_transient( 'settings_errors', get_settings_errors(), 30 );
426
427 /**
428 * Redirect back to the settings page that was submitted.
429 */
430 imagify_maybe_redirect( false, [ 'settings-updated' => 'true' ] );
431 }
432
433
434 /** ----------------------------------------------------------------------------------------- */
435 /** FIELDS ================================================================================== */
436 /** ----------------------------------------------------------------------------------------- */
437
438 /**
439 * Display a single checkbox.
440 *
441 * @since 1.7
442 *
443 * @param array $args Arguments:
444 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
445 * {label} string The label to use.
446 * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
447 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
448 * {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.
449 */
450 public function field_checkbox( $args ) {
451 $args = array_merge(
452 [
453 'option_name' => '',
454 'label' => '',
455 'info' => '',
456 'attributes' => [],
457 // To not use the plugin settings: use an integer.
458 'current_value' => null,
459 ],
460 $args
461 );
462
463 if ( ! $args['option_name'] || ! $args['label'] ) {
464 return;
465 }
466
467 if ( is_numeric( $args['current_value'] ) || is_bool( $args['current_value'] ) ) {
468 // We don't use the plugin settings.
469 $current_value = (int) (bool) $args['current_value'];
470 } else {
471 // This is a normal plugin setting.
472 $current_value = $this->options->get( $args['option_name'] );
473 }
474
475 $option_name_class = sanitize_html_class( $args['option_name'] );
476 $attributes = [
477 'name' => $this->option_name . '[' . $args['option_name'] . ']',
478 'id' => 'imagify_' . $option_name_class,
479 ];
480
481 if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
482 $attributes['aria-describedby'] = 'describe-' . $option_name_class;
483 }
484
485 $attributes = array_merge( $attributes, $args['attributes'] );
486 $args['attributes'] = self::build_attributes( $attributes );
487 ?>
488 <input type="checkbox" value="1" <?php checked( $current_value, 1 ); ?> <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
489 <!-- Empty onclick attribute to make clickable labels on iTruc & Mac -->
490 <label for="<?php echo esc_attr( $attributes['id'] ); ?>" onclick="">
491 <?php self::print_text( $args['label'] ); ?>
492 </label>
493 <?php
494 if ( ! $args['info'] ) {
495 return;
496 }
497 ?>
498 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
499 <span class="dashicons dashicons-info"></span>
500 <?php self::print_text( $args['info'] ); ?>
501 </span>
502 <?php
503 }
504
505 /**
506 * Display a checkbox group.
507 *
508 * @since 1.7
509 *
510 * @param array $args Arguments:
511 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
512 * {legend} string Label to use for the <legend> tag.
513 * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
514 * {disabled_values} array Values to be disabled. Values are the array keys.
515 * {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).
516 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
517 * {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.
518 */
519 public function field_checkbox_list( $args ) {
520 $args = array_merge(
521 [
522 'option_name' => '',
523 'legend' => '',
524 'values' => [],
525 'disabled_values' => [],
526 'reverse_check' => false,
527 'attributes' => [],
528 // To not use the plugin settings: use an array.
529 'current_values' => false,
530 ],
531 $args
532 );
533
534 if ( ! $args['option_name'] || ! $args['values'] ) {
535 return;
536 }
537
538 if ( is_array( $args['current_values'] ) ) {
539 // We don't use the plugin settings.
540 $current_values = $args['current_values'];
541 } else {
542 // This is a normal plugin setting.
543 $current_values = $this->options->get( $args['option_name'] );
544 }
545
546 $option_name_class = sanitize_html_class( $args['option_name'] );
547 $attributes = array_merge(
548 [
549 'name' => $this->option_name . '[' . $args['option_name'] . ( $args['reverse_check'] ? '-checked' : '' ) . '][]',
550 'id' => 'imagify_' . $option_name_class . '_%s',
551 'class' => 'imagify-row-check',
552 ],
553 $args['attributes']
554 );
555
556 $id_attribute = $attributes['id'];
557 unset( $attributes['id'] );
558 $args['attributes'] = self::build_attributes( $attributes );
559
560 $current_values = array_diff_key( $current_values, $args['disabled_values'] );
561 $nb_of_values = count( $args['values'] );
562 $display_check_all = $nb_of_values > 3;
563 $nb_of_checked = 0;
564 ?>
565 <fieldset class="imagify-check-group <?php echo $nb_of_values > 5 ? ' imagify-is-scrollable' : ''; ?>">
566 <?php
567 if ( $args['legend'] ) {
568 ?>
569 <legend class="screen-reader-text">
570 <?php self::print_text( $args['legend'] ); ?>
571 </legend>
572 <?php
573 }
574
575 foreach ( $args['values'] as $value => $label ) {
576 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
577 $disabled = isset( $args['disabled_values'][ $value ] );
578
579 if ( $args['reverse_check'] ) {
580 $checked = ! $disabled && ! isset( $current_values[ $value ] );
581 } else {
582 $checked = ! $disabled && isset( $current_values[ $value ] );
583 }
584
585 $nb_of_checked = $checked ? $nb_of_checked + 1 : $nb_of_checked;
586
587 if ( $args['reverse_check'] ) {
588 echo '<input type="hidden" name="' . esc_attr( $this->option_name . '[' . $args['option_name'] ) . '-reversed][]" value="' . esc_attr( $value ) . '" />';
589 }
590 ?>
591 <p>
592 <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 ); ?> />
593 <label for="<?php echo esc_attr( $input_id ); ?>" onclick="">
594 <?php self::print_text( $label ); ?>
595 </label>
596 </p>
597 <?php
598 }
599 ?>
600 </fieldset>
601 <?php
602 if ( $display_check_all ) {
603 if ( $args['reverse_check'] ) {
604 $all_checked = ! array_intersect_key( $args['values'], $current_values );
605 } else {
606 $all_checked = ! array_diff_key( $args['values'], $current_values );
607 }
608 ?>
609 <p class="hide-if-no-js imagify-select-all-buttons">
610 <button type="button" class="imagify-link-like imagify-select-all <?php echo $all_checked ? ' imagify-is-inactive" aria-disabled="true' : ''; ?>" data-action="select">
611 <?php
612 esc_html_e( 'Select All', 'imagify' );
613 ?>
614 </button>
615
616 <span class="imagify-pipe"></span>
617
618 <button type="button" class="imagify-link-like imagify-select-all <?php echo $nb_of_checked ? '' : ' imagify-is-inactive" aria-disabled="true'; ?> " data-action="unselect">
619 <?php
620 esc_html_e( 'Unselect All', 'imagify' );
621 ?>
622 </button>
623 </p>
624 <?php
625 }
626 }
627
628 /**
629 * Display a radio list group.
630 *
631 * @since 1.9
632 *
633 * @param array $args {
634 * Arguments.
635 *
636 * @type string $option_name The option name. E.g. 'disallowed-sizes'. Mandatory.
637 * @type string $legend Label to use for the <legend> tag.
638 * @type string $info Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
639 * @type array $values List of values to display, in the form of 'value' => 'Label'. Mandatory.
640 * @type array $attributes A list of HTML attributes, as 'attribute' => 'value'.
641 * @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.
642 * }
643 */
644 public function field_radio_list( $args ) {
645 $args = array_merge(
646 [
647 'option_name' => '',
648 'legend' => '',
649 'info' => '',
650 'values' => [],
651 'attributes' => [],
652 // To not use the plugin settings: use an array.
653 'current_value' => false,
654 ],
655 $args
656 );
657
658 if ( ! $args['option_name'] || ! $args['values'] ) {
659 return;
660 }
661
662 if ( is_array( $args['current_value'] ) ) {
663 // We don't use the plugin settings.
664 $current_value = $args['current_value'];
665 } else {
666 // This is a normal plugin setting.
667 $current_value = $this->options->get( $args['option_name'] );
668 }
669
670 $option_name_class = sanitize_html_class( $args['option_name'] );
671 $attributes = array_merge(
672 [
673 'name' => $this->option_name . '[' . $args['option_name'] . ']',
674 'id' => 'imagify_' . $option_name_class . '_%s',
675 'class' => 'imagify-row-radio',
676 ],
677 $args['attributes']
678 );
679
680 $id_attribute = $attributes['id'];
681 unset( $attributes['id'] );
682 $args['attributes'] = self::build_attributes( $attributes );
683 ?>
684 <fieldset class="imagify-radio-group">
685 <?php
686 if ( $args['legend'] ) {
687 ?>
688 <legend class="screen-reader-text">
689 <?php
690 self::print_text( $args['legend'] );
691 ?>
692 </legend>
693 <?php
694 }
695
696 foreach ( $args['values'] as $value => $label ) {
697 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
698 ?>
699 <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 ); ?> />
700 <label for="<?php echo esc_attr( $input_id ); ?>" onclick="">
701 <?php self::print_text( $label ); ?>
702 </label>
703 <br/>
704 <?php
705 }
706 ?>
707 </fieldset>
708 <?php
709 if ( ! $args['info'] ) {
710 return;
711 }
712 ?>
713 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
714 <span class="dashicons dashicons-info"></span>
715 <?php self::print_text( $args['info'] ); ?>
716 </span>
717 <?php
718 }
719
720 /**
721 * Display styled radio list group.
722 *
723 * @param array $args Arguments:
724 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
725 * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
726 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
727 * {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.
728 *
729 * @return void
730 */
731 public function field_inline_radio_list( $args ) {
732 $args = array_merge(
733 [
734 'option_name' => '',
735 'values' => [],
736 'info' => '',
737 'attributes' => [],
738 'current_value' => false,
739 ],
740 $args
741 );
742
743 if ( ! $args['option_name'] || ! $args['values'] ) {
744 return;
745 }
746
747 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
748 $current_value = $args['current_value'];
749 } else {
750 $current_value = $this->options->get( $args['option_name'] );
751 }
752
753 $option_name_class = sanitize_html_class( $args['option_name'] );
754 $attributes = array_merge(
755 [
756 'name' => $this->option_name . '[' . $args['option_name'] . ']',
757 'id' => 'imagify_' . $option_name_class . '_%s',
758 'class' => 'imagify-row-radio',
759 ],
760 $args['attributes']
761 );
762
763 $id_attribute = $attributes['id'];
764 unset( $attributes['id'] );
765 $args['attributes'] = self::build_attributes( $attributes );
766 ?>
767 <div class="imagify-setting-optim-level">
768 <p class="imagify-inline-options imagify-inline-options-<?php echo esc_attr( $args['info_class'] ); ?>">
769 <?php
770 foreach ( $args['values'] as $value => $label ) {
771 $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
772 ?>
773 <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 ); ?> />
774 <label for="<?php echo esc_attr( $input_id ); ?>" onclick=""><?php self::print_text( $label ); ?></label>
775 <?php
776 }
777 ?>
778 </p>
779 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-<?php echo esc_attr( $args['info_class'] ); ?>">
780 <span class="dashicons dashicons-info"></span>
781 <?php self::print_text( $args['info'] ); ?>
782 </span>
783 </div>
784 <?php
785 }
786
787 /**
788 * Display a text box.
789 *
790 * @since 1.9.3
791 *
792 * @param array $args Arguments:
793 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
794 * {label} string The label to use.
795 * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
796 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
797 * {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.
798 */
799 public function field_text_box( $args ) {
800 $args = array_merge(
801 [
802 'option_name' => '',
803 'label' => '',
804 'info' => '',
805 'attributes' => [],
806 // To not use the plugin settings.
807 'current_value' => null,
808 ],
809 $args
810 );
811
812 if ( ! $args['option_name'] || ! $args['label'] ) {
813 return;
814 }
815
816 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
817 // We don't use the plugin settings.
818 $current_value = $args['current_value'];
819 } else {
820 // This is a normal plugin setting.
821 $current_value = $this->options->get( $args['option_name'] );
822 }
823
824 $option_name_class = sanitize_html_class( $args['option_name'] );
825 $attributes = [
826 'name' => $this->option_name . '[' . $args['option_name'] . ']',
827 'id' => 'imagify_' . $option_name_class,
828 ];
829
830 if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
831 $attributes['aria-describedby'] = 'describe-' . $option_name_class;
832 }
833
834 $attributes = array_merge( $attributes, $args['attributes'] );
835 $args['attributes'] = self::build_attributes( $attributes );
836 ?>
837 <!-- Empty onclick attribute to make clickable labels on iTruc & Mac -->
838 <label for="<?php echo esc_attr( $attributes['id'] ); ?>" onclick="">
839 <?php self::print_text( $args['label'] ); ?>
840 </label>
841 <input type="text" value="<?php echo esc_attr( $current_value ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
842 <?php
843 if ( ! $args['info'] ) {
844 return;
845 }
846 ?>
847 <span id="<?php echo esc_attr( $attributes['aria-describedby'] ); ?>" class="imagify-info">
848 <span class="dashicons dashicons-info"></span>
849 <?php self::print_text( $args['info'] ); ?>
850 </span>
851 <?php
852 }
853
854 /**
855 * Display a simple hidden input.
856 *
857 * @since 1.9.3
858 *
859 * @param array $args Arguments:
860 * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
861 * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
862 * {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.
863 */
864 public function field_hidden( $args ) {
865 $args = array_merge(
866 [
867 'option_name' => '',
868 'attributes' => [],
869 // To not use the plugin settings.
870 'current_value' => null,
871 ],
872 $args
873 );
874
875 if ( ! $args['option_name'] ) {
876 return;
877 }
878
879 if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
880 // We don't use the plugin settings.
881 $current_value = $args['current_value'];
882 } else {
883 // This is a normal plugin setting.
884 $current_value = $this->options->get( $args['option_name'] );
885 }
886
887 $option_name_class = sanitize_html_class( $args['option_name'] );
888 $attributes = [
889 'name' => $this->option_name . '[' . $args['option_name'] . ']',
890 'id' => 'imagify_' . $option_name_class,
891 ];
892
893 $attributes = array_merge( $attributes, $args['attributes'] );
894 $args['attributes'] = self::build_attributes( $attributes );
895 ?>
896 <input type="hidden" value="<?php echo esc_attr( $current_value ); ?>" <?php echo $args['attributes']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> />
897 <?php
898 }
899
900
901 /** ----------------------------------------------------------------------------------------- */
902 /** FIELD VALUES ============================================================================ */
903 /** ----------------------------------------------------------------------------------------- */
904
905 /**
906 * Get the thumbnail sizes.
907 *
908 * @since 1.7
909 * @return array A list of thumbnail sizes in the form of 'medium' => 'medium - 300 × 300'.
910 */
911 public static function get_thumbnail_sizes() {
912 static $sizes;
913
914 if ( isset( $sizes ) ) {
915 return $sizes;
916 }
917
918 $sizes = get_imagify_thumbnail_sizes();
919
920 foreach ( $sizes as $size_key => $size_data ) {
921 $sizes[ $size_key ] = sprintf( '%s - %d &times; %d', esc_html( stripslashes( $size_data['name'] ) ), $size_data['width'], $size_data['height'] );
922 }
923
924 return $sizes;
925 }
926
927
928 /** ----------------------------------------------------------------------------------------- */
929 /** TOOLS =================================================================================== */
930 /** ----------------------------------------------------------------------------------------- */
931
932 /**
933 * Create HTML attributes from an array.
934 *
935 * @since 1.7
936 *
937 * @param array $attributes A list of attribute pairs.
938 *
939 * @return string HTML attributes.
940 */
941 public static function build_attributes( $attributes ) {
942 if ( ! $attributes || ! is_array( $attributes ) ) {
943 return '';
944 }
945
946 $out = '';
947
948 foreach ( $attributes as $attribute => $value ) {
949 $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
950 }
951
952 return $out;
953 }
954
955 /**
956 * Print a human-readable field string, keeping the inline formatting it carries.
957 *
958 * The labels, legends and info messages of the field renderers are written as
959 * markup by their callers: a `<br>` separating two sentences, a `<code>`
960 * naming a filter, an `<em>` marking the recommended choice, a link to the
961 * documentation. Passing them through `esc_html()` printed those tags to the
962 * user instead of applying them, so they go through a narrow allow-list of
963 * inline tags.
964 *
965 * Entities survive the allow-list, so a label built with `&times;` or
966 * `&lt;picture&gt;` renders the character it stands for instead of being
967 * escaped a second time.
968 *
969 * The string is already translated by the time it arrives, which is exactly
970 * why the filtering happens here: whatever a translation introduces is held
971 * to the same allow-list as the original string.
972 *
973 * @since 2.3.3
974 *
975 * @param string $text The string to print.
976 * @return void
977 */
978 public static function print_text( $text ) {
979 echo wp_kses(
980 $text,
981 [
982 'a' => [
983 'href' => true,
984 'rel' => true,
985 'target' => true,
986 ],
987 'br' => [],
988 'code' => [],
989 'em' => [],
990 'strong' => [],
991 ]
992 );
993 }
994 }
995