PluginProbe
WebberZone Top 10 — Popular Posts / 4.3.1
WebberZone Top 10 — Popular Posts v4.3.1
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.3.1, at includes/admin/settings/class-settings-form.php

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