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 +316 -40 4.3.2 → 4.5.2 View file →
@@ -15,8 +15,10 @@
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
22 24
@@ -71,9 +73,9 @@
71 73 *
72 74 * @return string Description of the field.
73 75 */
74 76 public function get_field_description( $args ) {
75 - $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>' : '';
76 78
77 79 /**
78 80 * After Settings Output filter
79 81 *
@@ -81,12 +83,166 @@
81 83 * @param array $args Arguments array.
82 84 */
83 85 $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
84 86
85 - return $desc;
87 + return $this->get_modified_indicator( $args ) . $desc . $this->get_default_description( $args );
86 88 }
87 89
88 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 + /**
89 245 * Get the value of a settings field.
90 246 *
91 247 * @param string $option Settings field name.
92 248 * @param mixed $default_value Default value if option is not found.
@@ -124,9 +280,9 @@
124 280 $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['field_class'] ?? '' ) ) );
125 281 if ( $default_class ) {
126 282 $class = $default_class . ' ' . $class;
127 283 }
128 - return trim( $class );
284 + return trim( $class, " \t\n\r\0\x0B" );
129 285 }
130 286
131 287 /**
132 288 * Get placeholder attribute string.
@@ -144,9 +300,9 @@
144 300 * @param array $args Field arguments.
145 301 * @return string Concatenated boolean attributes.
146 302 */
147 303 protected function get_boolean_attributes( $args ) {
148 - $disabled = ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
304 + $disabled = $this->is_field_disabled( $args ) ? ' disabled="disabled"' : '';
149 305 $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : '';
150 306 $required = ( isset( $args['required'] ) && true === $args['required'] ) ? ' required' : '';
151 307 return $disabled . $readonly . $required;
152 308 }
@@ -151,8 +307,18 @@
151 307 return $disabled . $readonly . $required;
152 308 }
153 309
154 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 + /**
155 321 * Get field ID and name attributes.
156 322 *
157 323 * @param array $args Field arguments.
158 324 * @return array Array containing field_id and field_name.
@@ -185,8 +351,20 @@
185 351 );
186 352 }
187 353
188 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 + /**
189 367 * Returns the allowed HTML tags and attributes for settings form output.
190 368 *
191 369 * Use the `{prefix}_settings_form_allowed_html` filter to add extra tags or
192 370 * attributes needed by custom field types or `field_attributes` entries.
@@ -267,14 +445,33 @@
267 445 'class' => true,
268 446 'style' => true,
269 447 ),
270 448 'button' => array(
271 - 'type' => true,
272 - 'id' => true,
273 - 'class' => true,
274 - 'style' => true,
275 - '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,
276 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 + ),
277 474 'template' => array(
278 475 'class' => true,
279 476 'data-id' => true,
280 477 ),
@@ -389,8 +586,34 @@
389 586 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
390 587 }
391 588
392 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 + /**
393 616 * Display url fields.
394 617 *
395 618 * @param array $args Array of arguments.
396 619 */
@@ -493,40 +716,61 @@
493 716 * @param array $args Field arguments.
494 717 * @return string Disabled attribute or empty string.
495 718 */
496 719 protected function get_disabled_attribute( $args ) {
497 - return ( ! empty( $args['disabled'] ) || ! empty( $args['pro'] ) ) ? ' disabled="disabled"' : '';
720 + return $this->is_field_disabled( $args ) ? ' disabled="disabled"' : '';
498 721 }
499 722
500 723 /**
501 724 * Display checkboxes.
502 725 *
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 + *
503 731 * @param array $args Array of arguments.
504 732 * @return void
505 733 */
506 734 public function callback_checkbox( $args ) {
507 - $value = $this->get_field_value( $args );
508 - $checked = ! empty( $value ) ? checked( 1, $value, false ) : '';
509 - $default = isset( $args['default'] ) ? (int) $args['default'] : '';
510 - $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 );
511 740
512 741 $field_attributes = $this->get_field_attributes( $args );
513 742
514 - $html = sprintf(
515 - '<input type="hidden" name="%1$s" value="-1" />',
516 - $field_attributes['field_name']
517 - );
518 - $html .= sprintf(
519 - '<input type="checkbox" id="%1$s" name="%2$s" value="1" %3$s %4$s />',
520 - $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 />',
521 747 $field_attributes['field_name'],
522 - $checked,
523 748 $disabled
524 749 );
525 - $checkbox_modified = $this->translation_strings['checkbox_modified'] ?? 'Modified from default setting';
526 - $html .= ( (bool) $value !== (bool) $default ) ? '<em style="color:#9B0800">' . $checkbox_modified . '</em>' : '';
527 - $html .= $this->get_field_description( $args );
528 750
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 +
529 773 /**
530 774 * This filter has been defined in class-settings-api.php
531 775 */
532 776 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
@@ -532,8 +776,20 @@
532 776 echo wp_kses( apply_filters( $this->prefix . '_after_setting_output', $html, $args ), $this->get_allowed_html() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
533 777 }
534 778
535 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 + /**
536 792 * Multicheck Callback
537 793 *
538 794 * Renders multiple checkboxes.
539 795 *
@@ -740,9 +996,9 @@
740 996 public function callback_number( $args ) {
741 997 $value = $this->get_field_value( $args );
742 998 $max = isset( $args['max'] ) ? intval( $args['max'] ) : 999999;
743 999 $min = isset( $args['min'] ) ? intval( $args['min'] ) : 0;
744 - $step = isset( $args['step'] ) ? intval( $args['step'] ) : 1;
1000 + $step = isset( $args['step'] ) && ( is_numeric( $args['step'] ) || 'any' === $args['step'] ) ? $args['step'] : 1;
745 1001 $size = $args['size'] ?? 'regular';
746 1002 $placeholder = $this->get_placeholder_attribute( $args );
747 1003 $attributes = $this->get_boolean_attributes( $args );
748 1004
@@ -1027,8 +1283,13 @@
1027 1283
1028 1284 $class = $this->get_field_class( $args );
1029 1285 $attributes = $this->get_boolean_attributes( $args ) . $this->build_field_attributes( $args );
1030 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 +
1031 1292 $data_index = (string) count( $value );
1032 1293 $live_update_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1033 1294 $fallback_title = ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'];
1034 1295 $live_update_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
@@ -1056,9 +1317,9 @@
1056 1317 }
1057 1318 }
1058 1319 ?>
1059 1320 </div>
1060 - <button type="button" class="button add-item" data-target="<?php echo esc_attr( $args['id'] ); ?>">
1321 + <button type="button" class="button add-item" data-target="<?php echo esc_attr( $args['id'] ); ?>" <?php disabled( $this->is_field_disabled( $args ) ); ?>>
1061 1322 <?php echo esc_html( ! empty( $args['add_button_text'] ) ? $args['add_button_text'] : 'Add Item' ); ?>
1062 1323 </button>
1063 1324
1064 1325 <template class="repeater-template" data-id="<?php echo esc_attr( $args['id'] ); ?>">
@@ -1101,12 +1362,19 @@
1101 1362 // For new items, use a placeholder that will be replaced.
1102 1363 $item_id = '{{ROW_ID}}';
1103 1364 }
1104 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 + );
1105 1373 ?>
1106 1374 <div class="wz-repeater-item" data-row-id="<?php echo esc_attr( $item_id ); ?>">
1107 - <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 ); ?>" />
1108 - <div class="repeater-item-header">
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 ); ?>">
1109 1377 <?php
1110 1378 $display_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
1111 1379 $live_options = ! empty( $args['live_update_field_options'] ) && is_array( $args['live_update_field_options'] ) ? $args['live_update_field_options'] : array();
1112 1380 $raw_live_value = ! empty( $item['fields'][ $display_field ] ) ? (string) $item['fields'][ $display_field ] : '';
@@ -1114,11 +1382,11 @@
1114 1382 ? ( isset( $live_options[ $raw_live_value ] ) ? $live_options[ $raw_live_value ] : $raw_live_value )
1115 1383 : $fallback_title;
1116 1384 ?>
1117 1385 <span class="repeater-title"><?php echo esc_html( $display_value ); ?></span>
1118 - <span class="toggle-icon">▼</span>
1119 - </div>
1120 - <div class="repeater-item-content" style="display: none;">
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;">
1121 1389 <?php
1122 1390 foreach ( $args['fields'] as $field ) {
1123 1391 $field_id = sanitize_key( $field['id'] );
1124 1392
@@ -1129,8 +1397,18 @@
1129 1397 '_repeater_id' => $args['id'],
1130 1398 '_index' => $index,
1131 1399 )
1132 1400 );
1401 +
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 +
1133 1411 $field_args = Settings_API::parse_field_args( $field_args, $args['section'] );
1134 1412
1135 1413 if ( ! isset( $field['type'] ) || ! is_string( $field['type'] ) ) {
1136 1414 continue;
@@ -1163,16 +1441,16 @@
1163 1441 </div>
1164 1442
1165 1443 <div class="repeater-item-footer">
1166 1444 <div class="repeater-item-actions">
1167 - <button type="button" class="button button-secondary move-up">
1168 - <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>
1169 1447 </button>
1170 - <button type="button" class="button button-secondary move-down">
1171 - <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>
1172 1450 </button>
1173 - <button type="button" class="button button-secondary remove-item">
1174 - <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>
1175 1453 </button>
1176 1454 </div>
1177 1455 </div>
1178 1456 </div>
@@ -1183,10 +1461,8 @@
1183 1461
1184 1462
1185 1463 /**
1186 1464 * Display sensitive fields.
1187 - *
1188 - * @since 1.0.0
1189 1465 *
1190 1466 * @param array $args Array of arguments.
1191 1467 */
1192 1468 public function callback_sensitive( $args ) {