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 / post-types / class-acf-taxonomy.php
advanced-custom-fields / includes / post-types Last commit date
class-acf-field-group.php 5 months ago class-acf-post-type.php 5 months ago class-acf-taxonomy.php 5 months ago index.php 2 years ago
class-acf-taxonomy.php
857 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 ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 if ( ! class_exists( 'ACF_Taxonomy' ) ) {
17 class ACF_Taxonomy extends ACF_Internal_Post_Type {
18
19 /**
20 * The ACF internal post type name.
21 *
22 * @var string
23 */
24 public $post_type = 'acf-taxonomy';
25
26 /**
27 * The prefix for the key used in the main post array.
28 *
29 * @var string
30 */
31 public $post_key_prefix = 'taxonomy_';
32
33 /**
34 * The cache key for a singular post.
35 *
36 * @var string
37 */
38 public $cache_key = 'acf_get_taxonomy_post:key:';
39
40 /**
41 * The cache key for a collection of posts.
42 *
43 * @var string
44 */
45 public $cache_key_plural = 'acf_get_taxonomy_posts';
46
47 /**
48 * The hook name for a singular post.
49 *
50 * @var string
51 */
52 public $hook_name = 'taxonomy';
53
54 /**
55 * The hook name for a collection of posts.
56 *
57 * @var string
58 */
59 public $hook_name_plural = 'taxonomies';
60
61 /**
62 * The name of the store used for the post type.
63 *
64 * @var string
65 */
66 public $store = 'taxonomies';
67
68 /**
69 * Constructs the class.
70 */
71 public function __construct() {
72 $this->register_post_type();
73
74 // Include admin classes in admin.
75 if ( is_admin() ) {
76 acf_include( 'includes/admin/admin-internal-post-type-list.php' );
77 acf_include( 'includes/admin/admin-internal-post-type.php' );
78 acf_include( 'includes/admin/post-types/admin-taxonomy.php' );
79 acf_include( 'includes/admin/post-types/admin-taxonomies.php' );
80 }
81
82 parent::__construct();
83
84 add_action( 'acf/init', array( $this, 'register_taxonomies' ), 6 );
85 }
86
87 /**
88 * Registers the acf-taxonomy custom post type with WordPress.
89 *
90 * @since 6.1
91 */
92 public function register_post_type() {
93 $cap = acf_get_setting( 'capability' );
94
95 register_post_type(
96 'acf-taxonomy',
97 array(
98 'labels' => array(
99 'name' => __( 'Taxonomies', 'acf' ),
100 'singular_name' => __( 'Taxonomies', 'acf' ),
101 'add_new' => __( 'Add New', 'acf' ),
102 'add_new_item' => __( 'Add New Taxonomy', 'acf' ),
103 'edit_item' => __( 'Edit Taxonomy', 'acf' ),
104 'new_item' => __( 'New Taxonomy', 'acf' ),
105 'view_item' => __( 'View Taxonomy', 'acf' ),
106 'search_items' => __( 'Search Taxonomies', 'acf' ),
107 'not_found' => __( 'No Taxonomies found', 'acf' ),
108 'not_found_in_trash' => __( 'No Taxonomies found in Trash', 'acf' ),
109 ),
110 'public' => false,
111 'hierarchical' => true,
112 'show_ui' => true,
113 'show_in_menu' => false,
114 '_builtin' => false,
115 'capability_type' => 'post',
116 'capabilities' => array(
117 'edit_post' => $cap,
118 'delete_post' => $cap,
119 'edit_posts' => $cap,
120 'delete_posts' => $cap,
121 ),
122 'supports' => false,
123 'rewrite' => false,
124 'query_var' => false,
125 )
126 );
127 }
128
129 /**
130 * Register activated taxonomies with WordPress
131 *
132 * @since 6.1
133 */
134 public function register_taxonomies() {
135 $taxonomies = $this->get_posts( array( 'active' => true ) );
136 foreach ( $taxonomies as $taxonomy ) {
137 $args = $this->get_taxonomy_args( $taxonomy );
138 if ( ! taxonomy_exists( $taxonomy['taxonomy'] ) ) {
139 register_taxonomy( $taxonomy['taxonomy'], (array) $taxonomy['object_type'], $args );
140 } else {
141 // Flag on the store we bailed on registering this as it already exists.
142 $store = acf_get_store( $this->store );
143 $store_value = $store->get( $taxonomy['key'] );
144 $store_value['not_registered'] = true;
145 $store->set( $taxonomy['key'], $store_value );
146 }
147 }
148 }
149
150 /**
151 * Gets the default settings array for an ACF taxonomy.
152 *
153 * @return array
154 */
155 public function get_settings_array() {
156 return array(
157 // ACF-specific settings.
158 'ID' => 0,
159 'key' => '',
160 'title' => '',
161 'menu_order' => 0,
162 'active' => true,
163 'taxonomy' => '', // Taxonomy key passed as first param to register_taxonomy().
164 'object_type' => array(), // Converted to objects array passed as second parameter.
165 'advanced_configuration' => 0,
166 'import_source' => '',
167 'import_date' => '',
168 // Settings passed to register_taxonomy().
169 'labels' => array(
170 'singular_name' => '',
171 'name' => '',
172 'menu_name' => '',
173 'search_items' => '',
174 'popular_items' => '',
175 'all_items' => '',
176 'parent_item' => '',
177 'parent_item_colon' => '',
178 'name_field_description' => '',
179 'slug_field_description' => '',
180 'parent_field_description' => '',
181 'desc_field_description' => '',
182 'edit_item' => '',
183 'view_item' => '',
184 'update_item' => '',
185 'add_new_item' => '',
186 'new_item_name' => '',
187 'separate_items_with_commas' => '',
188 'add_or_remove_items' => '',
189 'choose_from_most_used' => '',
190 'not_found' => '',
191 'no_terms' => '',
192 'filter_by_item' => '',
193 'items_list_navigation' => '',
194 'items_list' => '',
195 'most_used' => '',
196 'back_to_items' => '',
197 'item_link' => '',
198 'item_link_description' => '',
199 ),
200 'description' => '',
201 'capabilities' => array(
202 'manage_terms' => 'manage_categories',
203 'edit_terms' => 'manage_categories',
204 'delete_terms' => 'manage_categories',
205 'assign_terms' => 'edit_posts',
206 ),
207 'public' => true,
208 'publicly_queryable' => true,
209 'hierarchical' => false,
210 'show_ui' => true,
211 'show_in_menu' => true,
212 'show_in_nav_menus' => true,
213 'show_in_rest' => true,
214 'rest_base' => '',
215 'rest_namespace' => 'wp/v2',
216 'rest_controller_class' => 'WP_REST_Terms_Controller',
217 'show_tagcloud' => true,
218 'show_in_quick_edit' => true,
219 'show_admin_column' => false,
220 'rewrite' => array(
221 'permalink_rewrite' => 'taxonomy_key', // ACF-specific option.
222 'slug' => '',
223 'with_front' => true,
224 'rewrite_hierarchical' => false,
225 ),
226 'query_var' => 'taxonomy_key',
227 'query_var_name' => '',
228 'default_term' => array(
229 'default_term_enabled' => false,
230 'default_term_name' => '',
231 'default_term_slug' => '',
232 'default_term_description' => '',
233 ),
234 'sort' => null,
235 'meta_box' => 'default',
236 'meta_box_cb' => '',
237 'meta_box_sanitize_cb' => '',
238 'allow_ai_access' => false,
239 'ai_description' => '',
240 );
241 }
242
243 /**
244 * Validates post type values before allowing save from the global $_POST object.
245 * Errors are added to the form using acf_add_internal_post_type_validation_error().
246 *
247 * @since 6.1
248 *
249 * @return boolean validity status
250 */
251 public function ajax_validate_values() {
252 if ( empty( $_POST['acf_taxonomy'] ) || empty( $_POST['acf_taxonomy']['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
253 return false;
254 }
255
256 $taxonomy_key = acf_sanitize_request_args( wp_unslash( $_POST['acf_taxonomy']['taxonomy'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
257 $taxonomy_key = is_string( $taxonomy_key ) ? $taxonomy_key : '';
258 $valid = true;
259
260 if ( strlen( $taxonomy_key ) > 32 ) {
261 $valid = false;
262 acf_add_internal_post_type_validation_error( 'taxonomy', __( 'The taxonomy key must be under 32 characters.', 'acf' ) );
263 }
264
265 if ( preg_match( '/^[a-z0-9_-]*$/', $taxonomy_key ) !== 1 ) {
266 $valid = false;
267 acf_add_internal_post_type_validation_error( 'taxonomy', __( 'The taxonomy key must only contain lower case alphanumeric characters, underscores or dashes.', 'acf' ) );
268 }
269
270 if ( in_array( $taxonomy_key, acf_get_wp_reserved_terms(), true ) ) {
271 $valid = false;
272 /* translators: %s a link to WordPress.org's Reserved Terms page */
273 $message = sprintf( __( 'This field must not be a WordPress <a href="%s" target="_blank">reserved term</a>.', 'acf' ), 'https://codex.wordpress.org/Reserved_Terms' );
274 acf_add_internal_post_type_validation_error( 'taxonomy', $message );
275 } else {
276 // Check if this post key exists in the ACF store for registered post types, excluding those which failed registration.
277 $store = acf_get_store( $this->store );
278 $post_id = (int) acf_maybe_get_POST( 'post_id', 0 );
279
280 $matches = array_filter(
281 $store->get_data(),
282 function ( $item ) use ( $taxonomy_key ) {
283 return $item['taxonomy'] === $taxonomy_key && empty( $item['not_registered'] );
284 }
285 );
286 $duplicates = array_filter(
287 $matches,
288 function ( $item ) use ( $post_id ) {
289 return $item['ID'] !== $post_id;
290 }
291 );
292
293 if ( $duplicates ) {
294 $valid = false;
295 acf_add_internal_post_type_validation_error( 'taxonomy', __( 'This taxonomy key is already in use by another taxonomy in ACF and cannot be used.', 'acf' ) );
296 // If we're not already in use with another ACF taxonomy, check if we're registered, but not by ACF.
297 } elseif ( empty( $matches ) && taxonomy_exists( $taxonomy_key ) ) {
298 $valid = false;
299 acf_add_internal_post_type_validation_error( 'taxonomy', __( 'This taxonomy key is already in use by another taxonomy registered outside of ACF and cannot be used.', 'acf' ) );
300 }
301 }
302
303 $valid = apply_filters( "acf/{$this->hook_name}/ajax_validate_values", $valid, $_POST['acf_taxonomy'] ); // phpcs:ignore WordPress.Security -- Raw input send to hook for validation.
304
305 return $valid;
306 }
307
308 /**
309 * Parses ACF taxonomy settings and returns an array of taxonomy
310 * args that can be easily handled by `register_taxonomy()`.
311 *
312 * Omits settings that line up with the WordPress defaults to reduce the size
313 * of the array passed to `register_taxonomy()`, which might be exported.
314 *
315 * @since 6.1
316 *
317 * @param array $post The main ACF taxonomy settings array.
318 * @param boolean $escape_labels Determines if the label values should be escaped.
319 * @return array
320 */
321 public function get_taxonomy_args( $post, $escape_labels = true ) {
322 $args = array();
323
324 // Make sure any provided labels are escaped strings and not empty.
325 $labels = array_filter( $post['labels'] );
326 $labels = array_map( 'strval', $labels );
327 if ( $escape_labels ) {
328 $labels = array_map( 'esc_html', $labels );
329 }
330 if ( ! empty( $labels ) ) {
331 $args['labels'] = $labels;
332 }
333
334 // Description is an optional string.
335 if ( ! empty( $post['description'] ) ) {
336 $args['description'] = (string) $post['description'];
337 }
338
339 // ACF requires the public setting to decide other settings.
340 $args['public'] = ! empty( $post['public'] );
341
342 // WordPress and ACF both default to false, so this can be omitted if still false.
343 if ( ! empty( $post['hierarchical'] ) ) {
344 $args['hierarchical'] = true;
345 }
346
347 // WordPress defaults to the same as $args['public'].
348 $publicly_queryable = (bool) $post['publicly_queryable'];
349 if ( $publicly_queryable !== $args['public'] ) {
350 $args['publicly_queryable'] = $publicly_queryable;
351 }
352
353 // WordPress defaults to the same as $args['public'].
354 $show_ui = (bool) $post['show_ui'];
355 if ( $show_ui !== $args['public'] ) {
356 $args['show_ui'] = $show_ui;
357 }
358
359 // WordPress defaults to the same as $args['show_ui'].
360 $show_in_menu = $post['show_in_menu'];
361 if ( $show_in_menu !== $show_ui ) {
362 $args['show_in_menu'] = (bool) $show_in_menu;
363 }
364
365 // WordPress defaults to the same as $args['public'].
366 $show_in_nav_menus = (bool) $post['show_in_nav_menus'];
367 if ( $show_in_nav_menus !== $args['public'] ) {
368 $args['show_in_nav_menus'] = $show_in_nav_menus;
369 }
370
371 // ACF defaults to true, but can be overridden.
372 $show_in_rest = (bool) $post['show_in_rest'];
373 $args['show_in_rest'] = $show_in_rest;
374
375 // WordPress defaults to `$taxonomy`.
376 $rest_base = (string) $post['rest_base'];
377 if ( ! empty( $rest_base ) && $rest_base !== $post['taxonomy'] ) {
378 $args['rest_base'] = $rest_base;
379 }
380
381 // WordPress defaults to "wp/v2".
382 $rest_namespace = (string) $post['rest_namespace'];
383 if ( ! empty( $rest_namespace ) && 'wp/v2' !== $rest_namespace ) {
384 $args['rest_namespace'] = $post['rest_namespace'];
385 }
386
387 // WordPress defaults to `WP_REST_Terms_Controller`.
388 $rest_controller_class = (string) $post['rest_controller_class'];
389 if ( ! empty( $rest_controller_class ) && 'WP_REST_Terms_Controller' !== $rest_controller_class ) {
390 $args['rest_controller_class'] = $rest_controller_class;
391 }
392
393 // WordPress defaults to the same as `$args['show_ui']`.
394 $show_tagcloud = (bool) $post['show_tagcloud'];
395 if ( $show_tagcloud !== $show_ui ) {
396 $args['show_tagcloud'] = $show_tagcloud;
397 }
398
399 // WordPress defaults to the same as `$args['show_ui']`.
400 $show_in_quick_edit = (bool) $post['show_in_quick_edit'];
401 if ( $show_in_quick_edit !== $show_ui ) {
402 $args['show_in_quick_edit'] = $show_tagcloud;
403 }
404
405 // WordPress defaults to false.
406 $show_admin_column = (bool) $post['show_admin_column'];
407 if ( $show_admin_column ) {
408 $args['show_admin_column'] = true;
409 }
410
411 $capabilities = array();
412
413 if ( ! empty( $post['capabilities']['manage_terms'] ) && 'manage_categories' !== $post['capabilities']['manage_terms'] ) {
414 $capabilities['manage_terms'] = (string) $post['capabilities']['manage_terms'];
415 }
416
417 if ( ! empty( $post['capabilities']['edit_terms'] ) && 'manage_categories' !== $post['capabilities']['edit_terms'] ) {
418 $capabilities['edit_terms'] = (string) $post['capabilities']['edit_terms'];
419 }
420
421 if ( ! empty( $post['capabilities']['delete_terms'] ) && 'manage_categories' !== $post['capabilities']['delete_terms'] ) {
422 $capabilities['delete_terms'] = (string) $post['capabilities']['delete_terms'];
423 }
424
425 if ( ! empty( $post['capabilities']['assign_terms'] ) && 'edit_posts' !== $post['capabilities']['assign_terms'] ) {
426 $capabilities['assign_terms'] = (string) $post['capabilities']['assign_terms'];
427 }
428
429 if ( ! empty( $capabilities ) ) {
430 $args['capabilities'] = $capabilities;
431 }
432
433 // WordPress defaults to the tags/categories metabox, but a custom callback or `false` is also supported.
434 $meta_box = isset( $post['meta_box'] ) ? (string) $post['meta_box'] : 'default';
435
436 if ( 'custom' === $meta_box && ! empty( $post['meta_box_cb'] ) ) {
437 $args['meta_box_cb'] = array( $this, 'build_safe_context_for_metabox_cb' );
438
439 if ( ! empty( $post['meta_box_sanitize_cb'] ) ) {
440 $args['meta_box_sanitize_cb'] = (string) $post['meta_box_sanitize_cb'];
441 }
442 } elseif ( 'disabled' === $meta_box ) {
443 $args['meta_box_cb'] = false;
444 }
445
446 // The rewrite arg can be a boolean or array of further settings. WordPress and ACF default to true.
447 $rewrite = (array) $post['rewrite'];
448 $rewrite_enabled = true;
449 $rewrite_args = array();
450
451 // Value of ACF select (not passed to `register_taxonomy()`).
452 $rewrite['permalink_rewrite'] = isset( $rewrite['permalink_rewrite'] ) ? (string) $rewrite['permalink_rewrite'] : 'taxonomy_key';
453
454 if ( 'no_permalink' === $rewrite['permalink_rewrite'] ) {
455 $rewrite_enabled = false;
456 }
457
458 // Rewrite slug defaults to $taxonomy key.
459 if ( ! empty( $rewrite['slug'] ) && $rewrite['slug'] !== $post['taxonomy'] && 'custom_permalink' === $rewrite['permalink_rewrite'] ) {
460 $rewrite_args['slug'] = (string) $rewrite['slug'];
461 }
462
463 // WordPress defaults to true.
464 if ( isset( $rewrite['with_front'] ) && ! $rewrite['with_front'] && $rewrite_enabled ) {
465 $rewrite_args['with_front'] = false;
466 }
467
468 // WordPress defaults to false.
469 if ( isset( $rewrite['rewrite_hierarchical'] ) && $rewrite['rewrite_hierarchical'] && $rewrite_enabled ) {
470 $rewrite_args['hierarchical'] = true;
471 }
472
473 if ( $rewrite_enabled && ! empty( $rewrite_args ) ) {
474 $args['rewrite'] = $rewrite_args;
475 } elseif ( ! $rewrite_enabled ) {
476 $args['rewrite'] = false;
477 }
478
479 // WordPress and ACF default to $taxonomy key, a boolean can also be used.
480 $query_var = (string) $post['query_var'];
481 if ( 'custom_query_var' === $query_var ) {
482 $query_var_name = (string) $post['query_var_name'];
483
484 if ( ! empty( $query_var_name ) && $query_var_name !== $post['taxonomy'] ) {
485 $args['query_var'] = $query_var_name;
486 }
487 } elseif ( 'none' === $query_var ) {
488 $args['query_var'] = false;
489 }
490
491 // WordPress accepts a string or an array of term info, but always converts into an array.
492 $default_term = (array) $post['default_term'];
493 if ( isset( $default_term['default_term_enabled'] ) && $default_term['default_term_enabled'] ) {
494 $args['default_term'] = array();
495
496 if ( isset( $default_term['default_term_name'] ) && ! empty( $default_term['default_term_name'] ) ) {
497 $args['default_term']['name'] = (string) $default_term['default_term_name'];
498 }
499
500 if ( isset( $default_term['default_term_slug'] ) && ! empty( $default_term['default_term_slug'] ) ) {
501 $args['default_term']['slug'] = (string) $default_term['default_term_slug'];
502 }
503
504 if ( isset( $default_term['default_term_description'] ) && ! empty( $default_term['default_term_description'] ) ) {
505 $args['default_term']['description'] = (string) $default_term['default_term_description'];
506 }
507 }
508
509 // WordPress defaults to null, equivalent to false.
510 $sort = (bool) $post['sort'];
511 if ( $sort ) {
512 $args['sort'] = true;
513 }
514
515 return apply_filters( 'acf/taxonomy/registration_args', $args, $post );
516 }
517
518 /**
519 * Ensure the metabox being called does not perform any unsafe operations.
520 *
521 * @since 6.3.8
522 *
523 * @param WP_Post $post The post being rendered.
524 * @param array $tax The provided taxonomy information required for callback render.
525 * @return mixed The callback result.
526 */
527 public function build_safe_context_for_metabox_cb( $post, $tax ) {
528 $taxonomies = $this->get_posts();
529 $this_tax = array_filter(
530 $taxonomies,
531 function ( $taxonomy ) use ( $tax ) {
532 return $taxonomy['taxonomy'] === $tax['args']['taxonomy'];
533 }
534 );
535 if ( empty( $this_tax ) || ! is_array( $this_tax ) ) {
536 // Unable to find the ACF taxonomy. Don't do anything.
537 return;
538 }
539 $acf_taxonomy = array_shift( $this_tax );
540 $original_cb = isset( $acf_taxonomy['meta_box_cb'] ) ? $acf_taxonomy['meta_box_cb'] : false;
541
542 // Prevent access to any wp_ prefixed functions in a callback.
543 if ( apply_filters( 'acf/taxonomy/prevent_access_to_wp_functions_in_meta_box_cb', true ) && substr( strtolower( $original_cb ), 0, 3 ) === 'wp_' ) {
544 // Don't execute register meta box callbacks if an internal wp function by default.
545 return;
546 }
547
548 $unset = array( '_POST', '_GET', '_REQUEST', '_COOKIE', '_SESSION', '_FILES', '_ENV', '_SERVER' );
549 $originals = array();
550
551 foreach ( $unset as $var ) {
552 if ( isset( $GLOBALS[ $var ] ) ) {
553 $originals[ $var ] = $GLOBALS[ $var ];
554 $GLOBALS[ $var ] = array(); //phpcs:ignore -- used for building a safe context
555 }
556 }
557
558 $return = false;
559 if ( is_callable( $original_cb ) ) {
560 $return = call_user_func( $original_cb, $post, $tax );
561 }
562
563 foreach ( $unset as $var ) {
564 if ( isset( $originals[ $var ] ) ) {
565 $GLOBALS[ $var ] = $originals[ $var ]; //phpcs:ignore -- used for restoring the original context
566 }
567 }
568
569 return $return;
570 }
571
572 /**
573 * Returns a string that can be used to create a taxonomy in PHP.
574 *
575 * @since 6.1
576 *
577 * @param array $post The main taxonomy array.
578 * @return string
579 */
580 public function export_post_as_php( $post = array() ) {
581 $return = '';
582 if ( empty( $post ) ) {
583 return $return;
584 }
585
586 $post = $this->validate_post( $post );
587 $taxonomy_key = $post['taxonomy'];
588 $objects = (array) $post['object_type'];
589 $objects = var_export( $objects, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
590 $args = $this->get_taxonomy_args( $post, false );
591
592 // Restore original metabox callback.
593 if ( ! empty( $args['meta_box_cb'] ) && ! empty( $post['meta_box_cb'] ) ) {
594 $args['meta_box_cb'] = $post['meta_box_cb'];
595 }
596
597 $args = var_export( $args, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
598
599 if ( ! $args ) {
600 return $return;
601 }
602
603 $args = $this->format_code_for_export( $args );
604 $objects = $this->format_code_for_export( $objects );
605
606 $return .= "register_taxonomy( '{$taxonomy_key}', {$objects}, {$args} );\r\n";
607
608 return esc_textarea( $return );
609 }
610
611 /**
612 * Flush rewrite rules whenever anything changes about a taxonomy.
613 *
614 * @since 6.1
615 *
616 * @param array $post The main post type array.
617 */
618 public function flush_post_cache( $post ) {
619 // Bail early if we won't be able to register/unregister the taxonomy.
620 if ( ! isset( $post['taxonomy'] ) ) {
621 return;
622 }
623
624 // Temporarily unregister the taxonomy so that we can re-register with the latest args below.
625 if ( empty( $post['active'] ) || taxonomy_exists( $post['taxonomy'] ) ) {
626 unregister_taxonomy( $post['taxonomy'] );
627 }
628
629 // When this is being called, the post type may not have been registered yet, so we do it now.
630 if ( ! empty( $post['active'] ) ) {
631 $post['object_type'] = isset( $post['object_type'] ) ? (array) $post['object_type'] : array();
632 register_taxonomy( $post['taxonomy'], $post['object_type'], $this->get_taxonomy_args( $post ) );
633 }
634
635 parent::flush_post_cache( $post );
636 flush_rewrite_rules();
637 }
638
639 /**
640 * Translates an ACF post.
641 *
642 * @since 6.1
643 *
644 * @param array $post The field group array.
645 * @return array
646 */
647 public function translate_post( $post = array() ) {
648 // Get settings.
649 $l10n = acf_get_setting( 'l10n' );
650 $l10n_textdomain = acf_get_setting( 'l10n_textdomain' );
651
652 // Translate field settings if textdomain is set.
653 if ( $l10n && $l10n_textdomain ) {
654 $post['title'] = acf_translate( $post['title'] );
655 $post['description'] = acf_translate( $post['description'] );
656 foreach ( $post['labels'] as $key => $label ) {
657 $post['labels'][ $key ] = acf_translate( $label );
658 }
659
660 /**
661 * Filters the post array to translate strings.
662 *
663 * @date 12/02/2014
664 * @since 5.0.0
665 *
666 * @param array $post The post array.
667 */
668 $post = apply_filters( "acf/translate_{$this->hook_name}", $post );
669 }
670
671 return $post;
672 }
673
674 /**
675 * Prepares an ACF taxonomy for import.
676 *
677 * @since 6.3.10
678 *
679 * @param array $post The ACF post array.
680 * @return array
681 */
682 public function prepare_post_for_import( $post ) {
683 if ( ! acf_get_setting( 'enable_meta_box_cb_edit' ) && ( ! empty( $post['meta_box_cb'] ) || ! empty( $post['meta_box_sanitize_cb'] ) ) ) {
684 $post['meta_box_cb'] = '';
685 $post['meta_box_sanitize_cb'] = '';
686
687 if ( ! empty( $post['meta_box'] ) && 'custom' === $post['meta_box'] ) {
688 $post['meta_box'] = 'default';
689 }
690
691 if ( ! empty( $post['ID'] ) ) {
692 $existing_post = $this->get_post( $post['ID'] );
693
694 if ( is_array( $existing_post ) ) {
695 $post['meta_box'] = ! empty( $existing_post['meta_box'] ) ? (string) $existing_post['meta_box'] : '';
696 $post['meta_box_cb'] = ! empty( $existing_post['meta_box_cb'] ) ? (string) $existing_post['meta_box_cb'] : '';
697 $post['meta_box_sanitize_cb'] = ! empty( $existing_post['meta_box_sanitize_cb'] ) ? (string) $existing_post['meta_box_sanitize_cb'] : '';
698 }
699 }
700 }
701
702 return parent::prepare_post_for_import( $post );
703 }
704
705 /**
706 * Imports a taxonomy from CPTUI.
707 *
708 * @since 6.1
709 *
710 * @param array $args Arguments from CPTUI.
711 * @return array
712 */
713 public function import_cptui_taxonomy( $args ) {
714 $acf_args = $this->get_settings_array();
715
716 // Convert string boolean values to proper booleans.
717 foreach ( $args as $key => $value ) {
718 if ( in_array( $value, array( 'true', 'false' ), true ) ) {
719 $args[ $key ] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
720 }
721 }
722
723 if ( isset( $args['name'] ) ) {
724 $acf_args['taxonomy'] = (string) $args['name'];
725 unset( $args['name'] );
726 }
727
728 if ( isset( $args['labels'] ) ) {
729 $acf_args['labels'] = array_merge( $acf_args['labels'], $args['labels'] );
730 unset( $args['labels'] );
731 }
732
733 // ACF uses "name" as title, and stores in labels array.
734 if ( isset( $args['label'] ) ) {
735 $acf_args['title'] = (string) $args['label'];
736 $acf_args['labels']['name'] = (string) $args['label'];
737 unset( $args['label'] );
738 }
739
740 if ( isset( $args['singular_label'] ) ) {
741 $acf_args['labels']['singular_name'] = (string) $args['singular_label'];
742 unset( $args['singular_label'] );
743 }
744
745 // ACF handles the meta_box_cb with two different settings.
746 if ( isset( $args['meta_box_cb'] ) ) {
747 if ( false === $args['meta_box_cb'] ) {
748 $acf_args['meta_box'] = 'disabled';
749 } elseif ( ! empty( $args['meta_box_cb'] ) ) {
750 $acf_args['meta_box'] = 'custom';
751 $acf_args['meta_box_cb'] = $args['meta_box_cb'];
752 }
753
754 unset( $args['meta_box_cb'] );
755 }
756
757 // ACF handles the query var and query var slug/name differently.
758 if ( isset( $args['query_var'] ) ) {
759 if ( ! $args['query_var'] ) {
760 $acf_args['query_var'] = 'none';
761 } elseif ( ! empty( $args['query_var_slug'] ) ) {
762 $acf_args['query_var'] = 'custom_query_var';
763 } else {
764 $acf_args['query_var'] = 'taxonomy_key';
765 }
766
767 unset( $args['query_var'] );
768 }
769
770 if ( isset( $args['query_var_slug'] ) ) {
771 $acf_args['query_var_name'] = (string) $args['query_var_slug'];
772 unset( $args['query_var_slug'] );
773 }
774
775 if ( isset( $args['rewrite'] ) ) {
776 $rewrite = (bool) $args['rewrite'];
777
778 if ( ! $rewrite ) {
779 $acf_args['rewrite']['permalink_rewrite'] = 'no_permalink';
780 } elseif ( ! empty( $args['rewrite_slug'] ) ) {
781 $acf_args['rewrite']['permalink_rewrite'] = 'custom_permalink';
782 } else {
783 $acf_args['rewrite']['permalink_rewrite'] = 'taxonomy_key';
784 }
785
786 unset( $args['rewrite'] );
787 }
788
789 if ( isset( $args['rewrite_slug'] ) ) {
790 $acf_args['rewrite']['slug'] = (string) $args['rewrite_slug'];
791 unset( $args['rewrite_slug'] );
792 }
793
794 if ( isset( $args['rewrite_withfront'] ) ) {
795 $acf_args['rewrite']['with_front'] = $args['rewrite_withfront'] === '1';
796 unset( $args['rewrite_withfront'] );
797 }
798
799 if ( isset( $args['rewrite_hierarchical'] ) ) {
800 $acf_args['rewrite']['rewrite_hierarchical'] = $args['rewrite_hierarchical'] === '1';
801 unset( $args['rewrite_hierarchical'] );
802 }
803
804 // CPTUI stores all default_term settings as comma separate values.
805 if ( isset( $args['default_term'] ) ) {
806 if ( '' !== $args['default_term'] ) {
807 $default_term = explode( ',', $args['default_term'] );
808 $default_term = array_map( 'trim', $default_term );
809
810 if ( isset( $default_term[0] ) ) {
811 $acf_args['default_term']['default_term_enabled'] = true;
812 $acf_args['default_term']['default_term_name'] = (string) $default_term[0];
813 }
814
815 if ( isset( $default_term[1] ) ) {
816 $acf_args['default_term']['default_term_slug'] = (string) $default_term[1];
817 }
818
819 if ( isset( $default_term[2] ) ) {
820 $acf_args['default_term']['default_term_description'] = (string) $default_term[2];
821 }
822 }
823
824 unset( $args['default_term'] );
825 }
826
827 if ( isset( $args['object_types'] ) ) {
828 $acf_args['object_type'] = $args['object_types'];
829 unset( $args['object_types'] );
830 }
831
832 $acf_args = wp_parse_args( $args, $acf_args );
833 $acf_args['key'] = uniqid( 'taxonomy_' );
834 $acf_args['advanced_configuration'] = true;
835 $acf_args['import_source'] = 'cptui';
836 $acf_args['import_date'] = time();
837
838 $existing_taxonomies = acf_get_acf_taxonomies();
839
840 foreach ( $existing_taxonomies as $existing_taxonomy ) {
841 // Taxonomy already exists, so we need to update rather than import.
842 if ( $acf_args['taxonomy'] === $existing_taxonomy['taxonomy'] ) {
843 $acf_args = $this->prepare_post_for_import( $acf_args );
844 $acf_args['ID'] = $existing_taxonomy['ID'];
845 $acf_args['key'] = $existing_taxonomy['key'];
846 return $this->update_post( $acf_args );
847 }
848 }
849
850 return $this->import_post( $acf_args );
851 }
852 }
853
854 }
855
856 acf_new_instance( 'ACF_Taxonomy' );
857