PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.9
Advanced Custom Fields (ACF®) v6.8.9
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-taxonomy.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 6 months ago class-acf-field-image.php 1 month ago class-acf-field-link.php 5 months ago class-acf-field-message.php 6 months ago class-acf-field-number.php 5 months ago class-acf-field-oembed.php 2 months ago class-acf-field-output.php 6 months ago class-acf-field-page_link.php 1 month ago class-acf-field-password.php 5 months ago class-acf-field-post_object.php 1 month ago class-acf-field-radio.php 2 months ago class-acf-field-range.php 5 months ago class-acf-field-relationship.php 1 month ago class-acf-field-select.php 2 months ago class-acf-field-separator.php 6 months ago class-acf-field-tab.php 6 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-taxonomy.php
1072 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_taxonomy' ) ) :
13
14 class acf_field_taxonomy extends acf_field {
15
16 // vars
17 var $save_post_terms = array();
18
19
20 /**
21 * This function will setup the field type data
22 *
23 * @type function
24 * @date 5/03/2014
25 * @since 5.0.0
26 */
27 public function initialize() {
28 $this->name = 'taxonomy';
29 $this->label = __( 'Taxonomy', 'acf' );
30 $this->category = 'relational';
31 $this->description = __( 'Allows the selection of one or more taxonomy terms based on the criteria and options specified in the fields settings.', 'acf' );
32 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-taxonomy.png';
33 $this->doc_url = acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/resources/taxonomy/', 'docs', 'field-type-selection' );
34 $this->defaults = array(
35 'taxonomy' => 'category',
36 'field_type' => 'checkbox',
37 'multiple' => 0,
38 'allow_null' => 0,
39 'return_format' => 'id',
40 'add_term' => 1, // 5.2.3
41 'load_terms' => 0, // 5.2.7
42 'save_terms' => 0, // 5.2.7
43 'bidirectional_target' => array(),
44 );
45
46 // Register filter variations.
47 acf_add_filter_variations( 'acf/fields/taxonomy/query', array( 'name', 'key' ), 1 );
48 acf_add_filter_variations( 'acf/fields/taxonomy/result', array( 'name', 'key' ), 2 );
49
50 // ajax
51 add_action( 'wp_ajax_acf/fields/taxonomy/query', array( $this, 'ajax_query' ) );
52 add_action( 'wp_ajax_nopriv_acf/fields/taxonomy/query', array( $this, 'ajax_query' ) );
53 add_action( 'wp_ajax_acf/fields/taxonomy/add_term', array( $this, 'ajax_add_term' ) );
54 add_filter( 'acf/conditional_logic/choices', array( $this, 'render_field_taxonomy_conditional_choices' ), 10, 3 );
55
56 // actions
57 add_action( 'acf/save_post', array( $this, 'save_post' ), 15, 1 );
58 }
59
60 /**
61 * Returns AJAX results for the Taxonomy field.
62 *
63 * @since 5.0.0
64 *
65 * @return void
66 */
67 public function ajax_query() {
68 $nonce = acf_request_arg( 'nonce', '' );
69 $key = acf_request_arg( 'field_key', '' );
70 $conditional_logic = (bool) acf_request_arg( 'conditional_logic', false );
71
72 if ( $conditional_logic ) {
73 if ( ! acf_current_user_can_admin() ) {
74 die();
75 }
76
77 // Use the standard ACF admin nonce.
78 $nonce = '';
79 $key = '';
80 }
81
82 if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic, 'taxonomy' ) ) {
83 die();
84 }
85
86 acf_send_ajax_results( $this->get_ajax_query( $_POST ) );
87 }
88
89 /**
90 * This function will return an array of data formatted for use in a select2 AJAX response
91 *
92 * @type function
93 * @date 15/10/2014
94 * @since 5.0.9
95 *
96 * @param $options (array)
97 * @return (array)
98 */
99 function get_ajax_query( $options = array() ) {
100 $options = acf_parse_args(
101 $options,
102 array(
103 'post_id' => 0,
104 's' => '',
105 'field_key' => '',
106 'term_id' => '',
107 'include' => '',
108 'paged' => 1,
109 )
110 );
111
112 $field = acf_get_field( $options['field_key'] );
113 if ( ! $field ) {
114 return false;
115 }
116
117 // if options include isset, then we are loading a specific term.
118 if ( ! empty( $options['include'] ) ) {
119 $options['term_id'] = $options['include'];
120 // paged should be 1.
121 $options['paged'] = 1;
122 }
123
124 // Bail early if taxonomy does not exist.
125 if ( ! taxonomy_exists( $field['taxonomy'] ) ) {
126 return false;
127 }
128
129 $results = array();
130 $is_hierarchical = is_taxonomy_hierarchical( $field['taxonomy'] );
131 $is_pagination = ( $options['paged'] > 0 );
132 $is_search = false;
133 $limit = 20;
134 $offset = 20 * ( $options['paged'] - 1 );
135
136 $args = array(
137 'taxonomy' => $field['taxonomy'],
138 'hide_empty' => false,
139 );
140
141 // Don't bother for hierarchial terms, we will need to load all terms anyway.
142 if ( $is_pagination && ! $is_hierarchical ) {
143 $args['number'] = $limit;
144 $args['offset'] = $offset;
145 }
146
147 // search
148 if ( $options['s'] !== '' ) {
149
150 // strip slashes (search may be integer)
151 $s = wp_unslash( strval( $options['s'] ) );
152
153 $args['search'] = isset( $options['term_id'] ) && $options['term_id'] ? '' : $s;
154 $is_search = true;
155 }
156
157 $args = apply_filters( 'acf/fields/taxonomy/query', $args, $field, $options['post_id'] );
158
159 if ( ! empty( $options['include'] ) ) {
160 // Limit search to a specific id if one is provided.
161 $args['include'] = $options['include'];
162 }
163
164 $terms = acf_get_terms( $args );
165
166 // Sort hierachial.
167 if ( $is_hierarchical ) {
168 $limit = acf_maybe_get( $args, 'number', $limit );
169 $offset = acf_maybe_get( $args, 'offset', $offset );
170
171 $parent = acf_maybe_get( $args, 'parent', 0 );
172 $parent = acf_maybe_get( $args, 'child_of', $parent );
173
174 // This will fail if a search has taken place because parents wont exist.
175 if ( ! $is_search ) {
176 $ordered_terms = _get_term_children( $parent, $terms, $field['taxonomy'] );
177 // Check for empty array. Possible if parent did not exist within original data.
178 if ( ! empty( $ordered_terms ) ) {
179 $terms = $ordered_terms;
180 }
181 }
182
183 // Fake pagination.
184 if ( $is_pagination && ! $options['include'] ) {
185 $terms = array_slice( $terms, $offset, $limit );
186 }
187 }
188
189 // Append to r.
190 foreach ( $terms as $term ) {
191
192 // Add to json.
193 $results[] = array(
194 'id' => $term->term_id,
195 'text' => $this->get_term_title( $term, $field, $options['post_id'], true ),
196 );
197 }
198
199 $response = array(
200 'results' => $results,
201 'limit' => $limit,
202 );
203
204 return $response;
205 }
206
207 /**
208 * Returns the Term's title displayed in the field UI.
209 *
210 * @since 5.0.0
211 *
212 * @param WP_Term $term The term object.
213 * @param array $field The field settings.
214 * @param mixed $post_id The post_id being edited.
215 * @param boolean $unescape Should we return an unescaped post title.
216 * @return string
217 */
218 function get_term_title( $term, $field, $post_id = 0, $unescape = false ) {
219 $title = acf_get_term_title( $term );
220
221 // Default $post_id to current post being edited.
222 $post_id = $post_id ? $post_id : acf_get_form_data( 'post_id' );
223
224 // unescape for select2 output which handles the escaping.
225 if ( $unescape ) {
226 $title = html_entity_decode( $title );
227 }
228
229 /**
230 * Filters the term title.
231 *
232 * @date 1/11/2013
233 * @since 5.0.0
234 *
235 * @param string $title The term title.
236 * @param WP_Term $term The term object.
237 * @param array $field The field settings.
238 * @param (int|string) $post_id The post_id being edited.
239 */
240 return apply_filters( 'acf/fields/taxonomy/result', $title, $term, $field, $post_id );
241 }
242
243
244 /**
245 * This function will return an array of terms for a given field value
246 *
247 * @type function
248 * @date 13/06/2014
249 * @since 5.0.0
250 *
251 * @param $value (array)
252 * @return $value
253 */
254 function get_terms( $value, $taxonomy = 'category' ) {
255
256 // load terms in 1 query to save multiple DB calls from following code
257 if ( count( $value ) > 1 ) {
258 $terms = acf_get_terms(
259 array(
260 'taxonomy' => $taxonomy,
261 'include' => $value,
262 'hide_empty' => false,
263 )
264 );
265 }
266
267 // update value to include $post
268 foreach ( array_keys( $value ) as $i ) {
269 $value[ $i ] = get_term( $value[ $i ], $taxonomy );
270 }
271
272 // filter out null values
273 $value = array_filter( $value );
274
275 // return
276 return $value;
277 }
278
279
280 /**
281 * This filter is appied to the $value after it is loaded from the db
282 *
283 * @type filter
284 * @since 3.6
285 * @date 23/01/13
286 *
287 * @param $value - the value found in the database
288 * @param $post_id - the post_id from which the value was loaded from
289 * @param $field - the field array holding all the field options
290 *
291 * @return $value - the value to be saved in te database
292 */
293 function load_value( $value, $post_id, $field ) {
294
295 // get valid terms
296 $value = acf_get_valid_terms( $value, $field['taxonomy'] );
297
298 // load_terms
299 if ( $field['load_terms'] ) {
300
301 // Decode $post_id for $type and $id.
302 $decoded = acf_decode_post_id( $post_id );
303 $type = $decoded['type'];
304 $id = $decoded['id'];
305
306 if ( $type === 'block' ) {
307 // Get parent block...
308 }
309
310 // get terms
311 $term_ids = wp_get_object_terms(
312 $id,
313 $field['taxonomy'],
314 array(
315 'fields' => 'ids',
316 'orderby' => 'none',
317 )
318 );
319
320 // bail early if no terms
321 if ( empty( $term_ids ) || is_wp_error( $term_ids ) ) {
322 return false;
323 }
324
325 // sort
326 if ( ! empty( $value ) ) {
327 $order = array();
328
329 foreach ( $term_ids as $i => $v ) {
330 $order[ $i ] = array_search( $v, $value );
331 }
332
333 array_multisort( $order, $term_ids );
334 }
335
336 // update value
337 $value = $term_ids;
338 }
339
340 // convert back from array if neccessary
341 if ( $field['field_type'] == 'select' || $field['field_type'] == 'radio' ) {
342 $value = array_shift( $value );
343 }
344
345 // return
346 return $value;
347 }
348
349
350 /**
351 * Filters the field value before it is saved into the database.
352 *
353 * @since 3.6
354 *
355 * @param mixed $value The value which will be saved in the database.
356 * @param integer $post_id The post_id of which the value will be saved.
357 * @param array $field The field array holding all the field options.
358 * @return mixed $value The modified value.
359 */
360 public function update_value( $value, $post_id, $field ) {
361
362 if ( is_array( $value ) ) {
363 $value = array_filter( $value );
364 }
365
366 acf_update_bidirectional_values( acf_get_array( $value ), $post_id, $field, 'term' );
367
368 // save_terms if enabled.
369 if ( $field['save_terms'] ) {
370
371 // vars
372 $taxonomy = $field['taxonomy'];
373
374 // force value to array.
375 $term_ids = acf_get_array( $value );
376
377 // convert to int.
378 $term_ids = array_map( 'intval', $term_ids );
379
380 // get existing term id's (from a previously saved field).
381 $old_term_ids = isset( $this->save_post_terms[ $taxonomy ] ) ? $this->save_post_terms[ $taxonomy ] : array();
382
383 // append
384 $this->save_post_terms[ $taxonomy ] = array_merge( $old_term_ids, $term_ids );
385
386 // if called directly from frontend update_field().
387 if ( ! did_action( 'acf/save_post' ) ) {
388 $this->save_post( $post_id );
389 return $value;
390 }
391 }
392
393 return $value;
394 }
395
396 /**
397 * This function will save any terms in the save_post_terms array
398 *
399 * @since 5.0.9
400 *
401 * @param mixed $post_id The ACF post ID to save to.
402 * @return void
403 */
404 public function save_post( $post_id ) {
405 // Check for saved terms.
406 if ( ! empty( $this->save_post_terms ) ) {
407 /**
408 * Determine object ID allowing for non "post" $post_id (user, taxonomy, etc).
409 * Although not fully supported by WordPress, non "post" objects may use the term relationships table.
410 * Sharing taxonomies across object types is discouraged, but unique taxonomies work well.
411 * Note: Do not attempt to restrict to "post" only. This has been attempted in 5.8.9 and later reverted.
412 */
413 $decoded = acf_decode_post_id( $post_id );
414 $type = $decoded['type'];
415 $id = $decoded['id'];
416
417 if ( $type === 'block' ) {
418 // Get parent block...
419 }
420
421 // Loop over taxonomies and save terms.
422 foreach ( $this->save_post_terms as $taxonomy => $term_ids ) {
423 wp_set_object_terms( $id, $term_ids, $taxonomy, false );
424 }
425
426 // Reset storage.
427 $this->save_post_terms = array();
428 }
429 }
430
431 /**
432 * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
433 *
434 * @type filter
435 * @since 3.6
436 * @date 23/01/13
437 *
438 * @param $value (mixed) the value which was loaded from the database
439 * @param $post_id (mixed) the post_id from which the value was loaded
440 * @param $field (array) the field array holding all the field options
441 *
442 * @return $value (mixed) the modified value
443 */
444 function format_value( $value, $post_id, $field ) {
445
446 // bail early if no value
447 if ( empty( $value ) ) {
448 return false;
449 }
450
451 // force value to array
452 $value = acf_get_array( $value );
453
454 // load posts if needed
455 if ( $field['return_format'] == 'object' ) {
456
457 // get posts
458 $value = $this->get_terms( $value, $field['taxonomy'] );
459 }
460
461 // convert back from array if neccessary
462 if ( $field['field_type'] == 'select' || $field['field_type'] == 'radio' ) {
463 $value = array_shift( $value );
464 }
465
466 // return
467 return $value;
468 }
469
470 /**
471 * Renders the Taxonomy field.
472 *
473 * @since 3.6
474 *
475 * @param array $field The field settings array.
476 * @return void
477 */
478 public function render_field( $field ) {
479 // force value to array
480 $field['value'] = acf_get_array( $field['value'] );
481
482 $nonce = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
483
484 // vars
485 $div = array(
486 'class' => 'acf-taxonomy-field',
487 'data-save' => $field['save_terms'],
488 'data-ftype' => $field['field_type'],
489 'data-taxonomy' => $field['taxonomy'],
490 'data-allow_null' => $field['allow_null'],
491 'data-nonce' => $nonce,
492 );
493 // get taxonomy
494 $taxonomy = get_taxonomy( $field['taxonomy'] );
495
496 // bail early if taxonomy does not exist
497 if ( ! $taxonomy ) {
498 return;
499 }
500
501 ?>
502 <div <?php echo acf_esc_attrs( $div ); ?>>
503 <?php if ( $field['add_term'] && current_user_can( $taxonomy->cap->manage_terms ) ) : ?>
504 <div class="acf-actions -hover">
505 <a href="#" class="acf-icon -plus acf-js-tooltip small" data-name="add" title="<?php echo esc_attr( $taxonomy->labels->add_new_item ); ?>"></a>
506 </div>
507 <?php
508 endif;
509
510 if ( $field['field_type'] == 'select' ) {
511 $field['multiple'] = 0;
512
513 $this->render_field_select( $field, $nonce );
514 } elseif ( $field['field_type'] == 'multi_select' ) {
515 $field['multiple'] = 1;
516
517 $this->render_field_select( $field, $nonce );
518 } elseif ( $field['field_type'] == 'radio' ) {
519 $this->render_field_checkbox( $field );
520 } elseif ( $field['field_type'] == 'checkbox' ) {
521 $this->render_field_checkbox( $field );
522 }
523
524 ?>
525 </div>
526 <?php
527 }
528
529 /**
530 * Create the HTML interface for your field
531 *
532 * @type action
533 * @since 3.6
534 * @date 23/01/13
535 *
536 * @param $field - an array holding all the field's data
537 */
538 function render_field_select( $field, $nonce ) {
539
540 // Change Field into a select
541 $field['type'] = 'select';
542 $field['ui'] = 1;
543 $field['ajax'] = 1;
544 $field['nonce'] = $nonce;
545 $field['choices'] = array();
546
547 // value
548 if ( ! empty( $field['value'] ) ) {
549
550 // get terms
551 $terms = $this->get_terms( $field['value'], $field['taxonomy'] );
552
553 // set choices
554 if ( ! empty( $terms ) ) {
555 foreach ( array_keys( $terms ) as $i ) {
556
557 // vars
558 $term = acf_extract_var( $terms, $i );
559
560 // append to choices
561 $field['choices'][ $term->term_id ] = $this->get_term_title( $term, $field );
562 }
563 }
564 }
565
566 // render select
567 acf_render_field( $field );
568 }
569
570
571 /**
572 * Create the HTML interface for your field
573 *
574 * @since 3.6
575 *
576 * @param array $field an array holding all the field's data.
577 */
578 public function render_field_checkbox( $field ) {
579
580 // hidden input.
581 acf_hidden_input(
582 array(
583 'type' => 'hidden',
584 'name' => $field['name'],
585 )
586 );
587
588 // checkbox saves an array.
589 if ( $field['field_type'] == 'checkbox' ) {
590 $field['name'] .= '[]';
591 }
592
593 // taxonomy.
594 $taxonomy_obj = get_taxonomy( $field['taxonomy'] );
595
596 // include walker.
597 acf_include( 'includes/walkers/class-acf-walker-taxonomy-field.php' );
598
599 // vars.
600 $args = array(
601 'taxonomy' => $field['taxonomy'],
602 'show_option_none' => sprintf( _x( 'No %s', 'No Terms', 'acf' ), $taxonomy_obj->labels->name ),
603 'hide_empty' => false,
604 'style' => 'none',
605 'walker' => new ACF_Taxonomy_Field_Walker( $field ),
606 );
607
608 // filter for 3rd party customization.
609 $args = apply_filters( 'acf/fields/taxonomy/wp_list_categories', $args, $field );
610 $args = apply_filters( 'acf/fields/taxonomy/wp_list_categories/name=' . $field['_name'], $args, $field );
611 $args = apply_filters( 'acf/fields/taxonomy/wp_list_categories/key=' . $field['key'], $args, $field );
612
613 // Build UL attributes for accessibility and consistency.
614 $ul = array(
615 'class' => 'acf-checkbox-list acf-bl',
616 'role' => $field['field_type'] === 'radio' ? 'radiogroup' : 'group',
617 );
618
619 if ( ! empty( $field['id'] ) ) {
620 $ul['aria-labelledby'] = $field['id'] . '-label';
621 }
622 ?>
623 <div class="categorychecklist-holder">
624 <ul <?php echo acf_esc_attrs( $ul ); ?>>
625 <?php wp_list_categories( $args ); ?>
626 </ul>
627 </div>
628 <?php
629 }
630
631
632 /**
633 * Create extra options for your field. This is rendered when editing a field.
634 * The value of $field['name'] can be used (like bellow) to save extra data to the $field
635 *
636 * @type action
637 * @since 3.6
638 * @date 23/01/13
639 *
640 * @param $field - an array holding all the field's data
641 */
642 function render_field_settings( $field ) {
643 acf_render_field_setting(
644 $field,
645 array(
646 'label' => __( 'Taxonomy', 'acf' ),
647 'instructions' => __( 'Select the taxonomy to be displayed', 'acf' ),
648 'type' => 'select',
649 'name' => 'taxonomy',
650 'choices' => acf_get_taxonomy_labels(),
651 )
652 );
653
654 acf_render_field_setting(
655 $field,
656 array(
657 'label' => __( 'Create Terms', 'acf' ),
658 'instructions' => __( 'Allow new terms to be created whilst editing', 'acf' ),
659 'name' => 'add_term',
660 'type' => 'true_false',
661 'ui' => 1,
662 )
663 );
664
665 acf_render_field_setting(
666 $field,
667 array(
668 'label' => __( 'Save Terms', 'acf' ),
669 'instructions' => __( 'Connect selected terms to the post', 'acf' ),
670 'name' => 'save_terms',
671 'type' => 'true_false',
672 'ui' => 1,
673 )
674 );
675
676 acf_render_field_setting(
677 $field,
678 array(
679 'label' => __( 'Load Terms', 'acf' ),
680 'instructions' => __( 'Load value from posts terms', 'acf' ),
681 'name' => 'load_terms',
682 'type' => 'true_false',
683 'ui' => 1,
684 )
685 );
686
687 acf_render_field_setting(
688 $field,
689 array(
690 'label' => __( 'Return Value', 'acf' ),
691 'instructions' => '',
692 'type' => 'radio',
693 'name' => 'return_format',
694 'choices' => array(
695 'object' => __( 'Term Object', 'acf' ),
696 'id' => __( 'Term ID', 'acf' ),
697 ),
698 'layout' => 'horizontal',
699 )
700 );
701
702 acf_render_field_setting(
703 $field,
704 array(
705 'label' => __( 'Appearance', 'acf' ),
706 'instructions' => __( 'Select the appearance of this field', 'acf' ),
707 'type' => 'select',
708 'name' => 'field_type',
709 'optgroup' => true,
710 'choices' => array(
711 __( 'Multiple Values', 'acf' ) => array(
712 'checkbox' => __( 'Checkbox', 'acf' ),
713 'multi_select' => __( 'Multi Select', 'acf' ),
714 ),
715 __( 'Single Value', 'acf' ) => array(
716 'radio' => __( 'Radio Buttons', 'acf' ),
717 'select' => _x( 'Select', 'noun', 'acf' ),
718 ),
719 ),
720 )
721 );
722
723 acf_render_field_setting(
724 $field,
725 array(
726 'label' => __( 'Allow Null', 'acf' ),
727 'instructions' => '',
728 'name' => 'allow_null',
729 'type' => 'true_false',
730 'ui' => 1,
731 'conditions' => array(
732 'field' => 'field_type',
733 'operator' => '!=',
734 'value' => 'checkbox',
735 ),
736 )
737 );
738 }
739
740 /**
741 * Renders the field settings used in the "Advanced" tab.
742 *
743 * @since 6.2
744 *
745 * @param array $field The field settings array.
746 * @return void
747 */
748 public function render_field_advanced_settings( $field ) {
749 acf_render_bidirectional_field_settings( $field );
750 }
751
752 /**
753 * Filters choices in taxonomy conditions.
754 *
755 * @since 6.3
756 *
757 * @param array $choices The selected choice.
758 * @param array $conditional_field The conditional field settings object.
759 * @param string $rule_value The rule value.
760 * @return mixed
761 */
762 public function render_field_taxonomy_conditional_choices( $choices, $conditional_field, $rule_value ) {
763 if ( is_array( $conditional_field ) && $conditional_field['type'] === 'taxonomy' ) {
764 if ( ! empty( $rule_value ) ) {
765 $term = get_term( $rule_value );
766 $choices = array( $rule_value => $term->name );
767 }
768 }
769 return $choices;
770 }
771
772
773 /**
774 * AJAX handler for adding Taxonomy field terms.
775 *
776 * @since 5.2.3
777 *
778 * @return void
779 */
780 public function ajax_add_term() {
781 $args = acf_request_args(
782 array(
783 'nonce' => '',
784 'field_key' => '',
785 'term_name' => '',
786 'term_parent' => '',
787 )
788 );
789
790 if ( ! acf_verify_ajax( $args['nonce'], $args['field_key'], true, 'taxonomy' ) ) {
791 die();
792 }
793
794 // load field
795 $field = acf_get_field( $args['field_key'] );
796 if ( ! $field ) {
797 die();
798 }
799
800 // vars
801 $taxonomy_obj = get_taxonomy( $field['taxonomy'] );
802 $taxonomy_label = $taxonomy_obj->labels->singular_name;
803
804 // validate cap
805 // note: this situation should never occur due to condition of the add new button
806 if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
807 wp_send_json_error(
808 array(
809 'error' => sprintf( __( 'User unable to add new %s', 'acf' ), $taxonomy_label ),
810 )
811 );
812 }
813
814 // save?
815 if ( $args['term_name'] ) {
816
817 // exists
818 if ( term_exists( $args['term_name'], $field['taxonomy'], $args['term_parent'] ) ) {
819 wp_send_json_error(
820 array(
821 'error' => sprintf( __( '%s already exists', 'acf' ), $taxonomy_label ),
822 )
823 );
824 }
825
826 // vars
827 $extra = array();
828 if ( $args['term_parent'] ) {
829 $extra['parent'] = (int) $args['term_parent'];
830 }
831
832 // insert
833 $data = wp_insert_term( $args['term_name'], $field['taxonomy'], $extra );
834
835 // error
836 if ( is_wp_error( $data ) ) {
837 wp_send_json_error(
838 array(
839 'error' => $data->get_error_message(),
840 )
841 );
842 }
843
844 // load term
845 $term = get_term( $data['term_id'] );
846
847 // prepend ancenstors count to term name
848 $prefix = '';
849 $ancestors = get_ancestors( $term->term_id, $term->taxonomy );
850 if ( ! empty( $ancestors ) ) {
851 $prefix = str_repeat( '- ', count( $ancestors ) );
852 }
853
854 // success
855 wp_send_json_success(
856 array(
857 'message' => sprintf( __( '%s added', 'acf' ), $taxonomy_label ),
858 'term_id' => $term->term_id,
859 'term_name' => $term->name,
860 'term_label' => $prefix . $term->name,
861 'term_parent' => $term->parent,
862 )
863 );
864 }
865
866 ?>
867 <form method="post">
868 <?php
869
870 acf_render_field_wrap(
871 array(
872 'label' => __( 'Name', 'acf' ),
873 'name' => 'term_name',
874 'type' => 'text',
875 )
876 );
877
878 if ( is_taxonomy_hierarchical( $field['taxonomy'] ) ) {
879 $choices = array();
880 $response = $this->get_ajax_query( $args );
881
882 if ( $response ) {
883 foreach ( $response['results'] as $v ) {
884 $choices[ $v['id'] ] = $v['text'];
885 }
886 }
887
888 acf_render_field_wrap(
889 array(
890 'label' => __( 'Parent', 'acf' ),
891 'name' => 'term_parent',
892 'type' => 'select',
893 'allow_null' => 1,
894 'ui' => 0,
895 'choices' => $choices,
896 )
897 );
898 }
899
900 ?>
901 <p class="acf-submit">
902 <button class="acf-submit-button button button-primary" type="submit"><?php esc_html_e( 'Add', 'acf' ); ?></button>
903 </p>
904 </form><?php
905
906 // die
907 die;
908 }
909
910 /**
911 * Return the schema array for the REST API.
912 *
913 * @param array $field
914 * @return array
915 */
916 public function get_rest_schema( array $field ) {
917 $schema = array(
918 'type' => array( 'integer', 'array', 'null' ),
919 'required' => ! empty( $field['required'] ),
920 'items' => array(
921 'type' => 'integer',
922 ),
923 );
924
925 if ( empty( $field['allow_null'] ) ) {
926 $schema['minItems'] = 1;
927 }
928
929 if ( in_array( $field['field_type'], array( 'radio', 'select' ) ) ) {
930 $schema['maxItems'] = 1;
931 }
932
933 return $schema;
934 }
935
936 /**
937 * @see \acf_field::get_rest_links()
938 * @param mixed $value The raw (unformatted) field value.
939 * @param integer|string $post_id
940 * @param array $field
941 * @return array
942 */
943 public function get_rest_links( $value, $post_id, array $field ) {
944 $links = array();
945
946 if ( empty( $value ) ) {
947 return $links;
948 }
949
950 foreach ( (array) $value as $object_id ) {
951 $term = get_term( $object_id );
952 if ( ! $term instanceof WP_Term ) {
953 continue;
954 }
955
956 $rest_base = acf_get_object_type_rest_base( get_taxonomy( $term->taxonomy ) );
957 if ( ! $rest_base ) {
958 continue;
959 }
960
961 $links[] = array(
962 'rel' => 'acf:term',
963 'href' => rest_url( sprintf( '/wp/v2/%s/%s', $rest_base, $object_id ) ),
964 'embeddable' => true,
965 'taxonomy' => $term->taxonomy,
966 );
967 }
968
969 return $links;
970 }
971
972 /**
973 * Returns an array of JSON-LD Property output types that are supported by this field type.
974 *
975 * @since 6.8
976 *
977 * @return string[]
978 */
979 public function get_jsonld_output_types(): array {
980 return array( 'DefinedTerm', 'Text' );
981 }
982
983 /**
984 * Formats the field value for JSON-LD output.
985 *
986 * @since 6.8.0
987 *
988 * @param mixed $value The value of the field.
989 * @param integer|string $post_id The ID of the post.
990 * @param array $field The field array.
991 * @return mixed
992 */
993 public function format_value_for_jsonld( $value, $post_id, $field ) {
994 if ( empty( $value ) ) {
995 return null;
996 }
997
998 // Get output format with fallback.
999 $output_format = $field['schema_output_format'] ?? '';
1000 if ( empty( $output_format ) ) {
1001 $property = $field['schema_property'] ?? '';
1002 $output_format = \ACF\AI\GEO\Schema::get_default_output_format( $this->name, $property );
1003 }
1004
1005 // Default to Text if no format determined.
1006 if ( empty( $output_format ) ) {
1007 $output_format = 'Text';
1008 }
1009
1010 // Force value to array for consistent processing.
1011 $value = acf_get_array( $value );
1012
1013 // Get term objects.
1014 $terms = $this->get_terms( $value, $field['taxonomy'] );
1015
1016 if ( empty( $terms ) ) {
1017 return null;
1018 }
1019
1020 // Format based on output format.
1021 $formatted = array();
1022 foreach ( $terms as $term ) {
1023 if ( ! $term instanceof \WP_Term ) {
1024 continue;
1025 }
1026
1027 if ( 'Text' === $output_format ) {
1028 $formatted[] = $term->name;
1029 } else {
1030 // DefinedTerm format.
1031 $term_data = array(
1032 '@type' => 'DefinedTerm',
1033 'name' => $term->name,
1034 );
1035
1036 // Add term URL if available.
1037 $term_link = get_term_link( $term );
1038 if ( ! is_wp_error( $term_link ) ) {
1039 $term_data['url'] = $term_link;
1040 }
1041
1042 // Add term ID as identifier.
1043 $term_data['identifier'] = (string) $term->term_id;
1044
1045 $formatted[] = $term_data;
1046 }
1047 }
1048
1049 if ( empty( $formatted ) ) {
1050 return null;
1051 }
1052
1053 // Return single value for single-value field types.
1054 // Radio is always single. Select is single unless multiple is enabled.
1055 $is_single = 'radio' === $field['field_type'] ||
1056 ( 'select' === $field['field_type'] && empty( $field['multiple'] ) );
1057
1058 if ( $is_single ) {
1059 return $formatted[0];
1060 }
1061
1062 return $formatted;
1063 }
1064 }
1065
1066
1067 // initialize
1068 acf_register_field_type( 'acf_field_taxonomy' );
1069 endif; // class_exists check
1070
1071 ?>
1072