PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.0-beta2
Secure Custom Fields v6.4.0-beta2
6.9.5 6.9.4 6.9.3 6.9.2 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
class-acf-field-accordion.php 1 year ago class-acf-field-button-group.php 1 year ago class-acf-field-checkbox.php 1 year ago class-acf-field-color_picker.php 1 year ago class-acf-field-date_picker.php 1 year ago class-acf-field-date_time_picker.php 1 year ago class-acf-field-email.php 1 year ago class-acf-field-file.php 1 year ago class-acf-field-google-map.php 1 year ago class-acf-field-group.php 1 year ago class-acf-field-icon_picker.php 1 year ago class-acf-field-image.php 1 year ago class-acf-field-link.php 1 year ago class-acf-field-message.php 1 year ago class-acf-field-number.php 1 year ago class-acf-field-oembed.php 1 year ago class-acf-field-output.php 1 year ago class-acf-field-page_link.php 1 year ago class-acf-field-password.php 1 year ago class-acf-field-post_object.php 1 year ago class-acf-field-radio.php 1 year ago class-acf-field-range.php 1 year ago class-acf-field-relationship.php 1 year ago class-acf-field-select.php 1 year ago class-acf-field-separator.php 1 year ago class-acf-field-tab.php 1 year ago class-acf-field-taxonomy.php 1 year ago class-acf-field-text.php 1 year ago class-acf-field-textarea.php 1 year ago class-acf-field-time_picker.php 1 year ago class-acf-field-true_false.php 1 year ago class-acf-field-url.php 1 year ago class-acf-field-user.php 1 year ago class-acf-field-wysiwyg.php 1 year ago class-acf-field.php 1 year ago index.php 1 year ago
class-acf-field-select.php
710 lines
1 <?php
2
3 if ( ! class_exists( 'acf_field_select' ) ) :
4
5 class acf_field_select extends acf_field {
6
7
8 /**
9 * This function will setup the field type data
10 *
11 * @type function
12 * @date 5/03/2014
13 * @since 5.0.0
14 *
15 * @param n/a
16 * @return n/a
17 */
18 function initialize() {
19
20 // vars
21 $this->name = 'select';
22 $this->label = _x( 'Select', 'noun', 'acf' );
23 $this->category = 'choice';
24 $this->description = __( 'A dropdown list with a selection of choices that you specify.', 'acf' );
25 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-select.png';
26 $this->doc_url = 'https://www.advancedcustomfields.com/resources/select/';
27 $this->defaults = array(
28 'multiple' => 0,
29 'allow_null' => 0,
30 'choices' => array(),
31 'default_value' => '',
32 'ui' => 0,
33 'ajax' => 0,
34 'placeholder' => '',
35 'return_format' => 'value',
36 );
37
38 // ajax
39 add_action( 'wp_ajax_acf/fields/select/query', array( $this, 'ajax_query' ) );
40 add_action( 'wp_ajax_nopriv_acf/fields/select/query', array( $this, 'ajax_query' ) );
41 }
42
43
44 /**
45 * Enqueues admin scripts for the Select field.
46 *
47 * @since 5.3.2
48 *
49 * @return void
50 */
51 public function input_admin_enqueue_scripts() {
52 // Bail early if not enqueuing select2.
53 if ( ! acf_get_setting( 'enqueue_select2' ) ) {
54 return;
55 }
56
57 global $wp_scripts;
58
59 $min = defined( 'SCF_DEVELOPMENT_MODE' ) && SCF_DEVELOPMENT_MODE ? '' : '.min';
60 $major = acf_get_setting( 'select2_version' );
61
62 // attempt to find 3rd party Select2 version
63 // - avoid including v3 CSS when v4 JS is already enqueued.
64 if ( isset( $wp_scripts->registered['select2'] ) ) {
65 $major = (int) $wp_scripts->registered['select2']->ver;
66 }
67
68 if ( $major === 3 ) {
69 // Use v3 if necessary.
70 $version = '3.5.2';
71 $script = acf_get_url( "assets/inc/select2/3/select2{$min}.js" );
72 $style = acf_get_url( 'assets/inc/select2/3/select2.css' );
73 } else {
74 // Default to v4.
75 $version = '4.0.13';
76 $script = acf_get_url( "assets/inc/select2/4/select2.full{$min}.js" );
77 $style = acf_get_url( "assets/inc/select2/4/select2{$min}.css" );
78 }
79
80 wp_enqueue_script( 'select2', $script, array( 'jquery' ), $version );
81 wp_enqueue_style( 'select2', $style, '', $version );
82
83 acf_localize_data(
84 array(
85 'select2L10n' => array(
86 'matches_1' => _x( 'One result is available, press enter to select it.', 'Select2 JS matches_1', 'acf' ),
87 /* translators: %d - number of results available in select field */
88 'matches_n' => _x( '%d results are available, use up and down arrow keys to navigate.', 'Select2 JS matches_n', 'acf' ),
89 'matches_0' => _x( 'No matches found', 'Select2 JS matches_0', 'acf' ),
90 'input_too_short_1' => _x( 'Please enter 1 or more characters', 'Select2 JS input_too_short_1', 'acf' ),
91 /* translators: %d - number of characters to enter into select field input */
92 'input_too_short_n' => _x( 'Please enter %d or more characters', 'Select2 JS input_too_short_n', 'acf' ),
93 'input_too_long_1' => _x( 'Please delete 1 character', 'Select2 JS input_too_long_1', 'acf' ),
94 /* translators: %d - number of characters that should be removed from select field */
95 'input_too_long_n' => _x( 'Please delete %d characters', 'Select2 JS input_too_long_n', 'acf' ),
96 'selection_too_long_1' => _x( 'You can only select 1 item', 'Select2 JS selection_too_long_1', 'acf' ),
97 /* translators: %d - maximum number of items that can be selected in the select field */
98 'selection_too_long_n' => _x( 'You can only select %d items', 'Select2 JS selection_too_long_n', 'acf' ),
99 'load_more' => _x( 'Loading more results&hellip;', 'Select2 JS load_more', 'acf' ),
100 'searching' => _x( 'Searching&hellip;', 'Select2 JS searching', 'acf' ),
101 'load_fail' => _x( 'Loading failed', 'Select2 JS load_fail', 'acf' ),
102 ),
103 )
104 );
105 }
106
107 /**
108 * AJAX handler for getting Select field choices.
109 *
110 * @since 5.0.0
111 *
112 * @return void
113 */
114 public function ajax_query() {
115 $nonce = acf_request_arg( 'nonce', '' );
116 $key = acf_request_arg( 'field_key', '' );
117
118 // Back-compat for field settings.
119 if ( ! acf_is_field_key( $key ) ) {
120 $nonce = '';
121 $key = '';
122 }
123
124 if ( ! acf_verify_ajax( $nonce, $key ) ) {
125 die();
126 }
127
128 acf_send_ajax_results( $this->get_ajax_query( $_POST ) );
129 }
130
131 /**
132 * This function will return an array of data formatted for use in a select2 AJAX response
133 *
134 * @since 5.0.9
135 *
136 * @param array $options An array of options.
137 * @return array A select2 compatible array of options.
138 */
139 public function get_ajax_query( $options = array() ) {
140 $options = acf_parse_args(
141 $options,
142 array(
143 'post_id' => 0,
144 's' => '',
145 'field_key' => '',
146 'paged' => 1,
147 )
148 );
149
150 $shortcut = apply_filters( 'acf/fields/select/query', array(), $options );
151 $shortcut = apply_filters( 'acf/fields/select/query/key=' . $options['field_key'], $shortcut, $options );
152 if ( ! empty( $shortcut ) ) {
153 return $shortcut;
154 }
155
156 // load field.
157 $field = acf_get_field( $options['field_key'] );
158 if ( ! $field ) {
159 return false;
160 }
161
162 // get choices.
163 $choices = acf_get_array( $field['choices'] );
164 if ( empty( $field['choices'] ) ) {
165 return false;
166 }
167
168 $results = array();
169 $s = null;
170
171 // search.
172 if ( $options['s'] !== '' ) {
173
174 // strip slashes (search may be integer)
175 $s = strval( $options['s'] );
176 $s = wp_unslash( $s );
177 }
178
179 foreach ( $field['choices'] as $k => $v ) {
180
181 // ensure $v is a string.
182 $v = strval( $v );
183
184 // if searching, but doesn't exist.
185 if ( is_string( $s ) && stripos( $v, $s ) === false ) {
186 continue;
187 }
188
189 // append results.
190 $results[] = array(
191 'id' => $k,
192 'text' => $v,
193 );
194 }
195
196 $response = array(
197 'results' => $results,
198 );
199
200 return $response;
201 }
202
203
204 /**
205 * Create the HTML interface for your field
206 *
207 * @param $field - an array holding all the field's data
208 *
209 * @type action
210 * @since 3.6
211 * @date 23/01/13
212 */
213 function render_field( $field ) {
214
215 // convert
216 $value = acf_get_array( $field['value'] );
217 $choices = acf_get_array( $field['choices'] );
218
219 // placeholder
220 if ( empty( $field['placeholder'] ) ) {
221 $field['placeholder'] = _x( 'Select', 'verb', 'acf' );
222 }
223
224 // add empty value (allows '' to be selected)
225 if ( empty( $value ) ) {
226 $value = array( '' );
227 }
228
229 // prepend empty choice
230 // - only for single selects
231 // - have tried array_merge but this causes keys to re-index if is numeric (post ID's)
232 if ( $field['allow_null'] && ! $field['multiple'] ) {
233 $choices = array( '' => "- {$field['placeholder']} -" ) + $choices;
234 }
235
236 // clean up choices if using ajax
237 if ( $field['ui'] && $field['ajax'] ) {
238 $minimal = array();
239 foreach ( $value as $key ) {
240 if ( isset( $choices[ $key ] ) ) {
241 $minimal[ $key ] = $choices[ $key ];
242 }
243 }
244 $choices = $minimal;
245 }
246
247 // vars
248 $select = array(
249 'id' => $field['id'],
250 'class' => $field['class'],
251 'name' => $field['name'],
252 'data-ui' => $field['ui'],
253 'data-ajax' => $field['ajax'],
254 'data-multiple' => $field['multiple'],
255 'data-placeholder' => $field['placeholder'],
256 'data-allow_null' => $field['allow_null'],
257 );
258
259 if ( ! empty( $field['aria-label'] ) ) {
260 $select['aria-label'] = $field['aria-label'];
261 }
262
263 // multiple
264 if ( $field['multiple'] ) {
265 $select['multiple'] = 'multiple';
266 $select['size'] = 5;
267 $select['name'] .= '[]';
268
269 // Reduce size to single line if UI.
270 if ( $field['ui'] ) {
271 $select['size'] = 1;
272 }
273 }
274
275 // special atts
276 if ( ! empty( $field['readonly'] ) ) {
277 $select['readonly'] = 'readonly';
278 }
279 if ( ! empty( $field['disabled'] ) ) {
280 $select['disabled'] = 'disabled';
281 }
282 if ( ! empty( $field['ajax_action'] ) ) {
283 $select['data-ajax_action'] = $field['ajax_action'];
284 }
285 if ( ! empty( $field['nonce'] ) ) {
286 $select['data-nonce'] = $field['nonce'];
287 }
288 if ( $field['ajax'] && empty( $field['nonce'] ) && acf_is_field_key( $field['key'] ) ) {
289 $select['data-nonce'] = wp_create_nonce( $field['key'] );
290 }
291 if ( ! empty( $field['hide_search'] ) ) {
292 $select['data-minimum-results-for-search'] = '-1';
293 }
294
295 // hidden input is needed to allow validation to see <select> element with no selected value
296 if ( $field['multiple'] || $field['ui'] ) {
297 acf_hidden_input(
298 array(
299 'id' => $field['id'] . '-input',
300 'name' => $field['name'],
301 )
302 );
303 }
304
305 // append
306 $select['value'] = $value;
307 $select['choices'] = $choices;
308
309 // render
310 acf_select_input( $select );
311 }
312
313
314 /**
315 * Create extra options for your field. This is rendered when editing a field.
316 * The value of $field['name'] can be used (like bellow) to save extra data to the $field
317 *
318 * @type action
319 * @since 3.6
320 * @date 23/01/13
321 *
322 * @param $field - an array holding all the field's data
323 */
324 function render_field_settings( $field ) {
325
326 // encode choices (convert from array)
327 $field['choices'] = acf_encode_choices( $field['choices'] );
328 $field['default_value'] = acf_encode_choices( $field['default_value'], false );
329
330 // choices
331 acf_render_field_setting(
332 $field,
333 array(
334 'label' => __( 'Choices', 'acf' ),
335 '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>',
336 'name' => 'choices',
337 'type' => 'textarea',
338 )
339 );
340
341 // default_value
342 acf_render_field_setting(
343 $field,
344 array(
345 'label' => __( 'Default Value', 'acf' ),
346 'instructions' => __( 'Enter each default value on a new line', 'acf' ),
347 'name' => 'default_value',
348 'type' => 'textarea',
349 )
350 );
351
352 // return_format
353 acf_render_field_setting(
354 $field,
355 array(
356 'label' => __( 'Return Format', 'acf' ),
357 'instructions' => __( 'Specify the value returned', 'acf' ),
358 'type' => 'radio',
359 'name' => 'return_format',
360 'layout' => 'horizontal',
361 'choices' => array(
362 'value' => __( 'Value', 'acf' ),
363 'label' => __( 'Label', 'acf' ),
364 'array' => __( 'Both (Array)', 'acf' ),
365 ),
366 )
367 );
368
369 acf_render_field_setting(
370 $field,
371 array(
372 'label' => __( 'Select Multiple', 'acf' ),
373 'instructions' => 'Allow content editors to select multiple values',
374 'name' => 'multiple',
375 'type' => 'true_false',
376 'ui' => 1,
377 )
378 );
379 }
380
381 /**
382 * Renders the field settings used in the "Validation" tab.
383 *
384 * @since 6.0
385 *
386 * @param array $field The field settings array.
387 * @return void
388 */
389 function render_field_validation_settings( $field ) {
390 acf_render_field_setting(
391 $field,
392 array(
393 'label' => __( 'Allow Null', 'acf' ),
394 'instructions' => '',
395 'name' => 'allow_null',
396 'type' => 'true_false',
397 'ui' => 1,
398 )
399 );
400 }
401
402 /**
403 * Renders the field settings used in the "Presentation" tab.
404 *
405 * @since 6.0
406 *
407 * @param array $field The field settings array.
408 * @return void
409 */
410 function render_field_presentation_settings( $field ) {
411 acf_render_field_setting(
412 $field,
413 array(
414 'label' => __( 'Stylized UI', 'acf' ),
415 'instructions' => __( 'Use a stylized checkbox using select2', 'acf' ),
416 'name' => 'ui',
417 'type' => 'true_false',
418 'ui' => 1,
419 )
420 );
421
422 acf_render_field_setting(
423 $field,
424 array(
425 'label' => __( 'Use AJAX to lazy load choices?', 'acf' ),
426 'instructions' => '',
427 'name' => 'ajax',
428 'type' => 'true_false',
429 'ui' => 1,
430 'conditions' => array(
431 'field' => 'ui',
432 'operator' => '==',
433 'value' => 1,
434 ),
435 )
436 );
437 }
438
439 /**
440 * This filter is applied to the $value after it is loaded from the db
441 *
442 * @type filter
443 * @since 3.6
444 * @date 23/01/13
445 *
446 * @param $value (mixed) the value found in the database
447 * @param $post_id (mixed) the post_id from which the value was loaded
448 * @param $field (array) the field array holding all the field options
449 * @return $value
450 */
451 function load_value( $value, $post_id, $field ) {
452
453 // Return an array when field is set for multiple.
454 if ( $field['multiple'] ) {
455 if ( acf_is_empty( $value ) ) {
456 return array();
457 }
458 return acf_array( $value );
459 }
460
461 // Otherwise, return a single value.
462 return acf_unarray( $value );
463 }
464
465
466 /**
467 *
468 * This filter is appied to the $field before it is saved to the database
469 *
470 * @type filter
471 * @since 3.6
472 * @date 23/01/13
473 *
474 * @param $field - the field array holding all the field options
475 * @param $post_id - the field group ID (post_type = acf)
476 *
477 * @return $field - the modified field
478 */
479 function update_field( $field ) {
480
481 // decode choices (convert to array)
482 $field['choices'] = acf_decode_choices( $field['choices'] );
483 $field['default_value'] = acf_decode_choices( $field['default_value'], true );
484
485 // Convert back to string for single selects.
486 if ( ! $field['multiple'] ) {
487 $field['default_value'] = acf_unarray( $field['default_value'] );
488 }
489
490 // return
491 return $field;
492 }
493
494
495 /**
496 * This filter is appied to the $value before it is updated in the db
497 *
498 * @type filter
499 * @since 3.6
500 * @date 23/01/13
501 *
502 * @param $value - the value which will be saved in the database
503 * @param $post_id - the post_id of which the value will be saved
504 * @param $field - the field array holding all the field options
505 *
506 * @return $value - the modified value
507 */
508 function update_value( $value, $post_id, $field ) {
509
510 // Bail early if no value.
511 if ( empty( $value ) ) {
512 return $value;
513 }
514
515 // Format array of values.
516 // - Parse each value as string for SQL LIKE queries.
517 if ( is_array( $value ) ) {
518 $value = array_map( 'strval', $value );
519 }
520
521 // return
522 return $value;
523 }
524
525
526 /**
527 * This function will translate field settings
528 *
529 * @type function
530 * @date 8/03/2016
531 * @since 5.3.2
532 *
533 * @param $field (array)
534 * @return $field
535 */
536 function translate_field( $field ) {
537
538 // translate
539 $field['choices'] = acf_translate( $field['choices'] );
540
541 // return
542 return $field;
543 }
544
545
546 /**
547 * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
548 *
549 * @type filter
550 * @since 3.6
551 * @date 23/01/13
552 *
553 * @param $value (mixed) the value which was loaded from the database
554 * @param $post_id (mixed) the post_id from which the value was loaded
555 * @param $field (array) the field array holding all the field options
556 *
557 * @return $value (mixed) the modified value
558 */
559 function format_value( $value, $post_id, $field ) {
560 if ( is_array( $value ) ) {
561 foreach ( $value as $i => $val ) {
562 $value[ $i ] = $this->format_value_single( $val, $post_id, $field );
563 }
564 } else {
565 $value = $this->format_value_single( $value, $post_id, $field );
566 }
567 return $value;
568 }
569
570
571 function format_value_single( $value, $post_id, $field ) {
572
573 // bail early if is empty
574 if ( acf_is_empty( $value ) ) {
575 return $value;
576 }
577
578 // vars
579 $label = acf_maybe_get( $field['choices'], $value, $value );
580
581 // value
582 if ( $field['return_format'] == 'value' ) {
583
584 // do nothing
585 // label
586 } elseif ( $field['return_format'] == 'label' ) {
587 $value = $label;
588
589 // array
590 } elseif ( $field['return_format'] == 'array' ) {
591 $value = array(
592 'value' => $value,
593 'label' => $label,
594 );
595 }
596
597 // return
598 return $value;
599 }
600
601 /**
602 * Validates select fields updated via the REST API.
603 *
604 * @param boolean $valid The current validity booleean
605 * @param integer $value The value of the field
606 * @param array $field The field array
607 * @return boolean|WP_Error
608 */
609 public function validate_rest_value( $valid, $value, $field ) {
610 // rest_validate_request_arg() handles the other types, we just worry about strings.
611 if ( is_null( $value ) || is_array( $value ) ) {
612 return $valid;
613 }
614
615 $option_keys = array_diff(
616 array_keys( $field['choices'] ),
617 array_values( $field['choices'] )
618 );
619
620 $allowed = empty( $option_keys ) ? $field['choices'] : $option_keys;
621
622 if ( ! in_array( $value, $allowed ) ) {
623 $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
624 $data = array(
625 'param' => $param,
626 'value' => $value,
627 );
628 $error = sprintf(
629 __( '%1$s is not one of %2$s', 'acf' ),
630 $param,
631 implode( ', ', $allowed )
632 );
633
634 return new WP_Error( 'rest_invalid_param', $error, $data );
635 }
636
637 return $valid;
638 }
639
640 /**
641 * Formats the choices available for the REST API.
642 *
643 * @since 6.2
644 *
645 * @param array $choices The choices for the field.
646 * @return array
647 */
648 public function format_rest_choices( $choices ) {
649 $keys = array_keys( $choices );
650 $values = array_values( $choices );
651 $int_choices = array();
652
653 if ( array_diff( $keys, $values ) ) {
654 // User has specified custom keys.
655 $choices = $keys;
656 } else {
657 // Default keys, same as value.
658 $choices = $values;
659 }
660
661 // Assume everything is a string by default.
662 $choices = array_map( 'strval', $choices );
663
664 // Also allow integers if is_numeric().
665 foreach ( $choices as $choice ) {
666 if ( is_numeric( $choice ) ) {
667 $int_choices[] = (int) $choice;
668 }
669 }
670
671 return array_merge( $choices, $int_choices );
672 }
673
674 /**
675 * Return the schema array for the REST API.
676 *
677 * @param array $field The main field array.
678 * @return array
679 */
680 public function get_rest_schema( array $field ) {
681 $schema = array(
682 'type' => array( 'string', 'array', 'int', 'null' ),
683 'required' => ! empty( $field['required'] ),
684 'items' => array(
685 'type' => array( 'string', 'int' ),
686 'enum' => $this->format_rest_choices( $field['choices'] ),
687 ),
688 );
689
690 if ( empty( $field['allow_null'] ) ) {
691 $schema['minItems'] = 1;
692 }
693
694 if ( empty( $field['multiple'] ) ) {
695 $schema['maxItems'] = 1;
696 }
697
698 if ( isset( $field['default_value'] ) && '' !== $field['default_value'] ) {
699 $schema['default'] = $field['default_value'];
700 }
701
702 return $schema;
703 }
704 }
705
706
707 // initialize
708 acf_register_field_type( 'acf_field_select' );
709 endif; // class_exists check
710