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