PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / trunk
Secure Custom Fields vtrunk
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 / src / AI / Abilities / Taxonomy.php
secure-custom-fields / src / AI / Abilities Last commit date
Abilities.php 2 months ago AbstractAbilityGroup.php 2 months ago FieldGroup.php 1 month ago PostType.php 2 months ago SCF_REST_Ability.php 2 months ago Taxonomy.php 1 month ago
Taxonomy.php
701 lines
1 <?php
2 /**
3 * ACF 6.8.0 feature port.
4 *
5 * @package wordpress/secure-custom-fields
6 */
7
8 // phpcs:disable -- Upstream ACF 6.8.0 feature-port files are kept close to source.
9
10 namespace SCF\AI\Abilities;
11
12 use WP_Error;
13
14 // Exit if accessed directly.
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * ACF Taxonomy Abilities
19 *
20 * Handles ACF custom taxonomy related abilities for the WordPress Abilities API.
21 */
22 class Taxonomy extends AbstractAbilityGroup {
23
24 /**
25 * Register taxonomy related abilities.
26 *
27 * @since 6.8.0
28 *
29 * @return void
30 */
31 public function register_abilities() {
32 if ( ! $this->is_abilities_api_available() ) {
33 return;
34 }
35
36 // Register ACF Custom Taxonomies resource.
37 $this->register_ability(
38 'acf/custom-taxonomies',
39 array(
40 'label' => __( 'SCF Custom Taxonomies', 'secure-custom-fields' ),
41 'description' => __( 'Get all SCF registered custom taxonomies', 'secure-custom-fields' ),
42 'category' => 'acf-field-management',
43 'input_schema' => array(
44 'type' => array( 'object', 'null' ),
45 'properties' => array(),
46 'additionalProperties' => false,
47 ),
48 'output_schema' => array(
49 'type' => 'object',
50 'properties' => array(
51 'custom_taxonomies' => array(
52 'type' => 'array',
53 'items' => array( 'type' => 'object' ),
54 ),
55 'count' => array( 'type' => 'integer' ),
56 'message' => array( 'type' => 'string' ),
57 ),
58 ),
59 'execute_callback' => array( $this, 'get_custom_taxonomies' ),
60 'permission_callback' => function () {
61 return current_user_can( acf_get_setting( 'capability' ) );
62 },
63 'meta' => array(
64 'annotations' => array(
65 'readonly' => true,
66 'destructive' => false,
67 'idempotent' => true,
68 ),
69 'show_in_rest' => true,
70 ),
71 )
72 );
73
74 // Register custom taxonomy ability.
75 $this->register_ability(
76 'acf/register-custom-taxonomy',
77 array(
78 'label' => __( 'Register Custom Taxonomy', 'secure-custom-fields' ),
79 'description' => __( 'Register a new taxonomy definition in WordPress (e.g., "Genre", "Color"). This creates the taxonomy schema itself, not individual terms within it. Use the create term abilities to add terms to an existing taxonomy.', 'secure-custom-fields' ),
80 'category' => 'acf-field-management',
81 'input_schema' => array(
82 'type' => 'object',
83 'properties' => array(
84 'taxonomy' => array(
85 'type' => 'string',
86 'pattern' => '^[a-z0-9_-]*$',
87 'maxLength' => 32,
88 'description' => 'The taxonomy key (slug)',
89 'required' => true,
90 ),
91 'label' => array(
92 'type' => 'string',
93 'description' => 'The singular label for the taxonomy',
94 'required' => true,
95 ),
96 'plural_label' => array(
97 'type' => 'string',
98 'description' => 'The plural label for the taxonomy',
99 'required' => true,
100 ),
101 'description' => array(
102 'type' => 'string',
103 'description' => 'Description of the taxonomy',
104 'required' => false,
105 ),
106 'public' => array(
107 'type' => 'boolean',
108 'description' => 'Whether the taxonomy is public',
109 'required' => false,
110 ),
111 'hierarchical' => array(
112 'type' => 'boolean',
113 'description' => 'Whether the taxonomy is hierarchical (like categories) or flat (like tags)',
114 'required' => false,
115 ),
116 'post_types' => array(
117 'type' => 'array',
118 'description' => 'Array of post types this taxonomy applies to',
119 'required' => false,
120 'items' => array(
121 'type' => 'string',
122 ),
123 ),
124 'show_in_rest' => array(
125 'type' => 'boolean',
126 'description' => 'Whether to show this taxonomy in the REST API (required for AI abilities)',
127 'required' => false,
128 ),
129 'rest_base' => array(
130 'type' => 'string',
131 'description' => 'Custom REST API base path (defaults to taxonomy key)',
132 'required' => false,
133 ),
134 'allow_ai_access' => array(
135 'type' => 'boolean',
136 'description' => 'Whether to allow AI access to this taxonomy',
137 'required' => false,
138 ),
139 'ai_description' => array(
140 'type' => 'string',
141 'description' => 'Description to help AI understand the purpose of this taxonomy',
142 'required' => false,
143 ),
144 'show_ui' => array(
145 'type' => 'boolean',
146 'description' => 'Whether to generate a default UI for managing this taxonomy in the admin',
147 'required' => false,
148 ),
149 'show_admin_column' => array(
150 'type' => 'boolean',
151 'description' => 'Whether to display a column for the taxonomy on its post type listing screens',
152 'required' => false,
153 ),
154 ),
155 ),
156 'output_schema' => array(
157 'type' => 'object',
158 'properties' => array(
159 'success' => array( 'type' => 'boolean' ),
160 'taxonomy' => array( 'type' => 'object' ),
161 'message' => array( 'type' => 'string' ),
162 ),
163 ),
164 'execute_callback' => array( $this, 'create_custom_taxonomy' ),
165 'permission_callback' => function () {
166 return current_user_can( acf_get_setting( 'capability' ) );
167 },
168 'meta' => array(
169 'annotations' => array(
170 'readonly' => false,
171 'destructive' => false,
172 'idempotent' => false,
173 ),
174 'show_in_rest' => true,
175 ),
176 )
177 );
178
179 // Register abilities for each ACF custom taxonomy that has REST API enabled.
180 $this->register_acf_taxonomy_term_abilities();
181 }
182
183 /**
184 * Register CRUD abilities for taxonomy terms.
185 *
186 * @since 6.8.0
187 *
188 * @return void
189 */
190 private function register_acf_taxonomy_term_abilities() {
191 $acf_taxonomies = acf_get_acf_taxonomies();
192
193 foreach ( $acf_taxonomies as $acf_taxonomy ) {
194 $taxonomy_name = $acf_taxonomy['taxonomy'] ?? '';
195 if ( ! $taxonomy_name ) {
196 continue;
197 }
198
199 // Check if AI access is enabled for this taxonomy.
200 if ( empty( $acf_taxonomy['allow_ai_access'] ) || empty( $acf_taxonomy['active'] ) ) {
201 continue;
202 }
203
204 // Sanitize taxonomy name for feature ID (convert underscores to hyphens, ensure lowercase)
205 $sanitized_taxonomy_name = str_replace( '_', '-', strtolower( $taxonomy_name ) );
206
207 // Skip if we can't retrieve the taxonomy object or if it isn't configured with REST API access.
208 $taxonomy_object = get_taxonomy( $taxonomy_name );
209 if ( ! $taxonomy_object || empty( $taxonomy_object->show_in_rest ) ) {
210 continue;
211 }
212
213 $rest_base = acf_get_object_type_rest_base( $taxonomy_object );
214 $taxonomy_label = $taxonomy_object->labels->singular_name ?? $taxonomy_name;
215 $taxonomy_label_plural = $taxonomy_object->labels->name ?? $taxonomy_name . 's';
216
217 // Get AI description for enhanced ability descriptions
218 $ai_description = $acf_taxonomy['ai_description'] ?? '';
219 $description_suffix = $ai_description ? ' ' . $ai_description : '';
220
221 // Get ACF fields for this taxonomy.
222 $acf_fields = $this->get_acf_fields_for_object( 'taxonomy', $taxonomy_name );
223
224 // Get schemas from REST controller.
225 $item_schema = $this->get_rest_item_output_schema( $acf_fields, $taxonomy_name );
226 $collection_schema = $this->get_rest_item_output_schema( $acf_fields, $taxonomy_name, 'collection' );
227
228 // Register query/list feature for this taxonomy
229 $this->register_ability(
230 'acf/' . $sanitized_taxonomy_name . 's',
231 array(
232 /* translators: %s The plural label for the custom taxonomy. */
233 'label' => sprintf( __( 'Query %s', 'secure-custom-fields' ), $taxonomy_label_plural ),
234 /* translators: %s The plural label for the custom taxonomy. */
235 'description' => sprintf( __( 'Get a list of %s terms that match the query parameters.', 'secure-custom-fields' ), strtolower( $taxonomy_label_plural ) ) . $description_suffix,
236 'category' => 'wordpress-content-discovery',
237 'input_schema' => array(
238 'type' => array( 'object', 'null' ),
239 'properties' => array(
240 'per_page' => array(
241 'type' => 'integer',
242 'default' => 10,
243 'minimum' => 1,
244 'maximum' => 100,
245 ),
246 'page' => array(
247 'type' => 'integer',
248 'default' => 1,
249 'minimum' => 1,
250 ),
251 'search' => array(
252 'type' => 'string',
253 'default' => '',
254 'description' => 'Search terms by name.',
255 ),
256 'post' => array(
257 'type' => 'integer',
258 'default' => null,
259 'description' => 'Search terms assigned to a specific post.',
260 ),
261 'slug' => array(
262 'type' => 'array',
263 'items' => array(
264 'type' => 'string',
265 ),
266 'description' => 'Search terms with specific slugs.',
267 ),
268 'parent' => array(
269 'type' => 'integer',
270 'description' => 'Filter by parent term ID for hierarchical taxonomies. Use 0 for top-level terms only.',
271 'required' => false,
272 ),
273 'orderby' => array(
274 'type' => 'string',
275 'enum' => array( 'id', 'name', 'slug', 'description', 'count' ),
276 'default' => 'name',
277 'description' => 'Sort collection by term attribute.',
278 ),
279 'order' => array(
280 'type' => 'string',
281 'enum' => array( 'asc', 'desc' ),
282 'default' => 'asc',
283 'description' => 'Order sort attribute ascending or descending.',
284 ),
285 'hide_empty' => array(
286 'type' => 'boolean',
287 'default' => false,
288 'description' => 'Whether to hide terms not assigned to any posts.',
289 ),
290 ),
291 'additionalProperties' => false,
292 ),
293 'output_schema' => $collection_schema,
294 'execute_callback' => function ( $input = array() ) use ( $rest_base ) {
295 return $this->execute_rest_request( 'GET', $rest_base, $input );
296 },
297 'permission_callback' => function () {
298 return current_user_can( 'read' );
299 },
300 'meta' => array(
301 'annotations' => array(
302 'readonly' => true,
303 'destructive' => false,
304 'idempotent' => true,
305 ),
306 'show_in_rest' => true,
307 ),
308 'ability_class' => self::REST_ABILITY_CLASS,
309 )
310 );
311
312 // Register create ability for this taxonomy
313 $this->register_ability(
314 'acf/create-' . $sanitized_taxonomy_name,
315 array(
316 /* translators: %s The singular label for the custom taxonomy. */
317 'label' => sprintf( __( 'Create %s Term', 'secure-custom-fields' ), $taxonomy_label ),
318 /* translators: %s The singular label for the custom taxonomy. */
319 'description' => sprintf( __( 'Create a new "%s" term.', 'secure-custom-fields' ), strtolower( $taxonomy_label ) ) . $description_suffix,
320 'category' => 'wordpress-content-discovery',
321 'input_schema' => $this->get_rest_item_input_schema( $acf_fields, $taxonomy_label, $taxonomy_object->hierarchical ),
322 'output_schema' => $item_schema,
323 'execute_callback' => function ( $input = array() ) use ( $rest_base ) {
324 return $this->execute_rest_request( 'POST', $rest_base, $input );
325 },
326 'permission_callback' => function () use ( $taxonomy_object ) {
327 return current_user_can( $taxonomy_object->cap->manage_terms );
328 },
329 'meta' => array(
330 'annotations' => array(
331 'readonly' => false,
332 'destructive' => false,
333 'idempotent' => false,
334 ),
335 'show_in_rest' => true,
336 ),
337 'ability_class' => self::REST_ABILITY_CLASS,
338 )
339 );
340
341 // Register view single ability for this taxonomy
342 $this->register_ability(
343 'acf/view-' . $sanitized_taxonomy_name,
344 array(
345 /* translators: %s The singular label for the custom taxonomy. */
346 'label' => sprintf( __( 'View a %s Term', 'secure-custom-fields' ), $taxonomy_label ),
347 /* translators: %s The singular label for the custom taxonomy. */
348 'description' => sprintf( __( 'Get a %s term by its ID.', 'secure-custom-fields' ), strtolower( $taxonomy_label ) ) . $description_suffix,
349 'category' => 'wordpress-content-discovery',
350 'input_schema' => array(
351 'type' => 'object',
352 'properties' => array(
353 'id' => array(
354 'type' => 'integer',
355 'description' => sprintf( 'The ID of the %s term to view.', strtolower( $taxonomy_label ) ),
356 'required' => true,
357 ),
358 ),
359 ),
360 'output_schema' => $item_schema,
361 'execute_callback' => function ( $input = array() ) use ( $rest_base ) {
362 $item_id = $input['id'] ?? null;
363 return $this->execute_rest_request( 'GET', $rest_base, $input, $item_id );
364 },
365 'permission_callback' => function () {
366 return current_user_can( 'read' );
367 },
368 'meta' => array(
369 'annotations' => array(
370 'readonly' => true,
371 'destructive' => false,
372 'idempotent' => true,
373 ),
374 'show_in_rest' => true,
375 ),
376 'ability_class' => self::REST_ABILITY_CLASS,
377 )
378 );
379
380 // Register update ability for this taxonomy
381 $this->register_ability(
382 'acf/update-' . $sanitized_taxonomy_name,
383 array(
384 /* translators: %s The singular label for the custom taxonomy. */
385 'label' => sprintf( __( 'Update a %s Term', 'secure-custom-fields' ), $taxonomy_label ),
386 /* translators: %s The singular label for the custom taxonomy. */
387 'description' => sprintf( __( 'Update a %s term by its ID.', 'secure-custom-fields' ), strtolower( $taxonomy_label ) ) . $description_suffix,
388 'category' => 'wordpress-content-discovery',
389 'input_schema' => $this->get_rest_item_input_schema( $acf_fields, $taxonomy_label, $taxonomy_object->hierarchical, 'update' ),
390 'output_schema' => $item_schema,
391 'execute_callback' => function ( $input = array() ) use ( $rest_base ) {
392 $item_id = $input['id'] ?? null;
393 return $this->execute_rest_request( 'PUT', $rest_base, $input, $item_id );
394 },
395 'permission_callback' => function () use ( $taxonomy_object ) {
396 return current_user_can( $taxonomy_object->cap->edit_terms );
397 },
398 'meta' => array(
399 'annotations' => array(
400 'readonly' => false,
401 'destructive' => false,
402 'idempotent' => true,
403 ),
404 'show_in_rest' => true,
405 ),
406 'ability_class' => self::REST_ABILITY_CLASS,
407 )
408 );
409
410 // Register delete ability for this taxonomy.
411 $this->register_ability(
412 'acf/delete-' . $sanitized_taxonomy_name,
413 array(
414 /* translators: %s The singular label for the custom taxonomy. */
415 'label' => sprintf( __( 'Delete a %s Term', 'secure-custom-fields' ), $taxonomy_label ),
416 /* translators: %s The singular label for the custom taxonomy. */
417 'description' => sprintf( __( 'Delete a %s term by its ID.', 'secure-custom-fields' ), strtolower( $taxonomy_label ) ) . $description_suffix,
418 'category' => 'wordpress-content-discovery',
419 'input_schema' => array(
420 'type' => 'object',
421 'properties' => array(
422 'id' => array(
423 'type' => 'integer',
424 'description' => sprintf( 'The ID of the %s term to delete.', strtolower( $taxonomy_label ) ),
425 'required' => true,
426 ),
427 'force' => array(
428 'type' => 'boolean',
429 'description' => 'Whether to permanently delete the term. Defaults to true, as terms cannot be trashed.',
430 'required' => false,
431 'default' => true,
432 ),
433 ),
434 ),
435 'output_schema' => $item_schema,
436 'execute_callback' => function ( $input = array() ) use ( $rest_base ) {
437 $item_id = $input['id'] ?? null;
438 return $this->execute_rest_request( 'DELETE', $rest_base, $input, $item_id );
439 },
440 'permission_callback' => function () use ( $taxonomy_object ) {
441 return current_user_can( $taxonomy_object->cap->delete_terms );
442 },
443 'meta' => array(
444 'annotations' => array(
445 'readonly' => false,
446 'destructive' => true,
447 'idempotent' => true,
448 ),
449 'show_in_rest' => true,
450 ),
451 'ability_class' => self::REST_ABILITY_CLASS,
452 )
453 );
454 }
455 }
456
457 /**
458 * Get REST input schema for taxonomy terms.
459 *
460 * @since 6.8.0
461 *
462 * @param array $acf_fields ACF fields for this taxonomy.
463 * @param string $taxonomy_label Taxonomy label for descriptions.
464 * @param boolean $hierarchical Whether taxonomy is hierarchical.
465 * @param string $action Action type ('create' or 'update').
466 * @return array
467 */
468 private function get_rest_item_input_schema( array $acf_fields, string $taxonomy_label, bool $hierarchical = false, string $action = 'create' ): array {
469 $schema = array(
470 'type' => 'object',
471 'properties' => array(),
472 );
473
474 if ( 'update' === $action ) {
475 $schema['properties']['id'] = array(
476 'type' => 'integer',
477 'description' => sprintf( 'The ID of the %s term to update.', strtolower( $taxonomy_label ) ),
478 'required' => true,
479 );
480 }
481
482 $schema['properties']['name'] = array(
483 'type' => 'string',
484 'description' => sprintf( 'The name of the %s term.', strtolower( $taxonomy_label ) ),
485 'required' => 'update' !== $action,
486 );
487
488 $schema['properties']['description'] = array(
489 'type' => 'string',
490 'description' => sprintf( 'The description of the %s term.', strtolower( $taxonomy_label ) ),
491 'required' => false,
492 );
493
494 $schema['properties']['slug'] = array(
495 'type' => 'string',
496 'description' => sprintf( 'The slug of the %s term (auto-generated from name if not provided).', strtolower( $taxonomy_label ) ),
497 'required' => false,
498 );
499
500 if ( $hierarchical ) {
501 $schema['properties']['parent'] = array(
502 'type' => 'integer',
503 'description' => 'Parent term ID for hierarchical taxonomies. Use 0 or omit for top-level terms. For child terms, provide the ID of the parent term.',
504 'required' => false,
505 'default' => 0,
506 );
507 }
508
509 return $this->add_acf_fields_to_schema( $schema, $acf_fields );
510 }
511
512 /**
513 * Get REST output schema for taxonomy terms.
514 *
515 * @since 6.8.0
516 *
517 * @param array $acf_fields ACF fields for this taxonomy.
518 * @param string $taxonomy Taxonomy name.
519 * @param string $type Schema type ('item' or 'collection').
520 * @return array|null
521 */
522 private function get_rest_item_output_schema( array $acf_fields, string $taxonomy, string $type = 'item' ) {
523 $taxonomy_object = get_taxonomy( $taxonomy );
524
525 if ( ! $taxonomy_object ) {
526 return null;
527 }
528
529 $controller = $taxonomy_object->get_rest_controller();
530 if ( ! $controller || ! method_exists( $controller, 'get_public_item_schema' ) ) {
531 return null;
532 }
533
534 $schema = $controller->get_public_item_schema();
535 $schema = $this->add_acf_fields_to_schema( $schema, $acf_fields );
536
537 if ( $type === 'collection' ) {
538 return array(
539 'type' => 'array',
540 'items' => $schema,
541 );
542 }
543
544 return $schema;
545 }
546
547 /**
548 * Callback for the "acf/get-custom-taxonomies" ability.
549 *
550 * @since 6.8.0
551 *
552 * @param array $input Input args (unused).
553 * @return array
554 */
555 public function get_custom_taxonomies( $input ) {
556 unset( $input ); // Not used, but required by interface.
557
558 $custom_taxonomies = array();
559
560 // Get ACF custom taxonomies.
561 $acf_taxonomies = acf_get_acf_taxonomies();
562
563 foreach ( $acf_taxonomies as $acf_taxonomy ) {
564 $taxonomy_name = $acf_taxonomy['taxonomy'] ?? '';
565 if ( ! $taxonomy_name ) {
566 continue;
567 }
568
569 if ( empty( $acf_taxonomy['active'] ) || empty( $acf_taxonomy['allow_ai_access'] ) ) {
570 continue;
571 }
572
573 $taxonomy_object = get_taxonomy( $taxonomy_name );
574 if ( $taxonomy_object ) {
575 $taxonomy_data = array(
576 'taxonomy' => $taxonomy_name,
577 'label' => $taxonomy_object->label,
578 'labels' => (array) $taxonomy_object->labels,
579 'description' => $taxonomy_object->description,
580 'public' => $taxonomy_object->public,
581 'hierarchical' => $taxonomy_object->hierarchical,
582 'object_type' => $taxonomy_object->object_type,
583 'acf_settings' => $acf_taxonomy,
584 );
585
586 // Add ACF field groups information
587 $acf_fields = $this->get_acf_fields_for_object( 'taxonomy', $taxonomy_name );
588 if ( ! empty( $acf_fields ) ) {
589 $taxonomy_data['acf_field_groups'] = $acf_fields;
590 }
591
592 $custom_taxonomies[] = $taxonomy_data;
593 }
594 }
595
596 $count = count( $custom_taxonomies );
597
598 return array(
599 'custom_taxonomies' => $custom_taxonomies,
600 'count' => $count,
601 'message' => sprintf(
602 /* translators: %d: Number of SCF custom taxonomies */
603 _n( 'Found %d SCF custom taxonomy', 'Found %d SCF custom taxonomies', $count, 'secure-custom-fields' ),
604 $count
605 ),
606 );
607 }
608
609 /**
610 * Callback for the "acf/register-custom-taxonomy" ability.
611 *
612 * @since 6.8.0
613 *
614 * @param array $input Input args.
615 * @return array|WP_Error
616 */
617 public function create_custom_taxonomy( $input ) {
618 // Required parameters
619 $taxonomy = sanitize_key( $input['taxonomy'] );
620 $label = sanitize_text_field( $input['label'] );
621 $plural_label = sanitize_text_field( $input['plural_label'] );
622
623 // Basic optional parameters
624 $description = sanitize_text_field( $input['description'] ?? '' );
625 $public = $input['public'] ?? true;
626 $hierarchical = $input['hierarchical'] ?? false;
627 $post_types = array_map( 'sanitize_key', $input['post_types'] ?? array( 'post' ) );
628
629 // REST API settings
630 $show_in_rest = $input['show_in_rest'] ?? true;
631 $rest_base = $input['rest_base'] ?? '';
632
633 // AI settings
634 $allow_ai_access = $input['allow_ai_access'] ?? true;
635 $ai_description = $input['ai_description'] ?? '';
636
637 // UI settings
638 $show_ui = $input['show_ui'] ?? true;
639 $show_admin_column = $input['show_admin_column'] ?? false;
640
641 // Check if taxonomy already exists.
642 if ( taxonomy_exists( $taxonomy ) ) {
643 return new WP_Error(
644 'taxonomy_exists',
645 __( 'A taxonomy with this key already exists', 'secure-custom-fields' ),
646 array( 'status' => 400 )
647 );
648 }
649
650 // Use ACF's method to create the taxonomy.
651 $taxonomy_data = array(
652 'key' => uniqid( 'taxonomy_' ),
653 'taxonomy' => $taxonomy,
654 'title' => $plural_label,
655 'labels' => wp_parse_args(
656 array(
657 'name' => $plural_label,
658 'singular_name' => $label,
659 ),
660 acf_get_internal_post_type_instance( 'acf-taxonomy' )->get_settings_array()['labels']
661 ),
662 'description' => $description,
663 'public' => $public ? 1 : 0,
664 'hierarchical' => $hierarchical ? 1 : 0,
665 'object_type' => $post_types,
666 'active' => 1,
667 // REST API settings
668 'show_in_rest' => $show_in_rest ? 1 : 0,
669 // AI settings
670 'allow_ai_access' => $allow_ai_access ? 1 : 0,
671 // UI settings
672 'show_ui' => $show_ui ? 1 : 0,
673 'show_admin_column' => $show_admin_column ? 1 : 0,
674 );
675
676 // Add optional settings only if provided
677 if ( ! empty( $rest_base ) ) {
678 $taxonomy_data['rest_base'] = sanitize_text_field( $rest_base );
679 }
680
681 if ( ! empty( $ai_description ) ) {
682 $taxonomy_data['ai_description'] = sanitize_text_field( $ai_description );
683 }
684
685 $result = acf_import_taxonomy( $taxonomy_data );
686
687 if ( empty( $result['ID'] ) || ! is_int( $result['ID'] ) || ! taxonomy_exists( $result['taxonomy'] ) ) {
688 return new WP_Error(
689 'taxonomy_creation_failed',
690 __( 'Failed to create the custom taxonomy', 'secure-custom-fields' )
691 );
692 }
693
694 return array(
695 'success' => true,
696 'taxonomy' => $result,
697 'message' => __( 'SCF custom taxonomy created successfully', 'secure-custom-fields' ),
698 );
699 }
700 }
701