PluginProbe
WebberZone Top 10 — Popular Posts / 4.4.0
WebberZone Top 10 — Popular Posts v4.4.0
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / admin / settings / class-settings-form.php

class-settings-form.php in WebberZone Top 10 — Popular Posts 4.4.0, at includes/admin/settings/class-settings-form.php

1,388 lines 42.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Generates the settings form.
4 *
5 * @link https://webberzone.com
6 *
7 * @package WebberZone\Top_Ten
8 */
9
10 namespace WebberZone\Top_Ten\Admin\Settings;
11
12 // If this file is called directly, abort.
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 /**
18 * Generates the settings form.
19 *
20 * @since 4.0.0
21 */
22 class Settings_Form {
23
24
25 /**
26 * Settings Key.
27 *
28 * @var string Settings Key.
29 */
30 public $settings_key;
31
32 /**
33 * Prefix which is used for creating the unique filters and actions.
34 *
35 * @var string Prefix.
36 */
37 public $prefix;
38
39 /**
40 * Translation strings.
41 *
42 * @var array Translation strings.
43 */
44 public $translation_strings;
45
46 /**
47 * Main constructor class.
48 *
49 * @param mixed $args {
50 * Array or string of arguments. Default is blank array.
51 * @type string $settings_key Settings key.
52 * @type string $prefix Prefix.
53 * @type array $translation_strings Translation strings.
54 * }
55 */
56 public function __construct( $args ) {
57 $defaults = array(
58 'settings_key' => '',
59 'prefix' => '',
60 'translation_strings' => array(),
61 );
62 $args = wp_parse_args( $args, $defaults );
63
64 foreach ( $args as $name => $value ) {
65 $this->$name = $value;
66 }
67 }
68
69 /**
70 * Get field description for display.
71 *
72 * @param array $args settings Arguments array.
73 *
74 * @return string Description of the field.
75 */
76 public function get_field_description( $args ) {
77 $desc = ! empty( $args['desc'] ) ? '<p class="description">' . wp_kses_post( $args['desc'] ) . '</p>' : '';
78
79 /**
80 * After Settings Output filter
81 *
82 * @param string $desc Description of the field.
83 * @param array $args Arguments array.
84 */
85 $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
86
87 return $this->get_modified_indicator( $args ) . $desc . $this->get_default_description( $args );
88 }
89
90 /**
91 * Get the "Default: value" explanation shown below a field's description.
92 *
93 * Checkbox/toggle fields are excluded - their default state is visible
94 * from the toggle itself and the modified indicator.
95 *
96 * @param array $args Field arguments.
97 * @return string Default description HTML or empty string.
98 */
99 protected function get_default_description( $args ) {
100 $type = $args['type'] ?? 'text';
101
102 if ( in_array( $type, array( 'checkbox', 'toggle', 'header', 'descriptive_text', 'repeater' ), true ) ) {
103 return '';
104 }
105
106 $default = $this->get_field_default( $args );
107 if ( is_array( $default ) ) {
108 $default = implode( ',', array_map( 'strval', $default ) );
109 }
110 $default = trim( (string) $default );
111
112 // Map option keys to their labels where the field has a choices list.
113 if ( ! empty( $args['options'] ) && is_array( $args['options'] ) ) {
114 if ( 'radiodesc' === $type && '' !== $default ) {
115 foreach ( $args['options'] as $option ) {
116 if ( isset( $option['id'], $option['name'] ) && (string) $option['id'] === $default ) {
117 $default = (string) $option['name'];
118 break;
119 }
120 }
121 } elseif ( in_array( $type, array( 'select', 'radio' ), true ) ) {
122 if ( isset( $args['options'][ $default ] ) && is_string( $args['options'][ $default ] ) ) {
123 $default = $args['options'][ $default ];
124 }
125 } elseif ( '' !== $default && 'multicheck' === $type ) {
126 $labels = array();
127 foreach ( explode( ',', $default ) as $key ) {
128 $key = trim( $key );
129 $labels[] = isset( $args['options'][ $key ] ) && is_string( $args['options'][ $key ] ) ? $args['options'][ $key ] : $key;
130 }
131 $default = implode( ', ', $labels );
132 }
133 }
134
135 $label = $this->translation_strings['default_label'] ?? 'Default';
136 $none = $this->translation_strings['default_none'] ?? 'None';
137
138 $value_html = '' === $default ? esc_html( $none ) : '<code>' . esc_html( $default ) . '</code>';
139
140 return '<p class="description wz-default-value">' . esc_html( $label ) . ': ' . $value_html . '</p>';
141 }
142
143 /**
144 * Get the default value of a field, mirroring the back-compat logic in
145 * Settings_API::settings_defaults().
146 *
147 * @param array $args Field arguments.
148 * @return mixed Default value.
149 */
150 protected function get_field_default( $args ) {
151 if ( isset( $args['default'] ) ) {
152 return $args['default'];
153 }
154
155 $type = $args['type'] ?? 'text';
156
157 // Back-compat: checkbox used a truthy 'options' to indicate checked by default.
158 if ( 'checkbox' === $type || 'toggle' === $type ) {
159 return empty( $args['options'] ) ? 0 : 1;
160 }
161
162 // Back-compat: legacy configs used 'options' to store default values for text-like fields.
163 if ( in_array( $type, array( 'textarea', 'css', 'html', 'text', 'url', 'csv', 'color', 'numbercsv', 'postids', 'posttypes', 'number', 'wysiwyg', 'file', 'password' ), true ) && isset( $args['options'] ) && is_scalar( $args['options'] ) ) {
164 return $args['options'];
165 }
166
167 return '';
168 }
169
170 /**
171 * Whether the current value of a field differs from its default.
172 *
173 * @param array $args Field arguments.
174 * @return bool True when the saved value does not match the default.
175 */
176 protected function is_field_modified( $args ) {
177 $type = $args['type'] ?? 'text';
178
179 if ( in_array( $type, array( 'header', 'descriptive_text', 'repeater' ), true ) ) {
180 return false;
181 }
182
183 // Fields can opt out, e.g. when the default is environment-derived
184 // (URLs based on the plugin location) and literal comparison is noise.
185 if ( isset( $args['modified_indicator'] ) && false === $args['modified_indicator'] ) {
186 return false;
187 }
188
189 $default = $this->get_field_default( $args );
190 $value = $this->get_field_value( $args );
191
192 if ( 'checkbox' === $type || 'toggle' === $type ) {
193 return (bool) $value !== (bool) $default;
194 }
195
196 if ( is_array( $value ) || is_array( $default ) || in_array( $type, array( 'multicheck', 'posttypes', 'taxonomies' ), true ) ) {
197 return $this->normalize_list_value( $value ) !== $this->normalize_list_value( $default );
198 }
199
200 return trim( (string) $value ) !== trim( (string) $default );
201 }
202
203 /**
204 * Normalize a list-style value (array or comma-separated string) into a
205 * sorted comma-separated string for comparison.
206 *
207 * @param mixed $value Value to normalize.
208 * @return string Normalized value.
209 */
210 protected function normalize_list_value( $value ) {
211 if ( is_array( $value ) ) {
212 $value = implode( ',', array_map( 'strval', $value ) );
213 }
214 $list = array_filter(
215 array_map( 'trim', explode( ',', (string) $value ) ),
216 static function ( $item ) {
217 return '' !== $item;
218 }
219 );
220 sort( $list );
221
222 return implode( ',', $list );
223 }
224
225 /**
226 * Get the modified-from-default indicator for a field.
227 *
228 * Returns a small dot with a tooltip when the saved value differs from
229 * the default; the legend below the form buttons explains it.
230 *
231 * @param array $args Field arguments.
232 * @return string Indicator HTML or empty string.
233 */
234 public function get_modified_indicator( $args ) {
235 if ( ! $this->is_field_modified( $args ) ) {
236 return '';
237 }
238
239 $title = $this->translation_strings['modified_field'] ?? 'Modified from default setting';
240
241 return sprintf( '<span class="wz-modified-dot" title="%s"></span>', esc_attr( $title ) );
242 }
243
244 /**
245 * Get the value of a settings field.
246 *
247 * @param string $option Settings field name.
248 * @param mixed $default_value Default value if option is not found.
249 * @return mixed
250 */
251 public function get_option( $option, $default_value = '' ) {
252
253 $options = \get_option( $this->settings_key );
254
255 if ( isset( $options[ $option ] ) ) {
256 return $options[ $option ];
257 }
258
259 return $default_value;
260 }
261
262 /**
263 * Get field value from args or options.
264 *
265 * @param array $args Field arguments.
266 * @return mixed Field value.
267 */
268 protected function get_field_value( $args ) {
269 return $args['value'] ?? $this->get_option( $args['id'], $args['default'] ?? '' );
270 }
271
272 /**
273 * Get sanitized field class string.
274 *
275 * @param array $args Field arguments.
276 * @param string $default_class Default class to prepend.
277 * @return string Sanitized class string.
278 */
279 protected function get_field_class( $args, $default_class = '' ) {
280 $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['field_class'] ?? '' ) ) );
281 if ( $default_class ) {
282 $class = $default_class . ' ' . $class;
283 }
284 return trim( $class );
285 }
286
287 /**
288 * Get placeholder attribute string.
289 *
290 * @param array $args Field arguments.
291 * @return string Placeholder attribute or empty string.
292 */
293 protected function get_placeholder_attribute( $args ) {
294 return empty( $args['placeholder'] ) ? '' : ' placeholder="' . esc_attr( $args['placeholder'] ) . '"';
295 }
296
297 /**
298 * Get boolean state attributes (disabled, readonly, required).
299 *
300 * @param array $args Field arguments.
301 * @return string Concatenated boolean attributes.
302 */
303 protected function get_boolean_attributes( $args ) {
304 $disabled = ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
305 $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : '';
306 $required = ( isset( $args['required'] ) && true === $args['required'] ) ? ' required' : '';
307 return $disabled . $readonly . $required;
308 }
309
310 /**
311 * Get field ID and name attributes.
312 *
313 * @param array $args Field arguments.
314 * @return array Array containing field_id and field_name.
315 */
316 protected function get_field_attributes( $args ) {
317 $id = sanitize_key( $args['id'] );
318 if ( isset( $args['_repeater_id'] ) && isset( $args['_index'] ) ) {
319 $field_id = sprintf(
320 '%s-%s-%s-fields-%s',
321 $this->settings_key,
322 $args['_repeater_id'],
323 $args['_index'],
324 $id
325 );
326 $field_name = sprintf(
327 '%s[%s][%s][fields][%s]',
328 $this->settings_key,
329 $args['_repeater_id'],
330 $args['_index'],
331 $id
332 );
333 } else {
334 $field_id = $this->settings_key . '-' . $id;
335 $field_name = $this->settings_key . '[' . $id . ']';
336 }
337
338 return array(
339 'field_id' => $field_id,
340 'field_name' => $field_name,
341 );
342 }
343
344 /**
345 * Returns the allowed HTML tags and attributes for settings form output.
346 *
347 * Use the `{prefix}_settings_form_allowed_html` filter to add extra tags or
348 * attributes needed by custom field types or `field_attributes` entries.
349 *
350 * @return array<string, array<string, bool>>
351 */
352 protected function get_allowed_html(): array {
353 $allowed = wp_kses_allowed_html( 'post' );
354
355 $form_tags = array(
356 'input' => array(
357 'type' => true,
358 'name' => true,
359 'id' => true,
360 'value' => true,
361 'class' => true,
362 'style' => true,
363 'checked' => true,
364 'disabled' => true,
365 'readonly' => true,
366 'required' => true,
367 'placeholder' => true,
368 'size' => true,
369 'min' => true,
370 'max' => true,
371 'step' => true,
372 'multiple' => true,
373 'autocomplete' => true,
374 // Tom Select data attributes (built-in Settings API feature).
375 'data-wp-prefix' => true,
376 'data-wp-action' => true,
377 'data-wp-nonce' => true,
378 'data-wp-endpoint' => true,
379 'data-ts-config' => true,
380 'data-wp-extra-fields' => true,
381 ),
382 'select' => array(
383 'id' => true,
384 'name' => true,
385 'class' => true,
386 'style' => true,
387 'disabled' => true,
388 'multiple' => true,
389 'size' => true,
390 'autocomplete' => true,
391 // Tom Select data attributes (built-in Settings API feature).
392 'data-wp-prefix' => true,
393 'data-wp-action' => true,
394 'data-wp-nonce' => true,
395 'data-wp-endpoint' => true,
396 'data-ts-config' => true,
397 'data-wp-extra-fields' => true,
398 ),
399 'option' => array(
400 'value' => true,
401 'selected' => true,
402 'disabled' => true,
403 ),
404 'optgroup' => array(
405 'label' => true,
406 'disabled' => true,
407 ),
408 'textarea' => array(
409 'id' => true,
410 'name' => true,
411 'class' => true,
412 'style' => true,
413 'rows' => true,
414 'cols' => true,
415 'disabled' => true,
416 'readonly' => true,
417 'required' => true,
418 'placeholder' => true,
419 'autocomplete' => true,
420 ),
421 'label' => array(
422 'for' => true,
423 'class' => true,
424 'style' => true,
425 ),
426 'button' => array(
427 'type' => true,
428 'id' => true,
429 'class' => true,
430 'style' => true,
431 'disabled' => true,
432 ),
433 'template' => array(
434 'class' => true,
435 'data-id' => true,
436 ),
437 );
438
439 $allowed = array_replace_recursive( $allowed, $form_tags );
440
441 /**
442 * Filters the allowed HTML tags and attributes for settings form output.
443 *
444 * Use this filter to add data attributes or custom elements required by
445 * field_attributes entries in your registered settings.
446 *
447 * @param array<string, array<string, bool>> $allowed Allowed HTML tags/attributes.
448 */
449 return apply_filters( $this->prefix . '_settings_form_allowed_html', $allowed ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
450 }
451
452 /**
453 * Miscellaneous callback funcion
454 *
455 * @param array $args Arguments array.
456 * @return void
457 */
458 public function callback_missing( $args ) {
459 /* translators: 1: Code. */
460 printf( 'The callback function used for the %1$s setting is missing.', '<strong>' . esc_attr( $args['id'] ) . '</strong>' );
461 }
462
463 /**
464 * Header Callback
465 *
466 * Renders the header.
467 *
468 * @param array $args Arguments passed by the setting.
469 * @return void
470 */
471 public function callback_header( $args ) {
472 $html = $this->get_field_description( $args );
473
474 /**
475 * After Settings Output filter
476 *
477 * @param string $html HTML string.
478 * @param array $args Arguments array.
479 */
480 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
481 }
482
483 /**
484 * Descriptive text callback.
485 *
486 * Renders descriptive text onto the settings field.
487 *
488 * @param array $args Array of arguments.
489 * @return void
490 */
491 public function callback_descriptive_text( $args ) {
492 $this->callback_header( $args );
493 }
494
495 /**
496 * Build additional attributes string from field_attributes.
497 *
498 * @param array $args Field arguments.
499 * @return string Additional attributes string.
500 */
501 protected function build_field_attributes( $args ) {
502 $attributes = '';
503 foreach ( (array) ( $args['field_attributes'] ?? array() ) as $attribute => $val ) {
504 $attr_name = sanitize_key( $attribute );
505 if ( empty( $attr_name ) ) {
506 continue;
507 }
508 if ( true === $val ) {
509 $attributes .= ' ' . $attr_name;
510 } elseif ( false !== $val ) {
511 $attributes .= sprintf( ' %1$s="%2$s"', $attr_name, esc_attr( $val ) );
512 }
513 }
514 return $attributes;
515 }
516
517 /**
518 * Display text fields.
519 *
520 * @param array $args Array of arguments.
521 */
522 public function callback_text( $args ) {
523 $value = $this->get_field_value( $args );
524 $size = sanitize_html_class( $args['size'] ?? 'regular' );
525 $class = $this->get_field_class( $args );
526 $placeholder = $this->get_placeholder_attribute( $args );
527 $attributes = $this->get_boolean_attributes( $args ) . $this->build_field_attributes( $args );
528
529 $field_attributes = $this->get_field_attributes( $args );
530
531 $html = sprintf(
532 '<input type="text" id="%1$s" name="%2$s" class="%3$s" value="%4$s" %5$s %6$s />',
533 $field_attributes['field_id'],
534 $field_attributes['field_name'],
535 $class . ' ' . $size . '-text',
536 esc_attr( stripslashes( $value ) ),
537 $attributes,
538 $placeholder
539 );
540 $html .= $this->get_field_description( $args );
541
542 /**
543 * This filter has been defined in class-settings-api.php
544 */
545 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
546 }
547
548 /**
549 * Display url fields.
550 *
551 * @param array $args Array of arguments.
552 */
553 public function callback_url( $args ) {
554 $this->callback_text( $args );
555 }
556
557 /**
558 * Display csv fields.
559 *
560 * @param array $args Array of arguments.
561 */
562 public function callback_csv( $args ) {
563 $this->callback_text( $args );
564 }
565
566 /**
567 * Display color fields.
568 *
569 * @param array $args Array of arguments.
570 */
571 public function callback_color( $args ) {
572 // Add color-field class for wpColorPicker initialization.
573 $args['field_class'] = ! empty( $args['field_class'] ) ? $args['field_class'] . ' color-field' : 'color-field';
574 $this->callback_text( $args );
575 }
576
577 /**
578 * Display numbercsv fields.
579 *
580 * @param array $args Array of arguments.
581 */
582 public function callback_numbercsv( $args ) {
583 $this->callback_text( $args );
584 }
585
586 /**
587 * Display postids fields.
588 *
589 * @param array $args Array of arguments.
590 */
591 public function callback_postids( $args ) {
592 $this->callback_text( $args );
593 }
594
595 /**
596 * Display textarea.
597 *
598 * @param array $args Array of arguments.
599 * @return void
600 */
601 public function callback_textarea( $args ) {
602 $value = $this->get_field_value( $args );
603 $class = $this->get_field_class( $args, 'large-text' );
604 $placeholder = $this->get_placeholder_attribute( $args );
605 $attributes = $this->get_boolean_attributes( $args );
606
607 $field_attributes = $this->get_field_attributes( $args );
608
609 $html = sprintf(
610 '<textarea class="%4$s" cols="50" rows="5" id="%1$s" name="%2$s" %5$s %6$s>%3$s</textarea>',
611 $field_attributes['field_id'],
612 $field_attributes['field_name'],
613 esc_textarea( stripslashes( $value ) ),
614 $class,
615 $attributes,
616 $placeholder
617 );
618 $html .= $this->get_field_description( $args );
619
620 /**
621 * This filter has been defined in class-settings-api.php
622 */
623 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
624 }
625
626 /**
627 * Display CSS fields.
628 *
629 * @param array $args Array of arguments.
630 * @return void
631 */
632 public function callback_css( $args ) {
633 $this->callback_textarea( $args );
634 }
635
636 /**
637 * Display HTML fields.
638 *
639 * @param array $args Array of arguments.
640 * @return void
641 */
642 public function callback_html( $args ) {
643 $this->callback_textarea( $args );
644 }
645
646 /**
647 * Get disabled attribute for pro/premium fields.
648 *
649 * @param array $args Field arguments.
650 * @return string Disabled attribute or empty string.
651 */
652 protected function get_disabled_attribute( $args ) {
653 return ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
654 }
655
656 /**
657 * Display checkboxes.
658 *
659 * Renders as a toggle switch by default: a native checkbox wrapped in a
660 * styled label. Pass `no-toggle` in `field_class` to render a plain
661 * checkbox instead. Posting semantics are identical either way - a hidden
662 * field posts -1 when unchecked and the checkbox posts 1 when checked.
663 *
664 * @param array $args Array of arguments.
665 * @return void
666 */
667 public function callback_checkbox( $args ) {
668 $value = $this->get_field_value( $args );
669 $checked = ! empty( $value ) ? checked( 1, $value, false ) : '';
670 $disabled = $this->get_disabled_attribute( $args );
671 $class = $this->get_field_class( $args );
672 $no_toggle = in_array( 'no-toggle', explode( ' ', $class ), true );
673
674 $field_attributes = $this->get_field_attributes( $args );
675
676 // The hidden input is also disabled when the field is disabled so that
677 // the setting key is not saved at all and retains its default value.
678 $html = sprintf(
679 '<input type="hidden" name="%1$s" value="-1" %2$s />',
680 $field_attributes['field_name'],
681 $disabled
682 );
683
684 if ( $no_toggle ) {
685 $html .= sprintf(
686 '<input type="checkbox" id="%1$s" name="%2$s" value="1" class="%3$s" %4$s %5$s />',
687 $field_attributes['field_id'],
688 $field_attributes['field_name'],
689 $class,
690 $checked,
691 $disabled
692 );
693 } else {
694 $html .= sprintf(
695 '<label class="wz-toggle"><input type="checkbox" id="%1$s" name="%2$s" value="1" class="%3$s" %4$s %5$s /><span class="wz-toggle-slider"></span></label>',
696 $field_attributes['field_id'],
697 $field_attributes['field_name'],
698 $class,
699 $checked,
700 $disabled
701 );
702 }
703
704 $html .= $this->get_field_description( $args );
705
706 /**
707 * This filter has been defined in class-settings-api.php
708 */
709 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
710 }
711
712 /**
713 * Toggle Callback
714 *
715 * Alias of the checkbox type, which renders as a toggle switch.
716 *
717 * @param array $args Array of arguments.
718 * @return void
719 */
720 public function callback_toggle( $args ) {
721 $this->callback_checkbox( $args );
722 }
723
724 /**
725 * Multicheck Callback
726 *
727 * Renders multiple checkboxes.
728 *
729 * @param array $args Array of arguments.
730 * @return void
731 */
732 public function callback_multicheck( $args ) {
733 $html = '';
734
735 $value = $this->get_field_value( $args );
736 $value_array = wp_parse_list( $value );
737 $disabled = $this->get_disabled_attribute( $args );
738
739 $field_attributes = $this->get_field_attributes( $args );
740
741 if ( ! empty( $args['options'] ) ) {
742 $html .= sprintf(
743 '<input type="hidden" name="%1$s" value="-1" />',
744 $field_attributes['field_name']
745 );
746
747 foreach ( $args['options'] as $key => $option ) {
748 if ( in_array( $key, $value_array, true ) ) {
749 $enabled = $key;
750 } else {
751 $enabled = null;
752 }
753
754 $option_id = $field_attributes['field_id'] . '-' . sanitize_key( $key );
755 $option_name = $field_attributes['field_name'] . '[' . sanitize_key( $key ) . ']';
756
757 $html .= sprintf(
758 '<input name="%1$s" id="%2$s" type="checkbox" value="%3$s" %4$s %5$s /> ',
759 $option_name,
760 $option_id,
761 esc_attr( $key ),
762 checked( $key, $enabled, false ),
763 $disabled
764 );
765 $html .= sprintf(
766 '<label for="%1$s">%2$s</label> <br />',
767 $option_id,
768 wp_kses_post( $option )
769 );
770 }
771 }
772 $html .= $this->get_field_description( $args );
773
774 /**
775 * This filter has been defined in class-settings-api.php
776 */
777 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
778 }
779
780 /**
781 * Radio Callback
782 *
783 * Renders radio boxes.
784 *
785 * @param array $args Array of arguments.
786 * @return void
787 */
788 public function callback_radio( $args ) {
789 $html = '';
790
791 $value = $this->get_field_value( $args );
792 $disabled = $this->get_disabled_attribute( $args );
793
794 $field_attributes = $this->get_field_attributes( $args );
795
796 foreach ( $args['options'] as $key => $option ) {
797 $option_id = $field_attributes['field_id'] . '-' . $key;
798
799 $html .= sprintf(
800 '<input name="%1$s" id="%2$s" type="radio" value="%3$s" %4$s %5$s /> ',
801 $field_attributes['field_name'],
802 $option_id,
803 $key,
804 checked( $value, $key, false ),
805 $disabled
806 );
807 $html .= sprintf(
808 '<label for="%1$s">%2$s</label> <br />',
809 $option_id,
810 wp_kses_post( $option )
811 );
812 }
813
814 $html .= $this->get_field_description( $args );
815
816 /**
817 * This filter has been defined in class-settings-api.php
818 */
819 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
820 }
821
822 /**
823 * Radio callback with description.
824 *
825 * Renders radio boxes with each item having it separate description.
826 *
827 * @param array $args Array of arguments.
828 * @return void
829 */
830 public function callback_radiodesc( $args ) {
831 $html = '';
832
833 $value = $this->get_field_value( $args );
834 $disabled = $this->get_disabled_attribute( $args );
835
836 $field_attributes = $this->get_field_attributes( $args );
837
838 foreach ( $args['options'] as $option ) {
839 $option_id = $field_attributes['field_id'] . '-' . $option['id'];
840
841 $html .= sprintf(
842 '<input name="%1$s" id="%2$s" type="radio" value="%3$s" %4$s %5$s /> ',
843 $field_attributes['field_name'],
844 $option_id,
845 $option['id'],
846 checked( $value, $option['id'], false ),
847 $disabled
848 );
849 $html .= sprintf(
850 '<label for="%1$s">%2$s: <em>%3$s</em></label>',
851 $option_id,
852 wp_kses_post( $option['name'] ),
853 wp_kses_post( $option['description'] )
854 );
855
856 $html .= '<br />';
857 }
858
859 $html .= $this->get_field_description( $args );
860
861 /**
862 * This filter has been defined in class-settings-api.php
863 */
864 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
865 }
866
867 /**
868 * Radio callback with description.
869 *
870 * Renders radio boxes with each item having it separate description.
871 *
872 * @param array $args Array of arguments.
873 * @return void
874 */
875 public function callback_thumbsizes( $args ) {
876 $html = '';
877
878 $thumb_size = $this->prefix . '_thumbnail';
879
880 if ( ! isset( $args['options'][ $thumb_size ] ) ) {
881 $args['options'][ $thumb_size ] = array(
882 'name' => $thumb_size,
883 'width' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_width', 150 ) ),
884 'height' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_height', 150 ) ),
885 'crop' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_crop', true ) ),
886 );
887 }
888
889 $value = $this->get_field_value( $args );
890
891 $field_attributes = $this->get_field_attributes( $args );
892
893 foreach ( $args['options'] as $name => $option ) {
894 $option_id = $field_attributes['field_id'] . '-' . $name;
895
896 $html .= sprintf(
897 '<input name="%1$s" id="%2$s" type="radio" value="%3$s" %4$s /> ',
898 $field_attributes['field_name'],
899 $option_id,
900 $name,
901 checked( $value, $name, false )
902 );
903 $html .= sprintf(
904 '<label for="%1$s">%2$s (%3$sx%4$s%5$s)</label> <br />',
905 $option_id,
906 wp_kses_post( $name ),
907 (int) $option['width'],
908 (int) $option['height'],
909 (bool) $option['crop'] ? ' cropped' : ''
910 );
911 }
912
913 $html .= $this->get_field_description( $args );
914
915 /**
916 * This filter has been defined in class-settings-api.php
917 */
918 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
919 }
920
921 /**
922 * Number Callback
923 *
924 * Renders number fields.
925 *
926 * @param array $args Array of arguments.
927 * @return void
928 */
929 public function callback_number( $args ) {
930 $value = $this->get_field_value( $args );
931 $max = isset( $args['max'] ) ? intval( $args['max'] ) : 999999;
932 $min = isset( $args['min'] ) ? intval( $args['min'] ) : 0;
933 $step = isset( $args['step'] ) ? intval( $args['step'] ) : 1;
934 $size = $args['size'] ?? 'regular';
935 $placeholder = $this->get_placeholder_attribute( $args );
936 $attributes = $this->get_boolean_attributes( $args );
937
938 $field_attributes = $this->get_field_attributes( $args );
939
940 $html = sprintf(
941 '<input type="number" step="%1$s" max="%2$s" min="%3$s" class="%4$s" id="%5$s" name="%6$s" value="%7$s" %8$s %9$s />',
942 esc_attr( (string) $step ),
943 esc_attr( (string) $max ),
944 esc_attr( (string) $min ),
945 sanitize_html_class( $size ) . '-text',
946 $field_attributes['field_id'],
947 $field_attributes['field_name'],
948 esc_attr( stripslashes( $value ) ),
949 $placeholder,
950 $attributes
951 );
952 $html .= $this->get_field_description( $args );
953
954 /**
955 * This filter has been defined in class-settings-api.php
956 */
957 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
958 }
959
960 /**
961 * Select Callback
962 *
963 * Renders select fields.
964 *
965 * @param array $args Array of arguments.
966 * @return void
967 */
968 public function callback_select( $args ) {
969 $value = $this->get_field_value( $args );
970 $class = $this->get_field_class( $args );
971 $required = ( isset( $args['required'] ) && true === $args['required'] ) ? ' required' : '';
972 $attributes = $this->get_disabled_attribute( $args ) . $required . $this->build_field_attributes( $args );
973
974 if ( isset( $args['chosen'] ) ) {
975 $class .= ' chosen';
976 }
977
978 $field_attributes = $this->get_field_attributes( $args );
979
980 $html = sprintf(
981 '<select id="%1$s" name="%2$s" class="%3$s" %4$s />',
982 $field_attributes['field_id'],
983 $field_attributes['field_name'],
984 $class,
985 $attributes
986 );
987
988 foreach ( (array) $args['options'] as $option => $name ) {
989 $html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', sanitize_key( $option ), selected( $option, $value, false ), esc_html( $name ) );
990 }
991
992 $html .= '</select>';
993 $html .= $this->get_field_description( $args );
994
995 /**
996 * This filter has been defined in class-settings-api.php
997 */
998 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
999 }
1000
1001 /**
1002 * Display posttypes fields.
1003 *
1004 * @param array $args Array of arguments.
1005 * @return void
1006 */
1007 public function callback_posttypes( $args ) {
1008 $html = '';
1009
1010 $options = $this->get_field_value( $args );
1011 $disabled = $this->get_disabled_attribute( $args );
1012
1013 // If post_types contains a query string then parse it with wp_parse_args.
1014 if ( is_string( $options ) && strpos( $options, '=' ) ) {
1015 $post_types = wp_parse_args( $options );
1016 } else {
1017 $post_types = wp_parse_list( $options );
1018 }
1019
1020 $wp_post_types = get_post_types(
1021 array(
1022 'public' => true,
1023 ),
1024 'objects'
1025 );
1026
1027 $posts_types_inc = array_intersect( wp_list_pluck( $wp_post_types, 'name' ), $post_types );
1028
1029 $field_attributes = $this->get_field_attributes( $args );
1030
1031 $html .= sprintf(
1032 '<input type="hidden" name="%1$s" value="-1" />',
1033 $field_attributes['field_name']
1034 );
1035
1036 foreach ( $wp_post_types as $wp_post_type ) {
1037 $option_id = $field_attributes['field_id'] . '-' . esc_attr( $wp_post_type->name );
1038 $option_name = $field_attributes['field_name'] . '[' . esc_attr( $wp_post_type->name ) . ']';
1039
1040 $html .= sprintf(
1041 '<label for="%1$s"><input name="%2$s" id="%1$s" type="checkbox" value="%3$s" %4$s %5$s /> %6$s</label><br />',
1042 $option_id,
1043 $option_name,
1044 esc_attr( $wp_post_type->name ),
1045 checked( true, in_array( $wp_post_type->name, $posts_types_inc, true ), false ),
1046 $disabled,
1047 esc_html( $wp_post_type->label )
1048 );
1049
1050 }
1051
1052 $html .= $this->get_field_description( $args );
1053
1054 /**
1055 * This filter has been defined in class-settings-api.php
1056 */
1057 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1058 }
1059
1060
1061 /**
1062 * Display taxonomies fields.
1063 *
1064 * @param array $args Array of arguments.
1065 * @return void
1066 */
1067 public function callback_taxonomies( $args ) {
1068 $html = '';
1069
1070 $options = $this->get_field_value( $args );
1071
1072 // If taxonomies contains a query string then parse it with wp_parse_args.
1073 if ( is_string( $options ) && strpos( $options, '=' ) ) {
1074 $taxonomies = wp_parse_args( $options );
1075 } else {
1076 $taxonomies = wp_parse_list( $options );
1077 }
1078
1079 /* Fetch taxonomies */
1080 $argsc = array(
1081 'public' => true,
1082 );
1083 $output = 'objects';
1084 $operator = 'and';
1085 $wp_taxonomies = get_taxonomies( $argsc, $output, $operator );
1086
1087 $taxonomies_inc = array_intersect( wp_list_pluck( (array) $wp_taxonomies, 'name' ), $taxonomies );
1088
1089 $field_attributes = $this->get_field_attributes( $args );
1090
1091 $html .= sprintf( '<input type="hidden" name="%1$s" value="-1" />', $field_attributes['field_name'] );
1092
1093 foreach ( $wp_taxonomies as $wp_taxonomy ) {
1094 $option_id = $field_attributes['field_id'] . '-' . esc_attr( $wp_taxonomy->name );
1095 $option_name = $field_attributes['field_name'] . '[' . esc_attr( $wp_taxonomy->name ) . ']';
1096
1097 $html .= sprintf(
1098 '<label for="%1$s"><input name="%2$s" id="%1$s" type="checkbox" value="%3$s" %4$s /> %5$s (%3$s)</label><br />',
1099 $option_id,
1100 $option_name,
1101 esc_attr( $wp_taxonomy->name ),
1102 checked( true, in_array( $wp_taxonomy->name, $taxonomies_inc, true ), false ),
1103 esc_html( $wp_taxonomy->labels->name )
1104 );
1105
1106 }
1107
1108 $html .= $this->get_field_description( $args );
1109
1110 /**
1111 * This filter has been defined in class-settings-api.php
1112 */
1113 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1114 }
1115
1116
1117 /**
1118 * Displays a rich text textarea for a settings field.
1119 *
1120 * @param array $args Array of arguments.
1121 */
1122 public function callback_wysiwyg( $args ) {
1123 $value = $this->get_field_value( $args );
1124 $size = $args['size'] ?? '500px';
1125
1126 $field_attributes = $this->get_field_attributes( $args );
1127
1128 // wp_editor requires a unique ID without brackets.
1129 $editor_id = sanitize_key( str_replace( array( '[', ']' ), array( '-', '' ), $field_attributes['field_id'] ) );
1130
1131 printf( '<div style="max-width: %1$s;">', esc_attr( $size ) );
1132
1133 $editor_settings = array(
1134 'teeny' => true,
1135 'textarea_name' => $field_attributes['field_name'],
1136 'textarea_rows' => 10,
1137 );
1138
1139 if ( isset( $args['options'] ) && is_array( $args['options'] ) ) {
1140 $editor_settings = array_merge( $editor_settings, $args['options'] );
1141 }
1142
1143 wp_editor( $value, $editor_id, $editor_settings );
1144
1145 printf( '</div>' );
1146
1147 echo wp_kses_post( $this->get_field_description( $args ) );
1148 }
1149
1150 /**
1151 * Displays a file upload field for a settings field.
1152 *
1153 * @param array $args Array of arguments.
1154 */
1155 public function callback_file( $args ) {
1156 $value = $this->get_field_value( $args );
1157 $size = sanitize_html_class( $args['size'] ?? 'regular' );
1158 $class = $this->get_field_class( $args );
1159 $label = $args['options']['button_label'] ?? $this->translation_strings['button_label'];
1160
1161 $field_attributes = $this->get_field_attributes( $args );
1162
1163 $html = sprintf(
1164 '<input type="text" class="%1$s" id="%2$s" name="%3$s" value="%4$s"/>',
1165 $class . ' ' . $size . '-text file-url',
1166 $field_attributes['field_id'],
1167 $field_attributes['field_name'],
1168 esc_attr( $value )
1169 );
1170 $html .= sprintf( '<input type="button" class="button button-secondary file-browser" value="%s" />', esc_attr( $label ) );
1171 $html .= $this->get_field_description( $args );
1172
1173 /**
1174 * This filter has been defined in class-settings-api.php
1175 */
1176 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1177 }
1178
1179 /**
1180 * Displays a password field for a settings field.
1181 *
1182 * @param array $args Array of arguments.
1183 */
1184 public function callback_password( $args ) {
1185 $value = $this->get_field_value( $args );
1186 $size = sanitize_html_class( $args['size'] ?? 'regular' );
1187 $class = $this->get_field_class( $args );
1188
1189 $field_attributes = $this->get_field_attributes( $args );
1190
1191 $html = sprintf(
1192 '<input type="password" class="%1$s" id="%2$s" name="%3$s" value="%4$s" %5$s />',
1193 "$class $size-text",
1194 $field_attributes['field_id'],
1195 $field_attributes['field_name'],
1196 esc_attr( $value ),
1197 ! empty( $value ) ? 'placeholder="' . esc_attr( $this->translation_strings['previous_saved'] ) . '"' : ''
1198 );
1199 $html .= $this->get_field_description( $args );
1200
1201 /**
1202 * This filter has been defined in class-settings-api.php
1203 */
1204 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1205 }
1206
1207 /**
1208 * Callback for repeater field.
1209 *
1210 * @param array $args Array of arguments.
1211 * @return void
1212 */
1213 public function callback_repeater( $args ) {
1214 $raw_value = $this->get_field_value( $args );
1215 $value = ! empty( $raw_value ) && is_array( $raw_value ) ? $raw_value : array();
1216
1217 $class = $this->get_field_class( $args );
1218 $attributes = $this->get_boolean_attributes( $args ) . $this->build_field_attributes( $args );
1219
1220 $data_index = (string) count( $value );
1221 $live_update_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1222 $fallback_title = ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'];
1223 $live_update_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
1224
1225 ob_start();
1226 ?>
1227 <div class="<?php echo esc_attr( $class ); ?> wz-repeater-wrapper"
1228 id="<?php echo esc_attr( $args['id'] ); ?>-wrapper"
1229 data-index="<?php echo esc_attr( $data_index ); ?>"
1230 data-live-update-field="<?php echo esc_attr( $live_update_field ); ?>"
1231 data-fallback-title="<?php echo esc_attr( $fallback_title ); ?>"
1232 <?php if ( ! empty( $live_update_options ) ) : ?>
1233 data-live-update-field-options="<?php echo esc_attr( wp_json_encode( $live_update_options ) ); ?>"
1234 <?php endif; ?>
1235 <?php if ( ! empty( $args['unique_field'] ) ) : ?>
1236 data-unique-field="<?php echo esc_attr( $args['unique_field'] ); ?>"
1237 <?php endif; ?>
1238 <?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
1239
1240 <div class="<?php echo esc_attr( $args['id'] ); ?>-items wz-repeater-items">
1241 <?php
1242 if ( ! empty( $value ) ) {
1243 foreach ( array_values( $value ) as $index => $item ) {
1244 $this->render_repeater_item( $args, $index, $item );
1245 }
1246 }
1247 ?>
1248 </div>
1249 <button type="button" class="button add-item" data-target="<?php echo esc_attr( $args['id'] ); ?>">
1250 <?php echo esc_html( ! empty( $args['add_button_text'] ) ? $args['add_button_text'] : 'Add Item' ); ?>
1251 </button>
1252
1253 <template class="repeater-template" data-id="<?php echo esc_attr( $args['id'] ); ?>">
1254 <?php $this->render_repeater_item( $args, '{{INDEX}}' ); ?>
1255 </template>
1256 </div>
1257 <?php
1258 $html = ob_get_clean();
1259 $html .= $this->get_field_description( $args );
1260
1261 /**
1262 * This filter has been defined in class-settings-api.php
1263 */
1264 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1265 }
1266
1267 /**
1268 * Render a single repeater item.
1269 *
1270 * @param array $args Repeater field arguments.
1271 * @param string|int $index Current item index.
1272 * @param array|null $item Item data if exists.
1273 * @return void
1274 */
1275 public function render_repeater_item( $args, $index, $item = null ) {
1276 if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
1277 return;
1278 }
1279
1280 $fallback_title = ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'];
1281
1282 // Generate or retrieve unique row ID.
1283 $item_id = '';
1284 if ( is_array( $item ) && isset( $item['row_id'] ) ) {
1285 $item_id = $item['row_id'];
1286 } elseif ( '{{INDEX}}' !== $index ) {
1287 // For existing items without row_id, generate a persistent one.
1288 $item_id = 'row_' . md5( $args['id'] . '_' . $index );
1289 } else {
1290 // For new items, use a placeholder that will be replaced.
1291 $item_id = '{{ROW_ID}}';
1292 }
1293
1294 ?>
1295 <div class="wz-repeater-item" data-row-id="<?php echo esc_attr( $item_id ); ?>">
1296 <input type="hidden" name="<?php echo esc_attr( $this->settings_key ); ?>[<?php echo esc_attr( $args['id'] ); ?>][<?php echo esc_attr( $index ); ?>][row_id]" value="<?php echo esc_attr( $item_id ); ?>" />
1297 <div class="repeater-item-header">
1298 <?php
1299 $display_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1300 $live_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
1301 $raw_live_value = ! empty( $item['fields'][ $display_field ] ) ? (string) $item['fields'][ $display_field ] : '';
1302 $display_value = '' !== $raw_live_value
1303 ? ( isset( $live_options[ $raw_live_value ] ) ? $live_options[ $raw_live_value ] : $raw_live_value )
1304 : $fallback_title;
1305 ?>
1306 <span class="repeater-title"><?php echo esc_html( $display_value ); ?></span>
1307 <span class="toggle-icon">▼</span>
1308 </div>
1309 <div class="repeater-item-content" style="display: none;">
1310 <?php
1311 foreach ( $args['fields'] as $field ) {
1312 $field_id = sanitize_key( $field['id'] );
1313
1314 $field_args = array_merge(
1315 (array) $field,
1316 array(
1317 'value' => isset( $item['fields'][ $field_id ] ) ? $item['fields'][ $field_id ] : ( isset( $field['default'] ) ? $field['default'] : '' ),
1318 '_repeater_id' => $args['id'],
1319 '_index' => $index,
1320 )
1321 );
1322 $field_args = Settings_API::parse_field_args( $field_args, $args['section'] );
1323
1324 if ( ! isset( $field['type'] ) || ! is_string( $field['type'] ) ) {
1325 continue;
1326 }
1327 ?>
1328 <?php $repeater_field_attributes = $this->get_field_attributes( $field_args ); ?>
1329 <div class="wz-repeater-field">
1330 <div class="wz-repeater-field-header">
1331 <label class="wz-repeater-field-label" for="<?php echo esc_attr( $repeater_field_attributes['field_id'] ); ?>">
1332 <?php echo esc_html( $field['name'] ); ?>
1333 <?php if ( ! empty( $field['required'] ) ) : ?>
1334 <span class="required" title="<?php echo esc_attr( $this->translation_strings['required_label'] ); ?>">*</span>
1335 <?php endif; ?>
1336 </label>
1337 </div>
1338
1339 <div class="wz-repeater-field-input">
1340 <?php
1341 $callback = 'callback_' . $field['type'];
1342
1343 if ( method_exists( $this, $callback ) ) {
1344 $this->$callback( $field_args );
1345 } else {
1346 do_action( "{$this->prefix}_repeater_field_{$field['type']}", $field_args, $index ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1347 }
1348 ?>
1349 </div>
1350 </div>
1351 <?php } ?>
1352 </div>
1353
1354 <div class="repeater-item-footer">
1355 <div class="repeater-item-actions">
1356 <button type="button" class="button button-secondary move-up">
1357 <span class="dashicons dashicons-arrow-up-alt2"></span>
1358 </button>
1359 <button type="button" class="button button-secondary move-down">
1360 <span class="dashicons dashicons-arrow-down-alt2"></span>
1361 </button>
1362 <button type="button" class="button button-secondary remove-item">
1363 <span class="dashicons dashicons-trash"></span>
1364 </button>
1365 </div>
1366 </div>
1367 </div>
1368
1369 <?php
1370 }
1371
1372
1373
1374 /**
1375 * Display sensitive fields.
1376 *
1377 * @param array $args Array of arguments.
1378 */
1379 public function callback_sensitive( $args ) {
1380 $encrypted_key = $this->get_field_value( $args );
1381 $decrypted_key = Settings_API::decrypt_api_key( $encrypted_key );
1382
1383 $args['value'] = $decrypted_key ? str_repeat( '*', strlen( $decrypted_key ) - 4 ) . substr( $decrypted_key, -4 ) : '';
1384
1385 $this->callback_text( $args );
1386 }
1387 }
1388