PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.4
WooCommerce v10.9.4
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / Admin / Settings / SettingsUISchema.php
woocommerce / src / Internal / Admin / Settings Last commit date
Exceptions 1 year ago PaymentsProviders 3 months ago SettingsUIPages 1 month ago LegacySettingsPageAdapter.php 1 month ago Payments.php 3 months ago PaymentsController.php 11 months ago PaymentsProviders.php 2 months ago PaymentsRestController.php 3 months ago RegisteredSettingsSectionAdapter.php 2 weeks ago SettingsUIPageInterface.php 1 month ago SettingsUIRequestContext.php 1 week ago SettingsUISchema.php 2 weeks ago Utils.php 2 months ago
SettingsUISchema.php
466 lines
1 <?php
2 /**
3 * Settings UI schema builder.
4 */
5
6 declare( strict_types=1 );
7
8 namespace Automattic\WooCommerce\Internal\Admin\Settings;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Builds the canonical settings schema consumed by the settings UI renderer.
14 *
15 * @since 10.9.0
16 */
17 class SettingsUISchema {
18
19 /**
20 * Default group id for fields before the first title marker.
21 *
22 * @var string
23 */
24 private const DEFAULT_GROUP_ID = 'default';
25
26 /**
27 * Build a schema from a legacy WC settings array.
28 *
29 * @since 10.9.0
30 *
31 * @param string $page_id Settings page id.
32 * @param string $section Section id. Empty string means the default section.
33 * @param string $title Page title.
34 * @param array $settings Legacy settings definitions.
35 * @param string $default_save_adapter Default save adapter.
36 * @return array
37 */
38 public static function from_legacy_settings( string $page_id, string $section, string $title, array $settings, string $default_save_adapter = 'form_post' ): array {
39 $groups = array();
40 $current_group = null;
41 $current_id = null;
42 $group_index = 0;
43 $visibility_controller = null;
44
45 foreach ( $settings as $setting ) {
46 if ( ! is_array( $setting ) ) {
47 continue;
48 }
49
50 $type = isset( $setting['type'] ) && is_string( $setting['type'] ) ? $setting['type'] : 'text';
51
52 if ( 'title' === $type ) {
53 $visibility_controller = null;
54 if ( $current_group && $current_id ) {
55 $groups[ $current_id ] = $current_group;
56 }
57
58 $current_id = isset( $setting['id'] ) && is_scalar( $setting['id'] ) && '' !== (string) $setting['id']
59 ? (string) $setting['id']
60 : 'group_' . $group_index;
61 $current_group = array(
62 'id' => $current_id,
63 'title' => isset( $setting['title'] ) && is_scalar( $setting['title'] ) ? html_entity_decode( (string) $setting['title'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) : '',
64 'description' => isset( $setting['desc'] ) && is_scalar( $setting['desc'] ) ? wp_kses_post( (string) $setting['desc'] ) : '',
65 'actions' => self::get_group_actions( $setting ),
66 'order' => isset( $setting['order'] ) ? (int) $setting['order'] : $group_index,
67 'fields' => array(),
68 );
69 ++$group_index;
70 continue;
71 }
72
73 if ( 'sectionend' === $type ) {
74 $visibility_controller = null;
75 if ( $current_group && $current_id ) {
76 $groups[ $current_id ] = $current_group;
77 }
78 $current_group = null;
79 $current_id = null;
80 continue;
81 }
82
83 if ( empty( $setting['id'] ) ) {
84 continue;
85 }
86
87 if ( ! $current_group ) {
88 $current_id = self::DEFAULT_GROUP_ID;
89 $current_group = self::get_default_group( $group_index );
90 ++$group_index;
91 }
92
93 $field = self::transform_legacy_field( $setting, $default_save_adapter, $visibility_controller );
94 if ( $field ) {
95 $current_group['fields'][] = $field;
96 }
97
98 if ( 'checkbox' === $type && 'option' === ( $setting['show_if_checked'] ?? null ) ) {
99 $visibility_controller = $field['id'] ?? null;
100 }
101
102 if ( 'end' === ( $setting['checkboxgroup'] ?? null ) ) {
103 $visibility_controller = null;
104 }
105 }
106
107 if ( $current_group && $current_id ) {
108 $groups[ $current_id ] = $current_group;
109 }
110
111 uasort(
112 $groups,
113 static function ( array $a, array $b ): int {
114 return ( $a['order'] ?? 999 ) <=> ( $b['order'] ?? 999 );
115 }
116 );
117
118 foreach ( $groups as $group_id => $group ) {
119 unset( $group['order'] );
120 $groups[ $group_id ] = $group;
121 }
122
123 $decoded_title = html_entity_decode( $title, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
124
125 return array(
126 'id' => $page_id,
127 'title' => $decoded_title,
128 'section' => '' === $section ? self::DEFAULT_GROUP_ID : $section,
129 'save' => array(
130 'adapter' => $default_save_adapter,
131 ),
132 'shell' => array(
133 'title' => $decoded_title,
134 ),
135 'groups' => $groups,
136 );
137 }
138
139 /**
140 * Transform a legacy field into the canonical schema.
141 *
142 * @param array $setting Legacy field definition.
143 * @param string $default_save_adapter Default save adapter.
144 * @param string|null $visibility_controller Current checkbox group controller.
145 * @return array|null
146 */
147 private static function transform_legacy_field( array $setting, string $default_save_adapter, ?string $visibility_controller = null ): ?array {
148 $id = isset( $setting['id'] ) && is_scalar( $setting['id'] ) ? (string) $setting['id'] : '';
149 $type = isset( $setting['type'] ) && is_string( $setting['type'] ) ? $setting['type'] : 'text';
150 if ( '' === $id ) {
151 return null;
152 }
153
154 $canonical_type = self::normalize_type( $type );
155 $field = array(
156 'id' => $id,
157 'label' => self::get_field_label( $setting, $id, $type ),
158 'type' => $canonical_type,
159 'description' => self::get_field_description( $setting, $type ),
160 'value' => self::get_field_value( $setting, $canonical_type ),
161 'save' => self::get_save_schema( $setting, $default_save_adapter ),
162 );
163
164 foreach ( array( 'component', 'placeholder', 'disabled' ) as $key ) {
165 if ( array_key_exists( $key, $setting ) ) {
166 $field[ $key ] = $setting[ $key ];
167 }
168 }
169
170 if ( isset( $setting['custom_attributes'] ) && is_array( $setting['custom_attributes'] ) ) {
171 $field['customAttributes'] = self::get_custom_attributes( $setting['custom_attributes'] );
172 }
173
174 $visibility = self::get_field_visibility( $setting, $visibility_controller );
175 if ( $visibility ) {
176 $field['visibility'] = $visibility;
177 }
178
179 $options = self::get_options( $setting );
180 if ( ! empty( $options ) ) {
181 $field['options'] = $options;
182 }
183
184 if ( 'info' === $type && '' === $field['description'] && isset( $setting['text'] ) && is_scalar( $setting['text'] ) ) {
185 $field['description'] = wp_kses_post( (string) $setting['text'] );
186 $field['save'] = array( 'adapter' => 'none' );
187 }
188
189 return $field;
190 }
191
192 /**
193 * Get a field label.
194 *
195 * @param array $setting Legacy field definition.
196 * @param string $id Field id.
197 * @param string $type Raw field type.
198 * @return string
199 */
200 private static function get_field_label( array $setting, string $id, string $type ): string {
201 if ( 'checkbox' === $type && isset( $setting['desc'] ) && is_scalar( $setting['desc'] ) && '' !== (string) $setting['desc'] ) {
202 return wp_strip_all_tags( html_entity_decode( (string) $setting['desc'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
203 }
204
205 foreach ( array( 'title', 'name' ) as $key ) {
206 if ( isset( $setting[ $key ] ) && is_scalar( $setting[ $key ] ) && '' !== (string) $setting[ $key ] ) {
207 return wp_strip_all_tags( html_entity_decode( (string) $setting[ $key ], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
208 }
209 }
210
211 return $id;
212 }
213
214 /**
215 * Get a field description.
216 *
217 * @param array $setting Legacy field definition.
218 * @param string $type Raw field type.
219 * @return string
220 */
221 private static function get_field_description( array $setting, string $type ): string {
222 $description = 'checkbox' === $type || ! isset( $setting['desc'] ) || ! is_scalar( $setting['desc'] )
223 ? ''
224 : wp_kses_post( (string) $setting['desc'] );
225
226 $desc_tip = isset( $setting['desc_tip'] ) && is_string( $setting['desc_tip'] ) && '' !== $setting['desc_tip']
227 ? wp_kses_post( $setting['desc_tip'] )
228 : '';
229
230 if ( '' === $description ) {
231 return $desc_tip;
232 }
233
234 if ( '' === $desc_tip ) {
235 return $description;
236 }
237
238 return $description . '<br />' . $desc_tip;
239 }
240
241 /**
242 * Normalize legacy field type.
243 *
244 * @param string $type Legacy field type.
245 * @return string
246 */
247 private static function normalize_type( string $type ): string {
248 $type_map = array(
249 'multiselect' => 'array',
250 'multi_select_countries' => 'array',
251 'single_select_country' => 'select',
252 'single_select_page' => 'select',
253 );
254
255 return $type_map[ $type ] ?? $type;
256 }
257
258 /**
259 * Get a field value.
260 *
261 * @param array $setting Legacy field definition.
262 * @param string $type Canonical field type.
263 * @return mixed
264 */
265 private static function get_field_value( array $setting, string $type ) {
266 if ( array_key_exists( 'value', $setting ) ) {
267 return self::normalize_value( $setting['value'], $type );
268 }
269
270 $default = $setting['default'] ?? '';
271 $value = \WC_Admin_Settings::get_option( (string) $setting['id'], $default );
272
273 return self::normalize_value( $value, $type );
274 }
275
276 /**
277 * Normalize a value for the canonical schema.
278 *
279 * @param mixed $value Field value.
280 * @param string $type Canonical type.
281 * @return mixed
282 */
283 private static function normalize_value( $value, string $type ) {
284 switch ( $type ) {
285 case 'array':
286 return is_array( $value ) ? array_values( $value ) : array();
287 case 'checkbox':
288 return function_exists( 'wc_string_to_bool' ) ? wc_string_to_bool( $value ) : (bool) $value;
289 default:
290 return $value;
291 }
292 }
293
294 /**
295 * Get a field save schema.
296 *
297 * @param array $setting Legacy field definition.
298 * @param string $default_save_adapter Default save adapter.
299 * @return array
300 */
301 private static function get_save_schema( array $setting, string $default_save_adapter ): array {
302 if ( isset( $setting['save'] ) && is_array( $setting['save'] ) ) {
303 return $setting['save'];
304 }
305
306 if ( isset( $setting['is_option'] ) && false === $setting['is_option'] ) {
307 return array( 'adapter' => 'none' );
308 }
309
310 $field_name = isset( $setting['field_name'] ) && is_scalar( $setting['field_name'] )
311 ? (string) $setting['field_name']
312 : (string) $setting['id'];
313
314 return array(
315 'adapter' => $default_save_adapter,
316 'name' => $field_name,
317 );
318 }
319
320 /**
321 * Get visibility metadata for legacy conditional fields.
322 *
323 * @param array $setting Legacy field definition.
324 * @param string|null $visibility_controller Current checkbox group controller.
325 * @return array|null
326 */
327 private static function get_field_visibility( array $setting, ?string $visibility_controller ): ?array {
328 $class_names = isset( $setting['class'] ) && is_string( $setting['class'] ) ? explode( ' ', $setting['class'] ) : array();
329 if ( in_array( 'manage_stock_field', $class_names, true ) ) {
330 return array(
331 'controller' => 'woocommerce_manage_stock',
332 'value' => true,
333 );
334 }
335
336 if ( 'yes' === ( $setting['show_if_checked'] ?? null ) && $visibility_controller ) {
337 return array(
338 'controller' => $visibility_controller,
339 'value' => true,
340 );
341 }
342
343 return null;
344 }
345
346 /**
347 * Normalize field options.
348 *
349 * @param array $setting Legacy field definition.
350 * @return array
351 */
352 private static function get_options( array $setting ): array {
353 if ( ! isset( $setting['options'] ) || ! is_array( $setting['options'] ) ) {
354 return array();
355 }
356
357 $options = array();
358 foreach ( $setting['options'] as $value => $label ) {
359 if ( ! is_scalar( $label ) && null !== $label ) {
360 continue;
361 }
362
363 $options[] = array(
364 'label' => is_scalar( $label ) ? wp_strip_all_tags( html_entity_decode( (string) $label, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ) : '',
365 'value' => (string) $value,
366 );
367 }
368
369 return $options;
370 }
371
372 /**
373 * Normalize custom attributes for React controls.
374 *
375 * @param array $custom_attributes Raw custom attributes.
376 * @return array
377 */
378 private static function get_custom_attributes( array $custom_attributes ): array {
379 $attributes = array();
380
381 foreach ( $custom_attributes as $attribute => $value ) {
382 if ( ! is_scalar( $value ) ) {
383 continue;
384 }
385
386 $attribute_key = sanitize_key( (string) $attribute );
387 if ( '' === $attribute_key ) {
388 continue;
389 }
390
391 $attributes[ $attribute_key ] = $value;
392 }
393
394 return $attributes;
395 }
396
397 /**
398 * Normalize group header actions.
399 *
400 * @param array $setting Legacy title setting definition.
401 * @return array
402 */
403 private static function get_group_actions( array $setting ): array {
404 if ( empty( $setting['actions'] ) || ! is_array( $setting['actions'] ) ) {
405 return array();
406 }
407
408 $actions = array();
409
410 foreach ( $setting['actions'] as $index => $action ) {
411 if ( ! is_array( $action ) || empty( $action['label'] ) || ! is_scalar( $action['label'] ) ) {
412 continue;
413 }
414
415 $href = $action['href'] ?? $action['url'] ?? '';
416 if ( ! is_scalar( $href ) || '' === (string) $href ) {
417 continue;
418 }
419
420 $href = esc_url_raw( (string) $href );
421 if ( '' === $href ) {
422 continue;
423 }
424
425 $normalized_action = array(
426 'id' => isset( $action['id'] ) && is_scalar( $action['id'] ) ? sanitize_key( (string) $action['id'] ) : 'action_' . $index,
427 'label' => wp_strip_all_tags( html_entity_decode( (string) $action['label'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ),
428 'href' => $href,
429 );
430
431 if ( isset( $action['variant'] ) && is_scalar( $action['variant'] ) ) {
432 $normalized_action['variant'] = sanitize_key( (string) $action['variant'] );
433 }
434
435 if ( isset( $action['target'] ) && is_scalar( $action['target'] ) && in_array( (string) $action['target'], array( '_blank', '_self', '_parent', '_top' ), true ) ) {
436 $normalized_action['target'] = (string) $action['target'];
437 }
438
439 if ( isset( $action['rel'] ) && is_scalar( $action['rel'] ) ) {
440 $normalized_action['rel'] = sanitize_text_field( (string) $action['rel'] );
441 }
442
443 $actions[] = $normalized_action;
444 }
445
446 return $actions;
447 }
448
449 /**
450 * Get the default group.
451 *
452 * @param int $order Group order.
453 * @return array
454 */
455 private static function get_default_group( int $order ): array {
456 return array(
457 'id' => self::DEFAULT_GROUP_ID,
458 'title' => '',
459 'description' => '',
460 'actions' => array(),
461 'order' => $order,
462 'fields' => array(),
463 );
464 }
465 }
466