PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.7
Advanced Custom Fields (ACF®) v6.8.7
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.6.1 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10.2 6.3.11 6.3.12 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.6.1 6.4.0 6.4.0.1 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.8.0 trunk 1.0.0 1.0.2 1.0.3 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.3 2.1.4 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.6 3.0.7 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.8 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.4.0 4.4.1 4.4.10 4.4.11 4.4.12 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 5.10 5.10.1 5.10.2 5.11 5.11.1 5.11.2 5.11.3 5.11.4 5.12 5.12.1 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.6.10 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 5.6.7 5.6.8 5.6.9 5.7.0 5.7.1 5.7.10 5.7.12 5.7.13 5.7.2 5.7.3 5.7.4 5.7.5 5.7.6 5.7.7 5.7.8 5.7.9 5.8.0 5.8.1 5.8.10 5.8.11 5.8.12 5.8.13 5.8.14 5.8.2 5.8.3 5.8.4
advanced-custom-fields / includes / fields / class-acf-field-select.php
advanced-custom-fields / includes / fields Last commit date
class-acf-field-accordion.php 5 months ago class-acf-field-button-group.php 5 months ago class-acf-field-checkbox.php 2 months ago class-acf-field-color_picker.php 5 months ago class-acf-field-date_picker.php 5 months ago class-acf-field-date_time_picker.php 5 months ago class-acf-field-email.php 5 months ago class-acf-field-file.php 5 months ago class-acf-field-google-map.php 5 months ago class-acf-field-group.php 5 months ago class-acf-field-icon_picker.php 5 months ago class-acf-field-image.php 4 weeks ago class-acf-field-link.php 5 months ago class-acf-field-message.php 5 months ago class-acf-field-number.php 5 months ago class-acf-field-oembed.php 2 months ago class-acf-field-output.php 5 months ago class-acf-field-page_link.php 4 weeks ago class-acf-field-password.php 5 months ago class-acf-field-post_object.php 4 weeks ago class-acf-field-radio.php 2 months ago class-acf-field-range.php 5 months ago class-acf-field-relationship.php 4 weeks ago class-acf-field-select.php 2 months ago class-acf-field-separator.php 5 months ago class-acf-field-tab.php 5 months ago class-acf-field-taxonomy.php 2 months ago class-acf-field-text.php 5 months ago class-acf-field-textarea.php 5 months ago class-acf-field-time_picker.php 5 months ago class-acf-field-true_false.php 5 months ago class-acf-field-url.php 5 months ago class-acf-field-user.php 2 months ago class-acf-field-wysiwyg.php 5 months ago class-acf-field.php 5 months ago index.php 2 years ago
class-acf-field-select.php
844 lines
1 <?php
2 /**
3 * @package ACF
4 * @author WP Engine
5 *
6 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
7 * "ACF" is a trademark of WP Engine.
8 * Licensed under the GNU General Public License v2 or later.
9 * https://www.gnu.org/licenses/gpl-2.0.html
10 */
11
12 if ( ! class_exists( 'acf_field_select' ) ) :
13
14 class acf_field_select extends acf_field {
15
16 /**
17 * Sets up the field type data.
18 *
19 * @since 5.0.0
20 *
21 * @return void
22 */
23 public function initialize() {
24 $this->name = 'select';
25 $this->label = _x( 'Select', 'noun', 'acf' );
26 $this->category = 'choice';
27 $this->description = __( 'A dropdown list with a selection of choices that you specify.', 'acf' );
28 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-select.png';
29 $this->doc_url = acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/resources/select/', 'docs', 'field-type-selection' );
30 $this->defaults = array(
31 'multiple' => 0,
32 'allow_null' => 0,
33 'choices' => array(),
34 'default_value' => '',
35 'ui' => 0,
36 'ajax' => 0,
37 'placeholder' => '',
38 'return_format' => 'value',
39 'create_options' => 0,
40 'save_options' => 0,
41 );
42
43 add_action( 'wp_ajax_acf/fields/select/query', array( $this, 'ajax_query' ) );
44 add_action( 'wp_ajax_nopriv_acf/fields/select/query', array( $this, 'ajax_query' ) );
45 }
46
47 /**
48 * Enqueues admin scripts for the Select field.
49 *
50 * @since 5.3.2
51 *
52 * @return void
53 */
54 public function input_admin_enqueue_scripts() {
55 // Bail early if not enqueuing select2.
56 if ( ! acf_get_setting( 'enqueue_select2' ) ) {
57 return;
58 }
59
60 global $wp_scripts;
61
62 $min = defined( 'ACF_DEVELOPMENT_MODE' ) && ACF_DEVELOPMENT_MODE ? '' : '.min';
63 $major = acf_get_setting( 'select2_version' );
64
65 // attempt to find 3rd party Select2 version
66 // - avoid including v3 CSS when v4 JS is already enqueued.
67 if ( isset( $wp_scripts->registered['select2'] ) ) {
68 $major = (int) $wp_scripts->registered['select2']->ver;
69 }
70
71 if ( $major === 3 ) {
72 // Use v3 if necessary.
73 $version = '3.5.2';
74 $script = acf_get_url( "assets/inc/select2/3/select2{$min}.js" );
75 $style = acf_get_url( 'assets/inc/select2/3/select2.css' );
76 } else {
77 // Default to v4.
78 $version = '4.0.13';
79 $script = acf_get_url( "assets/inc/select2/4/select2.full{$min}.js" );
80 $style = acf_get_url( "assets/inc/select2/4/select2{$min}.css" );
81 }
82
83 wp_enqueue_script( 'select2', $script, array( 'jquery' ), $version );
84 wp_enqueue_style( 'select2', $style, '', $version );
85
86 acf_localize_data(
87 array(
88 'select2L10n' => array(
89 'matches_1' => _x( 'One result is available, press enter to select it.', 'Select2 JS matches_1', 'acf' ),
90 /* translators: %d - number of results available in select field */
91 'matches_n' => _x( '%d results are available, use up and down arrow keys to navigate.', 'Select2 JS matches_n', 'acf' ),
92 'matches_0' => _x( 'No matches found', 'Select2 JS matches_0', 'acf' ),
93 'input_too_short_1' => _x( 'Please enter 1 or more characters', 'Select2 JS input_too_short_1', 'acf' ),
94 /* translators: %d - number of characters to enter into select field input */
95 'input_too_short_n' => _x( 'Please enter %d or more characters', 'Select2 JS input_too_short_n', 'acf' ),
96 'input_too_long_1' => _x( 'Please delete 1 character', 'Select2 JS input_too_long_1', 'acf' ),
97 /* translators: %d - number of characters that should be removed from select field */
98 'input_too_long_n' => _x( 'Please delete %d characters', 'Select2 JS input_too_long_n', 'acf' ),
99 'selection_too_long_1' => _x( 'You can only select 1 item', 'Select2 JS selection_too_long_1', 'acf' ),
100 /* translators: %d - maximum number of items that can be selected in the select field */
101 'selection_too_long_n' => _x( 'You can only select %d items', 'Select2 JS selection_too_long_n', 'acf' ),
102 'load_more' => _x( 'Loading more results&hellip;', 'Select2 JS load_more', 'acf' ),
103 'searching' => _x( 'Searching&hellip;', 'Select2 JS searching', 'acf' ),
104 'load_fail' => _x( 'Loading failed', 'Select2 JS load_fail', 'acf' ),
105 ),
106 )
107 );
108 }
109
110 /**
111 * AJAX handler for getting Select field choices.
112 *
113 * @since 5.0.0
114 *
115 * @return void
116 */
117 public function ajax_query() {
118 $nonce = acf_request_arg( 'nonce', '' );
119 $key = acf_request_arg( 'field_key', '' );
120
121 $is_field_key = acf_is_field_key( $key );
122
123 // Back-compat for field settings.
124 if ( ! $is_field_key ) {
125 if ( ! acf_current_user_can_admin() ) {
126 die();
127 }
128
129 $nonce = '';
130 $key = '';
131 }
132
133 if ( ! acf_verify_ajax( $nonce, $key, $is_field_key, 'select' ) ) {
134 die();
135 }
136
137 acf_send_ajax_results( $this->get_ajax_query( $_POST ) );
138 }
139
140 /**
141 * This function will return an array of data formatted for use in a select2 AJAX response
142 *
143 * @since 5.0.9
144 *
145 * @param array $options An array of options.
146 * @return array A select2 compatible array of options.
147 */
148 public function get_ajax_query( $options = array() ) {
149 $options = acf_parse_args(
150 $options,
151 array(
152 'post_id' => 0,
153 's' => '',
154 'field_key' => '',
155 'paged' => 1,
156 )
157 );
158
159 $shortcut = apply_filters( 'acf/fields/select/query', array(), $options );
160 $shortcut = apply_filters( 'acf/fields/select/query/key=' . $options['field_key'], $shortcut, $options );
161 if ( ! empty( $shortcut ) ) {
162 return $shortcut;
163 }
164
165 // load field.
166 $field = acf_get_field( $options['field_key'] );
167 if ( ! $field ) {
168 return false;
169 }
170
171 // get choices.
172 $choices = acf_get_array( $field['choices'] );
173 if ( empty( $field['choices'] ) ) {
174 return false;
175 }
176
177 $results = array();
178 $s = null;
179
180 // search.
181 if ( $options['s'] !== '' ) {
182
183 // strip slashes (search may be integer)
184 $s = strval( $options['s'] );
185 $s = wp_unslash( $s );
186 }
187
188 foreach ( $field['choices'] as $k => $v ) {
189
190 // ensure $v is a string.
191 $v = strval( $v );
192
193 // if searching, but doesn't exist.
194 if ( is_string( $s ) && stripos( $v, $s ) === false ) {
195 continue;
196 }
197
198 // append results.
199 $results[] = array(
200 'id' => $k,
201 'text' => $v,
202 );
203 }
204
205 $response = array(
206 'results' => $results,
207 );
208
209 return $response;
210 }
211
212
213 /**
214 * Creates the HTML interface for the field.
215 *
216 * @since 3.6
217 *
218 * @param array $field An array holding all the field's data.
219 * @return void
220 */
221 public function render_field( $field ) {
222 $value = acf_get_array( $field['value'] );
223 $choices = acf_get_array( $field['choices'] );
224
225 if ( empty( $field['placeholder'] ) ) {
226 $field['placeholder'] = _x( 'Select', 'verb', 'acf' );
227 }
228
229 // Add empty value (allows '' to be selected).
230 if ( empty( $value ) ) {
231 $value = array( '' );
232 }
233
234 // prepend empty choice
235 // - only for single selects
236 // - have tried array_merge but this causes keys to re-index if is numeric (post ID's)
237 if ( $field['allow_null'] && ! $field['multiple'] ) {
238 $choices = array( '' => "- {$field['placeholder']} -" ) + $choices;
239 }
240
241 // clean up choices if using ajax
242 if ( $field['ui'] && $field['ajax'] ) {
243 $minimal = array();
244 foreach ( $value as $key ) {
245 if ( isset( $choices[ $key ] ) ) {
246 $minimal[ $key ] = $choices[ $key ];
247 }
248 }
249 $choices = $minimal;
250 }
251
252 $select = array(
253 'id' => $field['id'],
254 'class' => $field['class'],
255 'name' => $field['name'],
256 'data-ui' => $field['ui'],
257 'data-ajax' => $field['ajax'],
258 'data-multiple' => $field['multiple'],
259 'data-placeholder' => $field['placeholder'],
260 'data-allow_null' => $field['allow_null'],
261 );
262
263 if ( ! empty( $field['aria-label'] ) ) {
264 $select['aria-label'] = $field['aria-label'];
265 }
266
267 if ( $field['multiple'] ) {
268 $select['multiple'] = 'multiple';
269 $select['size'] = 5;
270 $select['name'] .= '[]';
271
272 // Reduce size to single line if UI.
273 if ( $field['ui'] ) {
274 $select['size'] = 1;
275 }
276 }
277
278 if ( ! empty( $field['create_options'] ) && $field['ui'] ) {
279 $select['data-create_options'] = true;
280 }
281
282 // special atts
283 if ( ! empty( $field['readonly'] ) ) {
284 $select['readonly'] = 'readonly';
285 }
286 if ( ! empty( $field['disabled'] ) ) {
287 $select['disabled'] = 'disabled';
288 }
289 if ( ! empty( $field['ajax_action'] ) ) {
290 $select['data-ajax_action'] = $field['ajax_action'];
291 }
292 if ( ! empty( $field['nonce'] ) ) {
293 $select['data-nonce'] = $field['nonce'];
294 }
295 if ( $field['ajax'] && empty( $field['nonce'] ) && acf_is_field_key( $field['key'] ) ) {
296 $select['data-nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
297 }
298 if ( ! empty( $field['hide_search'] ) ) {
299 $select['data-minimum-results-for-search'] = '-1';
300 }
301
302 // Hidden input is needed to allow validation to see <select> element with no selected value.
303 if ( $field['multiple'] || $field['ui'] ) {
304 acf_hidden_input(
305 array(
306 'id' => $field['id'] . '-input',
307 'name' => $field['name'],
308 )
309 );
310 }
311
312 $select['value'] = $value;
313 $select['choices'] = $choices;
314
315 if ( ! empty( $field['create_options'] ) && $field['ui'] && is_array( $field['value'] ) ) {
316 foreach ( $field['value'] as $value ) {
317 // Already exists in choices.
318 if ( isset( $field['choices'][ $value ] ) ) {
319 continue;
320 }
321
322 $option = esc_attr( $value );
323
324 $select['choices'][ $option ] = $option;
325 }
326 }
327
328 acf_select_input( $select );
329 }
330
331 /**
332 * Renders the field settings used in the "General" tab.
333 *
334 * @since 3.6
335 *
336 * @param array $field An array holding all the field's data.
337 * @return void
338 */
339 public function render_field_settings( $field ) {
340
341 // encode choices (convert from array)
342 $field['choices'] = acf_encode_choices( $field['choices'] );
343 $field['default_value'] = acf_encode_choices( $field['default_value'], false );
344
345 // choices
346 acf_render_field_setting(
347 $field,
348 array(
349 'label' => __( 'Choices', 'acf' ),
350 'instructions' => __( 'Enter each choice on a new line.', 'acf' ) . '<br />' . __( 'For more control, you may specify both a value and label like this:', 'acf' ) . '<br /><span class="acf-field-setting-example">' . __( 'red : Red', 'acf' ) . '</span>',
351 'name' => 'choices',
352 'type' => 'textarea',
353 )
354 );
355
356 // default_value
357 acf_render_field_setting(
358 $field,
359 array(
360 'label' => __( 'Default Value', 'acf' ),
361 'instructions' => __( 'Enter each default value on a new line', 'acf' ),
362 'name' => 'default_value',
363 'type' => 'textarea',
364 )
365 );
366
367 // return_format
368 acf_render_field_setting(
369 $field,
370 array(
371 'label' => __( 'Return Format', 'acf' ),
372 'instructions' => __( 'Specify the value returned', 'acf' ),
373 'type' => 'radio',
374 'name' => 'return_format',
375 'layout' => 'horizontal',
376 'choices' => array(
377 'value' => __( 'Value', 'acf' ),
378 'label' => __( 'Label', 'acf' ),
379 'array' => __( 'Both (Array)', 'acf' ),
380 ),
381 )
382 );
383
384 acf_render_field_setting(
385 $field,
386 array(
387 'label' => __( 'Select Multiple', 'acf' ),
388 'instructions' => 'Allow content editors to select multiple values',
389 'name' => 'multiple',
390 'type' => 'true_false',
391 'ui' => 1,
392 )
393 );
394 }
395
396 /**
397 * Renders the field settings used in the "Validation" tab.
398 *
399 * @since 6.0
400 *
401 * @param array $field The field settings array.
402 * @return void
403 */
404 public function render_field_validation_settings( $field ) {
405 acf_render_field_setting(
406 $field,
407 array(
408 'label' => __( 'Allow Null', 'acf' ),
409 'instructions' => '',
410 'name' => 'allow_null',
411 'type' => 'true_false',
412 'ui' => 1,
413 )
414 );
415 }
416
417 /**
418 * Renders the field settings used in the "Presentation" tab.
419 *
420 * @since 6.0
421 *
422 * @param array $field The field settings array.
423 * @return void
424 */
425 public function render_field_presentation_settings( $field ) {
426 acf_render_field_setting(
427 $field,
428 array(
429 'label' => __( 'Stylized UI', 'acf' ),
430 'instructions' => __( 'Use a stylized checkbox using select2', 'acf' ),
431 'name' => 'ui',
432 'type' => 'true_false',
433 'ui' => 1,
434 )
435 );
436
437 acf_render_field_setting(
438 $field,
439 array(
440 'label' => __( 'Use AJAX to lazy load choices?', 'acf' ),
441 'instructions' => '',
442 'name' => 'ajax',
443 'type' => 'true_false',
444 'ui' => 1,
445 'conditions' => array(
446 'field' => 'ui',
447 'operator' => '==',
448 'value' => 1,
449 ),
450 )
451 );
452
453 acf_render_field_setting(
454 $field,
455 array(
456 'label' => __( 'Create Options', 'acf' ),
457 'instructions' => __( 'Allow content editors to create new options by typing in the Select input. Multiple options can be created from a comma separated string.', 'acf' ),
458 'name' => 'create_options',
459 'type' => 'true_false',
460 'ui' => 1,
461 'conditions' => array(
462 array(
463 'field' => 'ui',
464 'operator' => '==',
465 'value' => 1,
466 ),
467 array(
468 'field' => 'multiple',
469 'operator' => '==',
470 'value' => 1,
471 ),
472 ),
473 )
474 );
475
476 acf_render_field_setting(
477 $field,
478 array(
479 'label' => __( 'Save Options', 'acf' ),
480 'instructions' => __( 'Save created options back to the "Choices" setting in the field definition.', 'acf' ),
481 'name' => 'save_options',
482 'type' => 'true_false',
483 'ui' => 1,
484 'conditions' => array(
485 array(
486 'field' => 'ui',
487 'operator' => '==',
488 'value' => 1,
489 ),
490 array(
491 'field' => 'multiple',
492 'operator' => '==',
493 'value' => 1,
494 ),
495 array(
496 'field' => 'create_options',
497 'operator' => '==',
498 'value' => 1,
499 ),
500 ),
501 )
502 );
503 }
504
505 /**
506 * Filters the $value after it is loaded from the db.
507 *
508 * @since 3.6
509 *
510 * @param mixed $value The value found in the database.
511 * @param integer|string $post_id The post_id from which the value was loaded.
512 * @param array $field The field array holding all the field options.
513 * @return mixed
514 */
515 public function load_value( $value, $post_id, $field ) {
516 // Return an array when field is set for multiple.
517 if ( $field['multiple'] ) {
518 if ( acf_is_empty( $value ) ) {
519 return array();
520 }
521 return acf_array( $value );
522 }
523
524 // Otherwise, return a single value.
525 return acf_unarray( $value );
526 }
527
528 /**
529 * Filters the $field before it is saved to the database.
530 *
531 * @since 3.6
532 *
533 * @param array $field The field array holding all the field options.
534 * @return array
535 */
536 public function update_field( $field ) {
537 // Decode choices (convert to array).
538 $field['choices'] = acf_decode_choices( $field['choices'] );
539 $field['default_value'] = acf_decode_choices( $field['default_value'], true );
540
541 // Convert back to string for single selects.
542 if ( ! $field['multiple'] ) {
543 $field['default_value'] = acf_unarray( $field['default_value'] );
544 }
545
546 return $field;
547 }
548
549 /**
550 * Filters the $value before it is updated in the db.
551 *
552 * @since 3.6
553 *
554 * @param mixed $value The value which will be saved in the database.
555 * @param integer|string $post_id The post_id of which the value will be saved.
556 * @param array $field The field array holding all the field options.
557 *
558 * @return mixed
559 */
560 public function update_value( $value, $post_id, $field ) {
561 // Bail early if no value.
562 if ( empty( $value ) ) {
563 return $value;
564 }
565
566 // Format array of values.
567 // - Parse each value as string for SQL LIKE queries.
568 if ( is_array( $value ) ) {
569 $value = array_map( 'strval', $value );
570 }
571
572 // Save custom options back to the field definition if configured.
573 if ( ! empty( $field['save_options'] ) && is_array( $value ) ) {
574 // Get the raw field, using the ID if present or the key otherwise (i.e. when using JSON).
575 $selector = $field['ID'] ? $field['ID'] : $field['key'];
576 $field = acf_get_field( $selector );
577
578 // Bail if we don't have a valid field or field ID (JSON only).
579 if ( empty( $field['ID'] ) ) {
580 return $value;
581 }
582
583 $this->append_user_choices_to_field( $value, $post_id, $field );
584 }
585
586 return $value;
587 }
588
589 /**
590 * Appends submitter-contributed values to a persisted field's `choices`
591 * array, subject to the `acf/fields/max_appended_choices` cap. Used by
592 * checkbox `save_custom` and select `save_options`. Callers must have
593 * already verified that the setting is enabled and that the field has
594 * an ID (i.e. it's not local/JSON-only).
595 *
596 * @internal Helper for ACF's choice field types; not intended for
597 * external callers. Signature may change without notice.
598 *
599 * @since 6.8.5
600 *
601 * @param array $value Submitted values.
602 * @param integer|string $post_id The post_id of which the value will be saved.
603 * @param array $field The persisted field array. Must have an ID.
604 * @return void
605 */
606 public function append_user_choices_to_field( $value, $post_id, $field ) {
607 /**
608 * Filters the maximum number of choices that may be stored on a field
609 * via the checkbox `save_custom`, radio `save_other_choice`, and select
610 * `save_options` settings. Once reached, additional submitter-contributed
611 * values are not appended to the field definition. The submitter's own
612 * field value still saves to their post.
613 *
614 * @since 6.8.5
615 *
616 * @param int $max Maximum number of choices. Default 1000.
617 * @param array $field The field array holding all the field options.
618 * @param integer|string $post_id The post_id of which the value will be saved.
619 */
620 $max = (int) apply_filters( 'acf/fields/max_appended_choices', 1000, $field, $post_id );
621
622 $appended = false;
623
624 foreach ( $value as $v ) {
625 // Skip if the raw submitted value matches an existing key,
626 // preserving back-compat for fields whose choices contain
627 // un-normalized keys (e.g. developer-registered with HTML).
628 if ( isset( $field['choices'][ $v ] ) ) {
629 continue;
630 }
631
632 // Unslash (fixes serialize single quote issue) and sanitize.
633 $v = wp_unslash( $v );
634 $v = sanitize_text_field( $v );
635
636 // Skip if the normalized value is empty or already exists.
637 if ( $v === '' || isset( $field['choices'][ $v ] ) ) {
638 continue;
639 }
640
641 // Stop appending once the cap is reached.
642 if ( count( $field['choices'] ) >= $max ) {
643 break;
644 }
645
646 // Append to the field choices.
647 $field['choices'][ $v ] = $v;
648 $appended = true;
649 }
650
651 // Save only if we actually appended a new choice.
652 if ( $appended ) {
653 acf_update_field( $field );
654 }
655 }
656
657 /**
658 * Translates the field settings.
659 *
660 * @since 5.3.2
661 *
662 * @param array $field The main field array.
663 * @return array
664 */
665 public function translate_field( $field ) {
666 $field['choices'] = acf_translate( $field['choices'] );
667 return $field;
668 }
669
670
671 /**
672 * Filters the $value after it is loaded from the db, and before it is returned to the template.
673 *
674 * @since 3.6
675 *
676 * @param mixed $value The value which was loaded from the database.
677 * @param integer|string $post_id The post_id from which the value was loaded.
678 * @param array $field The field array holding all the field options.
679 *
680 * @return mixed
681 */
682 public function format_value( $value, $post_id, $field ) {
683 if ( is_array( $value ) ) {
684 foreach ( $value as $i => $val ) {
685 $value[ $i ] = $this->format_value_single( $val, $post_id, $field );
686 }
687 } else {
688 $value = $this->format_value_single( $value, $post_id, $field );
689 }
690
691 return $value;
692 }
693
694 /**
695 * Formats the value when the select is not a multi-select.
696 *
697 * @since 3.6
698 *
699 * @param mixed $value The value to format.
700 * @param integer|string $post_id The post_id from which the value was loaded.
701 * @param array $field The field array holding all the field options.
702 * @return mixed
703 */
704 public function format_value_single( $value, $post_id, $field ) {
705 // Bail early if is empty.
706 if ( acf_is_empty( $value ) ) {
707 return $value;
708 }
709
710 $label = acf_maybe_get( $field['choices'], $value, $value );
711
712 if ( $field['return_format'] === 'label' ) {
713 $value = $label;
714 } elseif ( $field['return_format'] === 'array' ) {
715 $value = array(
716 'value' => $value,
717 'label' => $label,
718 );
719 }
720
721 return $value;
722 }
723
724 /**
725 * Validates select fields updated via the REST API.
726 *
727 * @param boolean $valid The current validity booleean
728 * @param integer $value The value of the field
729 * @param array $field The field array
730 * @return boolean|WP_Error
731 */
732 public function validate_rest_value( $valid, $value, $field ) {
733 // rest_validate_request_arg() handles the other types, we just worry about strings.
734 if ( is_null( $value ) || is_array( $value ) ) {
735 return $valid;
736 }
737
738 $option_keys = array_diff(
739 array_keys( $field['choices'] ),
740 array_values( $field['choices'] )
741 );
742
743 $allowed = empty( $option_keys ) ? $field['choices'] : $option_keys;
744
745 if ( ! in_array( $value, $allowed ) ) {
746 $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
747 $data = array(
748 'param' => $param,
749 'value' => $value,
750 );
751 $error = sprintf(
752 __( '%1$s is not one of %2$s', 'acf' ),
753 $param,
754 implode( ', ', $allowed )
755 );
756
757 return new WP_Error( 'rest_invalid_param', $error, $data );
758 }
759
760 return $valid;
761 }
762
763 /**
764 * Formats the choices available for the REST API.
765 *
766 * @since 6.2
767 *
768 * @param array $choices The choices for the field.
769 * @return array
770 */
771 public function format_rest_choices( $choices ) {
772 $keys = array_keys( $choices );
773 $values = array_values( $choices );
774 $int_choices = array();
775
776 if ( array_diff( $keys, $values ) ) {
777 // User has specified custom keys.
778 $choices = $keys;
779 } else {
780 // Default keys, same as value.
781 $choices = $values;
782 }
783
784 // Assume everything is a string by default.
785 $choices = array_map( 'strval', $choices );
786
787 // Also allow integers if is_numeric().
788 foreach ( $choices as $choice ) {
789 if ( is_numeric( $choice ) ) {
790 $int_choices[] = (int) $choice;
791 }
792 }
793
794 return array_merge( $choices, $int_choices );
795 }
796
797 /**
798 * Return the schema array for the REST API.
799 *
800 * @param array $field The main field array.
801 * @return array
802 */
803 public function get_rest_schema( array $field ) {
804 $schema = array(
805 'type' => array( 'string', 'array', 'int', 'null' ),
806 'required' => ! empty( $field['required'] ),
807 'items' => array(
808 'type' => array( 'string', 'int' ),
809 'enum' => $this->format_rest_choices( $field['choices'] ),
810 ),
811 );
812
813 if ( empty( $field['allow_null'] ) ) {
814 $schema['minItems'] = 1;
815 }
816
817 if ( empty( $field['multiple'] ) ) {
818 $schema['maxItems'] = 1;
819 }
820
821 if ( isset( $field['default_value'] ) && '' !== $field['default_value'] ) {
822 $schema['default'] = $field['default_value'];
823 }
824
825 return $schema;
826 }
827
828 /**
829 * Returns an array of JSON-LD Property output types that are supported by this field type.
830 *
831 * @since 6.8
832 *
833 * @return string[]
834 */
835 public function get_jsonld_output_types(): array {
836 return array( 'Text' );
837 }
838 }
839
840
841 // initialize
842 acf_register_field_type( 'acf_field_select' );
843 endif; // class_exists check
844