PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 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 All 118 releases
← All changes | includes/admin/settings/class-settings-form.php +494 -184 4.3.0 → 4.5.2 View file →
@@ -1,11 +1,11 @@
1 1 <?php
2 2 /**
3 3 * Generates the settings form.
4 4 *
5 - * @link https://webberzone.com
5 + * @link https://webberzone.com
6 6 *
7 - * @package WebberZone\Top_Ten
7 + * @package WebberZone\Top_Ten
8 8 */
9 9
10 10 namespace WebberZone\Top_Ten\Admin\Settings;
11 11
@@ -15,11 +15,14 @@
15 15 }
16 16
17 17 /**
18 18 * Generates the settings form.
19 + *
20 + * @since 4.0.0
19 21 */
20 22 class Settings_Form {
21 23
24 +
22 25 /**
23 26 * Settings Key.
24 27 *
25 28 * @var string Settings Key.
@@ -43,12 +46,12 @@
43 46 /**
44 47 * Main constructor class.
45 48 *
46 49 * @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.
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.
51 54 * }
52 55 */
53 56 public function __construct( $args ) {
54 57 $defaults = array(
@@ -70,9 +73,9 @@
70 73 *
71 74 * @return string Description of the field.
72 75 */
73 76 public function get_field_description( $args ) {
74 - $desc = ! empty( $args['desc'] ) ? '<p class="description">' . wp_kses_post( $args['desc'] ) . '</p>' : '';
77 + $desc = ! empty( $args['desc'] ) ? '<p class="description">' . wp_kses( $args['desc'], $this->get_allowed_html() ) . '</p>' : '';
75 78
76 79 /**
77 80 * After Settings Output filter
78 81 *
@@ -80,16 +83,170 @@
80 83 * @param array $args Arguments array.
81 84 */
82 85 $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
83 86
84 - return $desc;
87 + return $this->get_modified_indicator( $args ) . $desc . $this->get_default_description( $args );
85 88 }
86 89
87 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, " \t\n\r\0\x0B" );
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, " \t\n\r\0\x0B" );
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, " \t\n\r\0\x0B" ) !== trim( (string) $default, " \t\n\r\0\x0B" );
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 + /**
88 245 * Get the value of a settings field.
89 246 *
90 - * @param string $option Settings field name.
91 - * @param mixed $default_value Default value if option is not found.
247 + * @param string $option Settings field name.
248 + * @param mixed $default_value Default value if option is not found.
92 249 * @return mixed
93 250 */
94 251 public function get_option( $option, $default_value = '' ) {
95 252
@@ -104,9 +261,9 @@
104 261
105 262 /**
106 263 * Get field value from args or options.
107 264 *
108 - * @param array $args Field arguments.
265 + * @param array $args Field arguments.
109 266 * @return mixed Field value.
110 267 */
111 268 protected function get_field_value( $args ) {
112 269 return $args['value'] ?? $this->get_option( $args['id'], $args['default'] ?? '' );
@@ -114,10 +271,10 @@
114 271
115 272 /**
116 273 * Get sanitized field class string.
117 274 *
118 - * @param array $args Field arguments.
119 - * @param string $default_class Default class to prepend.
275 + * @param array $args Field arguments.
276 + * @param string $default_class Default class to prepend.
120 277 * @return string Sanitized class string.
121 278 */
122 279 protected function get_field_class( $args, $default_class = '' ) {
123 280 $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['field_class'] ?? '' ) ) );
@@ -123,15 +280,15 @@
123 280 $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['field_class'] ?? '' ) ) );
124 281 if ( $default_class ) {
125 282 $class = $default_class . ' ' . $class;
126 283 }
127 - return trim( $class );
284 + return trim( $class, " \t\n\r\0\x0B" );
128 285 }
129 286
130 287 /**
131 288 * Get placeholder attribute string.
132 289 *
133 - * @param array $args Field arguments.
290 + * @param array $args Field arguments.
134 291 * @return string Placeholder attribute or empty string.
135 292 */
136 293 protected function get_placeholder_attribute( $args ) {
137 294 return empty( $args['placeholder'] ) ? '' : ' placeholder="' . esc_attr( $args['placeholder'] ) . '"';
@@ -139,13 +296,13 @@
139 296
140 297 /**
141 298 * Get boolean state attributes (disabled, readonly, required).
142 299 *
143 - * @param array $args Field arguments.
300 + * @param array $args Field arguments.
144 301 * @return string Concatenated boolean attributes.
145 302 */
146 303 protected function get_boolean_attributes( $args ) {
147 - $disabled = ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
304 + $disabled = $this->is_field_disabled( $args ) ? ' disabled="disabled"' : '';
148 305 $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : '';
149 306 $required = ( isset( $args['required'] ) && true === $args['required'] ) ? ' required' : '';
150 307 return $disabled . $readonly . $required;
151 308 }
@@ -150,11 +307,21 @@
150 307 return $disabled . $readonly . $required;
151 308 }
152 309
153 310 /**
311 + * Whether a field is disabled, either explicitly or because it is a pro-only field.
312 + *
313 + * @param array $args Field arguments.
314 + * @return bool Whether the field is disabled.
315 + */
316 + protected function is_field_disabled( $args ) {
317 + return ! empty( $args['disabled'] ) || ! empty( $args['pro'] );
318 + }
319 +
320 + /**
154 321 * Get field ID and name attributes.
155 322 *
156 - * @param array $args Field arguments.
323 + * @param array $args Field arguments.
157 324 * @return array Array containing field_id and field_name.
158 325 */
159 326 protected function get_field_attributes( $args ) {
160 327 $id = sanitize_key( $args['id'] );
@@ -184,8 +351,20 @@
184 351 );
185 352 }
186 353
187 354 /**
355 + * Get the rendered field ID for a field definition.
356 + *
357 + * @param array $args Field arguments.
358 + * @return string Field ID.
359 + */
360 + public function get_field_id( $args ) {
361 + $field_attributes = $this->get_field_attributes( $args );
362 +
363 + return $field_attributes['field_id'];
364 + }
365 +
366 + /**
188 367 * Returns the allowed HTML tags and attributes for settings form output.
189 368 *
190 369 * Use the `{prefix}_settings_form_allowed_html` filter to add extra tags or
191 370 * attributes needed by custom field types or `field_attributes` entries.
@@ -196,47 +375,49 @@
196 375 $allowed = wp_kses_allowed_html( 'post' );
197 376
198 377 $form_tags = array(
199 378 '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,
379 + 'type' => true,
380 + 'name' => true,
381 + 'id' => true,
382 + 'value' => true,
383 + 'class' => true,
384 + 'style' => true,
385 + 'checked' => true,
386 + 'disabled' => true,
387 + 'readonly' => true,
388 + 'required' => true,
389 + 'placeholder' => true,
390 + 'size' => true,
391 + 'min' => true,
392 + 'max' => true,
393 + 'step' => true,
394 + 'multiple' => true,
395 + 'autocomplete' => true,
217 396 // 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,
397 + 'data-wp-prefix' => true,
398 + 'data-wp-action' => true,
399 + 'data-wp-nonce' => true,
400 + 'data-wp-endpoint' => true,
401 + 'data-ts-config' => true,
402 + 'data-wp-extra-fields' => true,
223 403 ),
224 404 'select' => array(
225 - 'id' => true,
226 - 'name' => true,
227 - 'class' => true,
228 - 'style' => true,
229 - 'disabled' => true,
230 - 'multiple' => true,
231 - 'size' => true,
232 - 'autocomplete' => true,
405 + 'id' => true,
406 + 'name' => true,
407 + 'class' => true,
408 + 'style' => true,
409 + 'disabled' => true,
410 + 'multiple' => true,
411 + 'size' => true,
412 + 'autocomplete' => true,
233 413 // Tom Select data attributes (built-in Settings API feature).
234 - 'data-wp-prefix' => true,
235 - 'data-wp-action' => true,
236 - 'data-wp-nonce' => true,
237 - 'data-wp-endpoint' => true,
238 - 'data-ts-config' => true,
414 + 'data-wp-prefix' => true,
415 + 'data-wp-action' => true,
416 + 'data-wp-nonce' => true,
417 + 'data-wp-endpoint' => true,
418 + 'data-ts-config' => true,
419 + 'data-wp-extra-fields' => true,
239 420 ),
240 421 'option' => array(
241 422 'value' => true,
242 423 'selected' => true,
@@ -264,14 +445,33 @@
264 445 'class' => true,
265 446 'style' => true,
266 447 ),
267 448 'button' => array(
268 - 'type' => true,
269 - 'id' => true,
270 - 'class' => true,
271 - 'style' => true,
272 - 'disabled' => true,
449 + 'type' => true,
450 + 'id' => true,
451 + 'class' => true,
452 + 'style' => true,
453 + 'disabled' => true,
454 + 'aria-controls' => true,
455 + 'aria-expanded' => true,
456 + 'aria-label' => true,
273 457 ),
458 + 'div' => array(
459 + 'id' => true,
460 + 'class' => true,
461 + 'style' => true,
462 + 'aria-hidden' => true,
463 + 'aria-busy' => true,
464 + 'role' => true,
465 + ),
466 + 'span' => array(
467 + 'class' => true,
468 + 'style' => true,
469 + 'aria-hidden' => true,
470 + 'aria-live' => true,
471 + 'aria-atomic' => true,
472 + 'role' => true,
473 + ),
274 474 'template' => array(
275 475 'class' => true,
276 476 'data-id' => true,
277 477 ),
@@ -292,9 +492,9 @@
292 492
293 493 /**
294 494 * Miscellaneous callback funcion
295 495 *
296 - * @param array $args Arguments array.
496 + * @param array $args Arguments array.
297 497 * @return void
298 498 */
299 499 public function callback_missing( $args ) {
300 500 /* translators: 1: Code. */
@@ -305,9 +505,9 @@
305 505 * Header Callback
306 506 *
307 507 * Renders the header.
308 508 *
309 - * @param array $args Arguments passed by the setting.
509 + * @param array $args Arguments passed by the setting.
310 510 * @return void
311 511 */
312 512 public function callback_header( $args ) {
313 513 $html = $this->get_field_description( $args );
@@ -325,9 +525,9 @@
325 525 * Descriptive text callback.
326 526 *
327 527 * Renders descriptive text onto the settings field.
328 528 *
329 - * @param array $args Array of arguments.
529 + * @param array $args Array of arguments.
330 530 * @return void
331 531 */
332 532 public function callback_descriptive_text( $args ) {
333 533 $this->callback_header( $args );
@@ -335,9 +535,9 @@
335 535
336 536 /**
337 537 * Build additional attributes string from field_attributes.
338 538 *
339 - * @param array $args Field arguments.
539 + * @param array $args Field arguments.
340 540 * @return string Additional attributes string.
341 541 */
342 542 protected function build_field_attributes( $args ) {
343 543 $attributes = '';
@@ -379,13 +579,41 @@
379 579 $placeholder
380 580 );
381 581 $html .= $this->get_field_description( $args );
382 582
383 - /** This filter has been defined in class-settings-api.php */
583 + /**
584 + * This filter has been defined in class-settings-api.php
585 +*/
384 586 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
385 587 }
386 588
387 589 /**
590 + * Display local date/time fields.
591 + *
592 + * @param array $args Field arguments.
593 + */
594 + public function callback_datetime( $args ) {
595 + $value = $this->get_field_value( $args );
596 + $class = $this->get_field_class( $args );
597 + $attributes = $this->get_boolean_attributes( $args ) . $this->build_field_attributes( $args );
598 + $placeholder = $this->get_placeholder_attribute( $args );
599 + $field_attributes = $this->get_field_attributes( $args );
600 +
601 + $html = sprintf(
602 + '<input type="datetime-local" id="%1$s" name="%2$s" class="%3$s" value="%4$s" step="60" %5$s %6$s />',
603 + $field_attributes['field_id'],
604 + $field_attributes['field_name'],
605 + $class . ' regular-text',
606 + esc_attr( stripslashes( (string) $value ) ),
607 + $attributes,
608 + $placeholder
609 + );
610 + $html .= $this->get_field_description( $args );
611 +
612 + echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
613 + }
614 +
615 + /**
388 616 * Display url fields.
389 617 *
390 618 * @param array $args Array of arguments.
391 619 */
@@ -433,9 +661,9 @@
433 661
434 662 /**
435 663 * Display textarea.
436 664 *
437 - * @param array $args Array of arguments.
665 + * @param array $args Array of arguments.
438 666 * @return void
439 667 */
440 668 public function callback_textarea( $args ) {
441 669 $value = $this->get_field_value( $args );
@@ -455,9 +683,11 @@
455 683 $placeholder
456 684 );
457 685 $html .= $this->get_field_description( $args );
458 686
459 - /** This filter has been defined in class-settings-api.php */
687 + /**
688 + * This filter has been defined in class-settings-api.php
689 +*/
460 690 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
461 691 }
462 692
463 693 /**
@@ -462,9 +692,9 @@
462 692
463 693 /**
464 694 * Display CSS fields.
465 695 *
466 - * @param array $args Array of arguments.
696 + * @param array $args Array of arguments.
467 697 * @return void
468 698 */
469 699 public function callback_css( $args ) {
470 700 $this->callback_textarea( $args );
@@ -472,9 +702,9 @@
472 702
473 703 /**
474 704 * Display HTML fields.
475 705 *
476 - * @param array $args Array of arguments.
706 + * @param array $args Array of arguments.
477 707 * @return void
478 708 */
479 709 public function callback_html( $args ) {
480 710 $this->callback_textarea( $args );
@@ -482,54 +712,89 @@
482 712
483 713 /**
484 714 * Get disabled attribute for pro/premium fields.
485 715 *
486 - * @param array $args Field arguments.
716 + * @param array $args Field arguments.
487 717 * @return string Disabled attribute or empty string.
488 718 */
489 719 protected function get_disabled_attribute( $args ) {
490 - return ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
720 + return $this->is_field_disabled( $args ) ? ' disabled="disabled"' : '';
491 721 }
492 722
493 723 /**
494 724 * Display checkboxes.
495 725 *
496 - * @param array $args Array of arguments.
726 + * Renders as a toggle switch by default: a native checkbox wrapped in a
727 + * styled label. Pass `no-toggle` in `field_class` to render a plain
728 + * checkbox instead. Posting semantics are identical either way - a hidden
729 + * field posts -1 when unchecked and the checkbox posts 1 when checked.
730 + *
731 + * @param array $args Array of arguments.
497 732 * @return void
498 733 */
499 734 public function callback_checkbox( $args ) {
500 - $value = $this->get_field_value( $args );
501 - $checked = ! empty( $value ) ? checked( 1, $value, false ) : '';
502 - $default = isset( $args['default'] ) ? (int) $args['default'] : '';
503 - $disabled = $this->get_disabled_attribute( $args );
735 + $value = $this->get_field_value( $args );
736 + $checked = ! empty( $value ) ? checked( 1, $value, false ) : '';
737 + $disabled = $this->get_disabled_attribute( $args );
738 + $class = $this->get_field_class( $args );
739 + $no_toggle = in_array( 'no-toggle', explode( ' ', $class ), true );
504 740
505 741 $field_attributes = $this->get_field_attributes( $args );
506 742
507 - $html = sprintf(
508 - '<input type="hidden" name="%1$s" value="-1" />',
509 - $field_attributes['field_name']
510 - );
511 - $html .= sprintf(
512 - '<input type="checkbox" id="%1$s" name="%2$s" value="1" %3$s %4$s />',
513 - $field_attributes['field_id'],
743 + // The hidden input is also disabled when the field is disabled so that
744 + // the setting key is not saved at all and retains its default value.
745 + $html = sprintf(
746 + '<input type="hidden" name="%1$s" value="-1" %2$s />',
514 747 $field_attributes['field_name'],
515 - $checked,
516 748 $disabled
517 749 );
518 - $checkbox_modified = $this->translation_strings['checkbox_modified'] ?? 'Modified from default setting';
519 - $html .= ( (bool) $value !== (bool) $default ) ? '<em style="color:#9B0800">' . $checkbox_modified . '</em>' : '';
520 - $html .= $this->get_field_description( $args );
521 750
522 - /** This filter has been defined in class-settings-api.php */
751 + if ( $no_toggle ) {
752 + $html .= sprintf(
753 + '<input type="checkbox" id="%1$s" name="%2$s" value="1" class="%3$s" %4$s %5$s />',
754 + $field_attributes['field_id'],
755 + $field_attributes['field_name'],
756 + $class,
757 + $checked,
758 + $disabled
759 + );
760 + } else {
761 + $html .= sprintf(
762 + '<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>',
763 + $field_attributes['field_id'],
764 + $field_attributes['field_name'],
765 + $class,
766 + $checked,
767 + $disabled
768 + );
769 + }
770 +
771 + $html .= $this->get_field_description( $args );
772 +
773 + /**
774 + * This filter has been defined in class-settings-api.php
775 +*/
523 776 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
524 777 }
525 778
526 779 /**
780 + * Toggle Callback
781 + *
782 + * Alias of the checkbox type, which renders as a toggle switch.
783 + *
784 + * @param array $args Array of arguments.
785 + * @return void
786 + */
787 + public function callback_toggle( $args ) {
788 + $this->callback_checkbox( $args );
789 + }
790 +
791 + /**
527 792 * Multicheck Callback
528 793 *
529 794 * Renders multiple checkboxes.
530 795 *
531 - * @param array $args Array of arguments.
796 + * @param array $args Array of arguments.
532 797 * @return void
533 798 */
534 799 public function callback_multicheck( $args ) {
535 800 $html = '';
@@ -552,20 +817,20 @@
552 817 } else {
553 818 $enabled = null;
554 819 }
555 820
556 - $option_id = $field_attributes['field_id'] . '-' . sanitize_key( $key );
557 - $option_name = $field_attributes['field_name'] . '[' . sanitize_key( $key ) . ']';
821 + $option_id = $field_attributes['field_id'] . '-' . sanitize_key( $key );
822 + $option_name = $field_attributes['field_name'] . '[' . sanitize_key( $key ) . ']';
558 823
559 - $html .= sprintf(
560 - '<input name="%1$s" id="%2$s" type="checkbox" value="%3$s" %4$s %5$s /> ',
561 - $option_name,
562 - $option_id,
563 - esc_attr( $key ),
564 - checked( $key, $enabled, false ),
565 - $disabled
566 - );
567 - $html .= sprintf(
824 + $html .= sprintf(
825 + '<input name="%1$s" id="%2$s" type="checkbox" value="%3$s" %4$s %5$s /> ',
826 + $option_name,
827 + $option_id,
828 + esc_attr( $key ),
829 + checked( $key, $enabled, false ),
830 + $disabled
831 + );
832 + $html .= sprintf(
568 833 '<label for="%1$s">%2$s</label> <br />',
569 834 $option_id,
570 835 wp_kses_post( $option )
571 836 );
@@ -572,9 +837,11 @@
572 837 }
573 838 }
574 839 $html .= $this->get_field_description( $args );
575 840
576 - /** This filter has been defined in class-settings-api.php */
841 + /**
842 + * This filter has been defined in class-settings-api.php
843 +*/
577 844 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
578 845 }
579 846
580 847 /**
@@ -581,9 +848,9 @@
581 848 * Radio Callback
582 849 *
583 850 * Renders radio boxes.
584 851 *
585 - * @param array $args Array of arguments.
852 + * @param array $args Array of arguments.
586 853 * @return void
587 854 */
588 855 public function callback_radio( $args ) {
589 856 $html = '';
@@ -612,9 +879,11 @@
612 879 }
613 880
614 881 $html .= $this->get_field_description( $args );
615 882
616 - /** This filter has been defined in class-settings-api.php */
883 + /**
884 + * This filter has been defined in class-settings-api.php
885 +*/
617 886 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
618 887 }
619 888
620 889 /**
@@ -621,9 +890,9 @@
621 890 * Radio callback with description.
622 891 *
623 892 * Renders radio boxes with each item having it separate description.
624 893 *
625 - * @param array $args Array of arguments.
894 + * @param array $args Array of arguments.
626 895 * @return void
627 896 */
628 897 public function callback_radiodesc( $args ) {
629 898 $html = '';
@@ -655,9 +924,11 @@
655 924 }
656 925
657 926 $html .= $this->get_field_description( $args );
658 927
659 - /** This filter has been defined in class-settings-api.php */
928 + /**
929 + * This filter has been defined in class-settings-api.php
930 +*/
660 931 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
661 932 }
662 933
663 934 /**
@@ -664,9 +935,9 @@
664 935 * Radio callback with description.
665 936 *
666 937 * Renders radio boxes with each item having it separate description.
667 938 *
668 - * @param array $args Array of arguments.
939 + * @param array $args Array of arguments.
669 940 * @return void
670 941 */
671 942 public function callback_thumbsizes( $args ) {
672 943 $html = '';
@@ -707,9 +978,11 @@
707 978 }
708 979
709 980 $html .= $this->get_field_description( $args );
710 981
711 - /** This filter has been defined in class-settings-api.php */
982 + /**
983 + * This filter has been defined in class-settings-api.php
984 +*/
712 985 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
713 986 }
714 987
715 988 /**
@@ -716,9 +989,9 @@
716 989 * Number Callback
717 990 *
718 991 * Renders number fields.
719 992 *
720 - * @param array $args Array of arguments.
993 + * @param array $args Array of arguments.
721 994 * @return void
722 995 */
723 996 public function callback_number( $args ) {
724 997 $value = $this->get_field_value( $args );
@@ -723,9 +996,9 @@
723 996 public function callback_number( $args ) {
724 997 $value = $this->get_field_value( $args );
725 998 $max = isset( $args['max'] ) ? intval( $args['max'] ) : 999999;
726 999 $min = isset( $args['min'] ) ? intval( $args['min'] ) : 0;
727 - $step = isset( $args['step'] ) ? intval( $args['step'] ) : 1;
1000 + $step = isset( $args['step'] ) && ( is_numeric( $args['step'] ) || 'any' === $args['step'] ) ? $args['step'] : 1;
728 1001 $size = $args['size'] ?? 'regular';
729 1002 $placeholder = $this->get_placeholder_attribute( $args );
730 1003 $attributes = $this->get_boolean_attributes( $args );
731 1004
@@ -744,9 +1017,11 @@
744 1017 $attributes
745 1018 );
746 1019 $html .= $this->get_field_description( $args );
747 1020
748 - /** This filter has been defined in class-settings-api.php */
1021 + /**
1022 + * This filter has been defined in class-settings-api.php
1023 +*/
749 1024 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
750 1025 }
751 1026
752 1027 /**
@@ -753,9 +1028,9 @@
753 1028 * Select Callback
754 1029 *
755 1030 * Renders select fields.
756 1031 *
757 - * @param array $args Array of arguments.
1032 + * @param array $args Array of arguments.
758 1033 * @return void
759 1034 */
760 1035 public function callback_select( $args ) {
761 1036 $value = $this->get_field_value( $args );
@@ -783,9 +1058,11 @@
783 1058
784 1059 $html .= '</select>';
785 1060 $html .= $this->get_field_description( $args );
786 1061
787 - /** This filter has been defined in class-settings-api.php */
1062 + /**
1063 + * This filter has been defined in class-settings-api.php
1064 +*/
788 1065 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
789 1066 }
790 1067
791 1068 /**
@@ -790,9 +1067,9 @@
790 1067
791 1068 /**
792 1069 * Display posttypes fields.
793 1070 *
794 - * @param array $args Array of arguments.
1071 + * @param array $args Array of arguments.
795 1072 * @return void
796 1073 */
797 1074 public function callback_posttypes( $args ) {
798 1075 $html = '';
@@ -840,9 +1117,11 @@
840 1117 }
841 1118
842 1119 $html .= $this->get_field_description( $args );
843 1120
844 - /** This filter has been defined in class-settings-api.php */
1121 + /**
1122 + * This filter has been defined in class-settings-api.php
1123 +*/
845 1124 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
846 1125 }
847 1126
848 1127
@@ -848,9 +1127,9 @@
848 1127
849 1128 /**
850 1129 * Display taxonomies fields.
851 1130 *
852 - * @param array $args Array of arguments.
1131 + * @param array $args Array of arguments.
853 1132 * @return void
854 1133 */
855 1134 public function callback_taxonomies( $args ) {
856 1135 $html = '';
@@ -894,9 +1173,11 @@
894 1173 }
895 1174
896 1175 $html .= $this->get_field_description( $args );
897 1176
898 - /** This filter has been defined in class-settings-api.php */
1177 + /**
1178 + * This filter has been defined in class-settings-api.php
1179 +*/
899 1180 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
900 1181 }
901 1182
902 1183
@@ -955,9 +1236,11 @@
955 1236 );
956 1237 $html .= sprintf( '<input type="button" class="button button-secondary file-browser" value="%s" />', esc_attr( $label ) );
957 1238 $html .= $this->get_field_description( $args );
958 1239
959 - /** This filter has been defined in class-settings-api.php */
1240 + /**
1241 + * This filter has been defined in class-settings-api.php
1242 +*/
960 1243 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
961 1244 }
962 1245
963 1246 /**
@@ -981,9 +1264,11 @@
981 1264 ! empty( $value ) ? 'placeholder="' . esc_attr( $this->translation_strings['previous_saved'] ) . '"' : ''
982 1265 );
983 1266 $html .= $this->get_field_description( $args );
984 1267
985 - /** This filter has been defined in class-settings-api.php */
1268 + /**
1269 + * This filter has been defined in class-settings-api.php
1270 +*/
986 1271 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
987 1272 }
988 1273
989 1274 /**
@@ -988,9 +1273,9 @@
988 1273
989 1274 /**
990 1275 * Callback for repeater field.
991 1276 *
992 - * @param array $args Array of arguments.
1277 + * @param array $args Array of arguments.
993 1278 * @return void
994 1279 */
995 1280 public function callback_repeater( $args ) {
996 1281 $raw_value = $this->get_field_value( $args );
@@ -998,8 +1283,13 @@
998 1283
999 1284 $class = $this->get_field_class( $args );
1000 1285 $attributes = $this->get_boolean_attributes( $args ) . $this->build_field_attributes( $args );
1001 1286
1287 + // `disabled` does nothing on the wrapper; the class neutralises the buttons.
1288 + if ( $this->is_field_disabled( $args ) ) {
1289 + $class .= ' wz-repeater-disabled';
1290 + }
1291 +
1002 1292 $data_index = (string) count( $value );
1003 1293 $live_update_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1004 1294 $fallback_title = ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'];
1005 1295 $live_update_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
@@ -1010,28 +1300,31 @@
1010 1300 id="<?php echo esc_attr( $args['id'] ); ?>-wrapper"
1011 1301 data-index="<?php echo esc_attr( $data_index ); ?>"
1012 1302 data-live-update-field="<?php echo esc_attr( $live_update_field ); ?>"
1013 1303 data-fallback-title="<?php echo esc_attr( $fallback_title ); ?>"
1014 - <?php if ( ! empty( $live_update_options ) ) : ?>
1304 + <?php if ( ! empty( $live_update_options ) ) : ?>
1015 1305 data-live-update-field-options="<?php echo esc_attr( wp_json_encode( $live_update_options ) ); ?>"
1016 - <?php endif; ?>
1017 - <?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
1306 + <?php endif; ?>
1307 + <?php if ( ! empty( $args['unique_field'] ) ) : ?>
1308 + data-unique-field="<?php echo esc_attr( $args['unique_field'] ); ?>"
1309 + <?php endif; ?>
1310 + <?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
1018 1311
1019 1312 <div class="<?php echo esc_attr( $args['id'] ); ?>-items wz-repeater-items">
1020 - <?php
1021 - if ( ! empty( $value ) ) {
1022 - foreach ( array_values( $value ) as $index => $item ) {
1023 - $this->render_repeater_item( $args, $index, $item );
1024 - }
1025 - }
1026 - ?>
1313 + <?php
1314 + if ( ! empty( $value ) ) {
1315 + foreach ( array_values( $value ) as $index => $item ) {
1316 + $this->render_repeater_item( $args, $index, $item );
1317 + }
1318 + }
1319 + ?>
1027 1320 </div>
1028 - <button type="button" class="button add-item" data-target="<?php echo esc_attr( $args['id'] ); ?>">
1029 - <?php echo esc_html( ! empty( $args['add_button_text'] ) ? $args['add_button_text'] : 'Add Item' ); ?>
1321 + <button type="button" class="button add-item" data-target="<?php echo esc_attr( $args['id'] ); ?>" <?php disabled( $this->is_field_disabled( $args ) ); ?>>
1322 + <?php echo esc_html( ! empty( $args['add_button_text'] ) ? $args['add_button_text'] : 'Add Item' ); ?>
1030 1323 </button>
1031 1324
1032 1325 <template class="repeater-template" data-id="<?php echo esc_attr( $args['id'] ); ?>">
1033 - <?php $this->render_repeater_item( $args, '{{INDEX}}' ); ?>
1326 + <?php $this->render_repeater_item( $args, '{{INDEX}}' ); ?>
1034 1327 </template>
1035 1328 </div>
1036 1329 <?php
1037 1330 $html = ob_get_clean();
@@ -1036,9 +1329,11 @@
1036 1329 <?php
1037 1330 $html = ob_get_clean();
1038 1331 $html .= $this->get_field_description( $args );
1039 1332
1040 - /** This filter has been defined in class-settings-api.php */
1333 + /**
1334 + * This filter has been defined in class-settings-api.php
1335 +*/
1041 1336 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1042 1337 }
1043 1338
1044 1339 /**
@@ -1043,11 +1338,11 @@
1043 1338
1044 1339 /**
1045 1340 * Render a single repeater item.
1046 1341 *
1047 - * @param array $args Repeater field arguments.
1048 - * @param string|int $index Current item index.
1049 - * @param array|null $item Item data if exists.
1342 + * @param array $args Repeater field arguments.
1343 + * @param string|int $index Current item index.
1344 + * @param array|null $item Item data if exists.
1050 1345 * @return void
1051 1346 */
1052 1347 public function render_repeater_item( $args, $index, $item = null ) {
1053 1348 if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
@@ -1067,84 +1362,101 @@
1067 1362 // For new items, use a placeholder that will be replaced.
1068 1363 $item_id = '{{ROW_ID}}';
1069 1364 }
1070 1365
1366 + $parent_disabled = $this->is_field_disabled( $args );
1367 + $content_id = sprintf(
1368 + '%s-%s-%s-content',
1369 + sanitize_key( $this->settings_key ),
1370 + sanitize_key( $args['id'] ),
1371 + '{{ROW_ID}}' === $item_id ? '{{ROW_ID}}' : sanitize_key( $item_id )
1372 + );
1071 1373 ?>
1072 1374 <div class="wz-repeater-item" data-row-id="<?php echo esc_attr( $item_id ); ?>">
1073 - <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 ); ?>" />
1074 - <div class="repeater-item-header">
1075 - <?php
1076 - $display_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1077 - $live_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
1078 - $raw_live_value = ! empty( $item['fields'][ $display_field ] ) ? (string) $item['fields'][ $display_field ] : '';
1079 - $display_value = '' !== $raw_live_value
1080 - ? ( isset( $live_options[ $raw_live_value ] ) ? $live_options[ $raw_live_value ] : $raw_live_value )
1081 - : $fallback_title;
1082 - ?>
1375 + <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 ); ?>" <?php disabled( $parent_disabled ); ?> />
1376 + <button type="button" class="repeater-item-header" aria-expanded="false" aria-controls="<?php echo esc_attr( $content_id ); ?>">
1377 + <?php
1378 + $display_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1379 + $live_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
1380 + $raw_live_value = ! empty( $item['fields'][ $display_field ] ) ? (string) $item['fields'][ $display_field ] : '';
1381 + $display_value = '' !== $raw_live_value
1382 + ? ( isset( $live_options[ $raw_live_value ] ) ? $live_options[ $raw_live_value ] : $raw_live_value )
1383 + : $fallback_title;
1384 + ?>
1083 1385 <span class="repeater-title"><?php echo esc_html( $display_value ); ?></span>
1084 - <span class="toggle-icon">▼</span>
1085 - </div>
1086 - <div class="repeater-item-content" style="display: none;">
1087 - <?php
1088 - foreach ( $args['fields'] as $field ) {
1089 - $field_id = sanitize_key( $field['id'] );
1386 + <span class="toggle-icon" aria-hidden="true">▼</span>
1387 + </button>
1388 + <div id="<?php echo esc_attr( $content_id ); ?>" class="repeater-item-content" aria-hidden="true" style="display: none;">
1389 + <?php
1390 + foreach ( $args['fields'] as $field ) {
1391 + $field_id = sanitize_key( $field['id'] );
1090 1392
1091 - $field_args = array_merge(
1092 - (array) $field,
1093 - array(
1094 - 'value' => isset( $item['fields'][ $field_id ] ) ? $item['fields'][ $field_id ] : ( isset( $field['default'] ) ? $field['default'] : '' ),
1095 - '_repeater_id' => $args['id'],
1096 - '_index' => $index,
1097 - )
1098 - );
1099 - $field_args = Settings_API::parse_field_args( $field_args, $args['section'] );
1393 + $field_args = array_merge(
1394 + (array) $field,
1395 + array(
1396 + 'value' => isset( $item['fields'][ $field_id ] ) ? $item['fields'][ $field_id ] : ( isset( $field['default'] ) ? $field['default'] : '' ),
1397 + '_repeater_id' => $args['id'],
1398 + '_index' => $index,
1399 + )
1400 + );
1100 1401
1101 - if ( ! isset( $field['type'] ) || ! is_string( $field['type'] ) ) {
1402 + // Subfields do not inherit the repeater's own arguments.
1403 + if ( $parent_disabled ) {
1404 + $field_args['disabled'] = true;
1405 + }
1406 +
1407 + // Rows collapse, and a browser blocks submit on a hidden required
1408 + // control without reporting why. The label keeps its asterisk.
1409 + $field_args['required'] = false;
1410 +
1411 + $field_args = Settings_API::parse_field_args( $field_args, $args['section'] );
1412 +
1413 + if ( ! isset( $field['type'] ) || ! is_string( $field['type'] ) ) {
1102 1414 continue;
1103 - }
1104 - ?>
1105 - <?php $repeater_field_attributes = $this->get_field_attributes( $field_args ); ?>
1415 + }
1416 + ?>
1417 + <?php $repeater_field_attributes = $this->get_field_attributes( $field_args ); ?>
1106 1418 <div class="wz-repeater-field">
1107 1419 <div class="wz-repeater-field-header">
1108 1420 <label class="wz-repeater-field-label" for="<?php echo esc_attr( $repeater_field_attributes['field_id'] ); ?>">
1109 - <?php echo esc_html( $field['name'] ); ?>
1110 - <?php if ( ! empty( $field['required'] ) ) : ?>
1421 + <?php echo esc_html( $field['name'] ); ?>
1422 + <?php if ( ! empty( $field['required'] ) ) : ?>
1111 1423 <span class="required" title="<?php echo esc_attr( $this->translation_strings['required_label'] ); ?>">*</span>
1112 - <?php endif; ?>
1424 + <?php endif; ?>
1113 1425 </label>
1114 1426 </div>
1115 1427
1116 1428 <div class="wz-repeater-field-input">
1117 - <?php
1118 - $callback = 'callback_' . $field['type'];
1429 + <?php
1430 + $callback = 'callback_' . $field['type'];
1119 1431
1120 - if ( method_exists( $this, $callback ) ) {
1121 - $this->$callback( $field_args );
1122 - } else {
1123 - do_action( "{$this->prefix}_repeater_field_{$field['type']}", $field_args, $index ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1124 - }
1125 - ?>
1432 + if ( method_exists( $this, $callback ) ) {
1433 + $this->$callback( $field_args );
1434 + } else {
1435 + do_action( "{$this->prefix}_repeater_field_{$field['type']}", $field_args, $index ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
1436 + }
1437 + ?>
1126 1438 </div>
1127 1439 </div>
1128 - <?php } ?>
1440 + <?php } ?>
1129 1441 </div>
1130 1442
1131 1443 <div class="repeater-item-footer">
1132 1444 <div class="repeater-item-actions">
1133 - <button type="button" class="button button-secondary move-up">
1134 - <span class="dashicons dashicons-arrow-up-alt2"></span>
1445 + <button type="button" class="button button-secondary move-up" aria-label="<?php echo esc_attr( $this->translation_strings['repeater_move_up'] ?? 'Move item up' ); ?>" <?php disabled( $parent_disabled ); ?>>
1446 + <span class="dashicons dashicons-arrow-up-alt2" aria-hidden="true"></span>
1135 1447 </button>
1136 - <button type="button" class="button button-secondary move-down">
1137 - <span class="dashicons dashicons-arrow-down-alt2"></span>
1448 + <button type="button" class="button button-secondary move-down" aria-label="<?php echo esc_attr( $this->translation_strings['repeater_move_down'] ?? 'Move item down' ); ?>" <?php disabled( $parent_disabled ); ?>>
1449 + <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
1138 1450 </button>
1139 - <button type="button" class="button button-secondary remove-item">
1140 - <span class="dashicons dashicons-trash"></span>
1451 + <button type="button" class="button button-secondary remove-item" aria-label="<?php echo esc_attr( $this->translation_strings['repeater_remove_item'] ?? 'Remove item' ); ?>" <?php disabled( $parent_disabled ); ?>>
1452 + <span class="dashicons dashicons-trash" aria-hidden="true"></span>
1141 1453 </button>
1142 1454 </div>
1143 1455 </div>
1144 1456 </div>
1145 1457
1146 - <?php
1458 + <?php
1147 1459 }
1148 1460
1149 1461
1150 1462
@@ -1149,10 +1461,8 @@
1149 1461
1150 1462
1151 1463 /**
1152 1464 * Display sensitive fields.
1153 - *
1154 - * @since 1.0.0
1155 1465 *
1156 1466 * @param array $args Array of arguments.
1157 1467 */
1158 1468 public function callback_sensitive( $args ) {