PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 5.0
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v5.0
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / third-party / content-model / acf / class-avcf-acf-content-models.php
atarim-visual-collaboration / third-party / content-model / acf Last commit date
class-avcf-acf-abilities.php 1 month ago class-avcf-acf-content-models.php 1 month ago class-avcf-acf-detector.php 1 month ago class-avcf-acf-field-groups.php 1 month ago class-avcf-acf-helpers.php 1 month ago
class-avcf-acf-content-models.php
429 lines
1 <?php
2 /**
3 * ACF — Content model MCP abilities (CPTs, taxonomies, options pages).
4 *
5 * ACF Pro 6.1+ can register custom post types and taxonomies, and 6.2+ can
6 * register UI options pages, storing them as its own "internal post types":
7 * acf-post-type, acf-taxonomy, acf-ui-options-page. All three share one CRUD
8 * surface (acf_get_internal_post_type_posts / acf_get_internal_post_type /
9 * acf_update_internal_post_type / acf_delete_internal_post_type), so this
10 * single class registers all 15 abilities through one shared dispatcher,
11 * varying only the labels, schemas, and payload shaping per kind.
12 *
13 * REQUIRES ACF Pro 6.5+ — every ability self-gates via the detector and
14 * returns a clear message when the internal-post-type APIs are unavailable.
15 * Only database-stored records are mutable; PHP/JSON-registered ones are
16 * reported read-only.
17 *
18 * Exposed (per kind = post-type | taxonomy | options-page):
19 * atarim/list-acf-post-types get-acf-post-type create/update/delete
20 * atarim/list-acf-taxonomies get-acf-taxonomy create/update/delete
21 * atarim/list-acf-options-pages get-acf-options-page create/update/delete
22 *
23 * @package atarim-visual-collaboration
24 */
25
26 if ( ! defined('ABSPATH') ) {
27 exit;
28 }
29
30 class AVCF_ACF_Content_Models extends AVCF_Abilities_Base {
31
32 /**
33 * @var AVCF_ACF_Detector
34 */
35 private $detector;
36
37 public function __construct() {
38 $this->detector = new AVCF_ACF_Detector();
39 }
40
41 public function register() {
42 if ( ! $this->detector->avcf_acf_is_available() ) {
43 return;
44 }
45
46 $this->register_post_types();
47 $this->register_taxonomies();
48 $this->register_options_pages();
49 }
50
51 /* ---------------------------------------------------------------------
52 * Shared internals
53 * ------------------------------------------------------------------- */
54
55 /**
56 * ACF internal post type slug for a kind, plus the key prefix ACF uses.
57 */
58 private function kind_meta( $kind ) {
59 switch ( $kind ) {
60 case 'post-type':
61 return [ 'pt' => 'acf-post-type', 'prefix' => 'post_type_', 'label' => 'post type' ];
62 case 'taxonomy':
63 return [ 'pt' => 'acf-taxonomy', 'prefix' => 'taxonomy_', 'label' => 'taxonomy' ];
64 case 'options-page':
65 return [ 'pt' => 'acf-ui-options-page', 'prefix' => 'ui_options_page_', 'label' => 'options page' ];
66 default:
67 return null;
68 }
69 }
70
71 /**
72 * Guard: ACF Pro 6.5+ internal-post-type APIs present.
73 *
74 * @return array|null Error response if unsupported, null if OK.
75 */
76 private function guard() {
77 if ( ! $this->detector->avcf_acf_supports_internal_post_types() ) {
78 return [
79 'success' => false,
80 'message' => 'Managing ACF post types, taxonomies, and options pages requires ACF Pro 6.5 or newer (the internal-post-type APIs are unavailable on this install).',
81 ];
82 }
83 return null;
84 }
85
86 private function generate_key( $kind ) {
87 $meta = $this->kind_meta( $kind );
88 return $meta['prefix'] . substr( md5( uniqid( (string) wp_rand(), true ) ), 0, 13 );
89 }
90
91 private function storage_mode( $record ) {
92 $local = isset( $record['local'] ) ? $record['local'] : null;
93 if ( $local === 'php' ) {
94 return 'php';
95 }
96 if ( $local === 'json' ) {
97 return 'json';
98 }
99 return 'db';
100 }
101
102 /**
103 * Resolve a key-or-slug to an internal-post-type key.
104 *
105 * @return string|null
106 */
107 private function resolve_key( $key_or_slug, $kind ) {
108 $meta = $this->kind_meta( $kind );
109 // Direct key hit.
110 $record = acf_get_internal_post_type( $key_or_slug, $meta['pt'] );
111 if ( $record ) {
112 return isset( $record['key'] ) ? (string) $record['key'] : (string) $key_or_slug;
113 }
114 // Fall back to matching by slug field.
115 $slug_field = ( $kind === 'taxonomy' ) ? 'taxonomy' : ( $kind === 'post-type' ? 'post_type' : 'menu_slug' );
116 foreach ( (array) acf_get_internal_post_type_posts( $meta['pt'] ) as $rec ) {
117 if ( isset( $rec[ $slug_field ] ) && (string) $rec[ $slug_field ] === (string) $key_or_slug ) {
118 return isset( $rec['key'] ) ? (string) $rec['key'] : null;
119 }
120 }
121 return null;
122 }
123
124 /**
125 * Shape an internal-post-type record for output.
126 */
127 private function shape( $record, $kind ) {
128 $out = [
129 'key' => isset( $record['key'] ) ? (string) $record['key'] : '',
130 'title' => isset( $record['title'] ) ? (string) $record['title'] : '',
131 'active' => isset( $record['active'] ) ? (bool) $record['active'] : true,
132 'storage' => $this->storage_mode( $record ),
133 'mutable' => $this->storage_mode( $record ) === 'db',
134 ];
135 if ( $kind === 'post-type' ) {
136 $out['post_type'] = isset( $record['post_type'] ) ? (string) $record['post_type'] : '';
137 $out['public'] = isset( $record['public'] ) ? (bool) $record['public'] : false;
138 $out['hierarchical'] = isset( $record['hierarchical'] ) ? (bool) $record['hierarchical'] : false;
139 } elseif ( $kind === 'taxonomy' ) {
140 $out['taxonomy'] = isset( $record['taxonomy'] ) ? (string) $record['taxonomy'] : '';
141 $out['object_type'] = isset( $record['object_type'] ) ? (array) $record['object_type'] : [];
142 $out['hierarchical'] = isset( $record['hierarchical'] ) ? (bool) $record['hierarchical'] : false;
143 } else {
144 $out['menu_slug'] = isset( $record['menu_slug'] ) ? (string) $record['menu_slug'] : '';
145 $out['page_title'] = isset( $record['page_title'] ) ? (string) $record['page_title'] : '';
146 $out['parent_slug'] = isset( $record['parent_slug'] ) ? (string) $record['parent_slug'] : '';
147 }
148 return $out;
149 }
150
151 /**
152 * Build the create/update payload from input for a given kind.
153 */
154 private function build_payload( $kind, $input, $base = [] ) {
155 $payload = is_array( $base ) ? $base : [];
156
157 // Generic passthrough for advanced/native args.
158 if ( isset( $input['advanced'] ) && is_array( $input['advanced'] ) ) {
159 $payload = array_merge( $payload, $input['advanced'] );
160 }
161 if ( isset( $input['active'] ) ) {
162 $payload['active'] = (bool) $input['active'];
163 }
164
165 if ( $kind === 'post-type' ) {
166 if ( isset( $input['post_type'] ) ) {
167 $payload['post_type'] = sanitize_key( (string) $input['post_type'] );
168 }
169 if ( isset( $input['plural_label'] ) ) {
170 $payload['title'] = (string) $input['plural_label'];
171 }
172 if ( isset( $input['singular_label'] ) ) {
173 $payload['labels']['singular_name'] = (string) $input['singular_label'];
174 }
175 if ( isset( $input['public'] ) ) {
176 $payload['public'] = (bool) $input['public'];
177 }
178 if ( isset( $input['hierarchical'] ) ) {
179 $payload['hierarchical'] = (bool) $input['hierarchical'];
180 }
181 } elseif ( $kind === 'taxonomy' ) {
182 if ( isset( $input['taxonomy'] ) ) {
183 $payload['taxonomy'] = sanitize_key( (string) $input['taxonomy'] );
184 }
185 if ( isset( $input['plural_label'] ) ) {
186 $payload['title'] = (string) $input['plural_label'];
187 }
188 if ( isset( $input['object_type'] ) && is_array( $input['object_type'] ) ) {
189 $payload['object_type'] = array_map( 'sanitize_key', $input['object_type'] );
190 }
191 if ( isset( $input['hierarchical'] ) ) {
192 $payload['hierarchical'] = (bool) $input['hierarchical'];
193 }
194 if ( isset( $input['public'] ) ) {
195 $payload['public'] = (bool) $input['public'];
196 }
197 } else { // options-page
198 if ( isset( $input['page_title'] ) ) {
199 $payload['page_title'] = (string) $input['page_title'];
200 $payload['title'] = (string) $input['page_title'];
201 }
202 if ( isset( $input['menu_slug'] ) ) {
203 $payload['menu_slug'] = sanitize_title( (string) $input['menu_slug'] );
204 }
205 if ( isset( $input['parent_slug'] ) ) {
206 $payload['parent_slug'] = (string) $input['parent_slug'];
207 }
208 if ( isset( $input['capability'] ) ) {
209 $payload['capability'] = (string) $input['capability'];
210 }
211 }
212
213 return $payload;
214 }
215
216 private function can() {
217 return current_user_can( 'manage_options' );
218 }
219
220 private function ro_meta() {
221 return [
222 'mcp' => [ 'public' => true, 'type' => 'tool' ],
223 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
224 ];
225 }
226
227 private function write_meta( $destructive = false ) {
228 return [
229 'mcp' => [ 'public' => true, 'type' => 'tool' ],
230 'annotations' => [ 'readonly' => false, 'destructive' => (bool) $destructive, 'idempotent' => false ],
231 ];
232 }
233
234 /**
235 * Register the five list/get/create/update/delete abilities for a kind.
236 *
237 * @param string $kind
238 * @param array $names ['list'=>..,'get'=>..,'create'=>..,'update'=>..,'delete'=>..]
239 * @param array $labels human labels keyed the same way
240 * @param array $create_props input_schema properties for create
241 * @param array $create_required
242 * @param array $update_props
243 */
244 private function register_kind( $kind, $names, $labels, $create_props, $create_required, $update_props ) {
245 $self = $this;
246 $meta = $this->kind_meta( $kind );
247 $klbl = $meta['label'];
248
249 // list
250 wp_register_ability( $names['list'], [
251 'label' => $labels['list'],
252 'description' => sprintf( 'List all ACF-registered %ss. Returns key, title, slug, active state, and storage mode (db / php / json; only db records are mutable). Requires ACF Pro 6.5+.', $klbl ),
253 'category' => 'atarim',
254 'input_schema' => [ 'type' => 'object', 'properties' => [], 'additionalProperties' => false ],
255 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'total' => [ 'type' => 'integer' ], 'items' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
256 'execute_callback' => function( $input = [] ) use ( $self, $kind, $meta ) {
257 $g = $self->guard(); if ( $g ) { return $g; }
258 $items = [];
259 foreach ( (array) acf_get_internal_post_type_posts( $meta['pt'] ) as $rec ) {
260 $items[] = $self->shape( $rec, $kind );
261 }
262 return [ 'success' => true, 'total' => count( $items ), 'items' => $items, 'message' => 'OK.' ];
263 },
264 'permission_callback' => function() use ( $self ) { return $self->can(); },
265 'meta' => $this->ro_meta(),
266 ] );
267
268 // get
269 wp_register_ability( $names['get'], [
270 'label' => $labels['get'],
271 'description' => sprintf( 'Get one ACF %s by key or slug, including its full raw ACF definition (use this as the template for update — pass changed fields back via "advanced"). Requires ACF Pro 6.5+.', $klbl ),
272 'category' => 'atarim',
273 'input_schema' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', 'description' => 'The record key or its slug.' ] ], 'required' => [ 'key' ], 'additionalProperties' => false ],
274 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'item' => [ 'type' => 'object' ], 'raw' => [ 'type' => 'object' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
275 'execute_callback' => function( $input = [] ) use ( $self, $kind, $meta ) {
276 $g = $self->guard(); if ( $g ) { return $g; }
277 $key = isset( $input['key'] ) ? (string) $input['key'] : '';
278 if ( $key === '' ) { return [ 'success' => false, 'message' => 'key is required.' ]; }
279 $resolved = $self->resolve_key( $key, $kind );
280 if ( ! $resolved ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
281 $record = acf_get_internal_post_type( $resolved, $meta['pt'] );
282 if ( ! $record ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
283 return [ 'success' => true, 'item' => $self->shape( $record, $kind ), 'raw' => $record, 'message' => 'OK.' ];
284 },
285 'permission_callback' => function() use ( $self ) { return $self->can(); },
286 'meta' => $this->ro_meta(),
287 ] );
288
289 // create
290 wp_register_ability( $names['create'], [
291 'label' => $labels['create'],
292 'description' => sprintf( 'Create a new ACF-registered %s (stored in the database). For settings beyond the common ones, pass an "advanced" object whose keys mirror the raw definition from get-acf-%s. Requires ACF Pro 6.5+.', $klbl, str_replace( ' ', '-', $klbl ) ),
293 'category' => 'atarim',
294 'input_schema' => [ 'type' => 'object', 'properties' => $create_props, 'required' => $create_required, 'additionalProperties' => false ],
295 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'key' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
296 'execute_callback' => function( $input = [] ) use ( $self, $kind, $meta ) {
297 $g = $self->guard(); if ( $g ) { return $g; }
298 $payload = $self->build_payload( $kind, $input, [ 'key' => $self->generate_key( $kind ), 'active' => isset( $input['active'] ) ? (bool) $input['active'] : true ] );
299 $result = acf_update_internal_post_type( $payload, $meta['pt'] );
300 if ( ! is_array( $result ) || empty( $result['key'] ) ) {
301 return [ 'success' => false, 'message' => sprintf( 'ACF reported no key after creating the %s.', $meta['label'] ) ];
302 }
303 if ( function_exists( 'flush_rewrite_rules' ) ) { flush_rewrite_rules( false ); }
304 return [ 'success' => true, 'key' => (string) $result['key'], 'message' => sprintf( 'Created %s.', $meta['label'] ) ];
305 },
306 'permission_callback' => function() use ( $self ) { return $self->can(); },
307 'meta' => $this->write_meta( false ),
308 ] );
309
310 // update
311 wp_register_ability( $names['update'], [
312 'label' => $labels['update'],
313 'description' => sprintf( 'Update an existing database-stored ACF %s. Supply the key plus only the properties to change; use "advanced" for raw-definition keys. Refuses on php/json-registered records. Requires ACF Pro 6.5+.', $klbl ),
314 'category' => 'atarim',
315 'input_schema' => [ 'type' => 'object', 'properties' => array_merge( [ 'key' => [ 'type' => 'string', 'description' => 'Record key or slug to update.' ] ], $update_props ), 'required' => [ 'key' ], 'additionalProperties' => false ],
316 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'key' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
317 'execute_callback' => function( $input = [] ) use ( $self, $kind, $meta ) {
318 $g = $self->guard(); if ( $g ) { return $g; }
319 $key = isset( $input['key'] ) ? (string) $input['key'] : '';
320 if ( $key === '' ) { return [ 'success' => false, 'message' => 'key is required.' ]; }
321 $resolved = $self->resolve_key( $key, $kind );
322 if ( ! $resolved ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
323 $existing = acf_get_internal_post_type( $resolved, $meta['pt'] );
324 if ( ! $existing ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
325 if ( $self->storage_mode( $existing ) !== 'db' ) {
326 return [ 'success' => false, 'message' => sprintf( '%s "%s" is registered via %s and is read-only.', ucfirst( $meta['label'] ), $key, $self->storage_mode( $existing ) ) ];
327 }
328 $payload = $self->build_payload( $kind, $input, $existing );
329 $result = acf_update_internal_post_type( $payload, $meta['pt'] );
330 if ( ! is_array( $result ) || empty( $result['key'] ) ) {
331 return [ 'success' => false, 'message' => sprintf( 'ACF reported no key after updating the %s.', $meta['label'] ) ];
332 }
333 if ( function_exists( 'flush_rewrite_rules' ) ) { flush_rewrite_rules( false ); }
334 return [ 'success' => true, 'key' => (string) $result['key'], 'message' => sprintf( 'Updated %s.', $meta['label'] ) ];
335 },
336 'permission_callback' => function() use ( $self ) { return $self->can(); },
337 'meta' => $this->write_meta( false ),
338 ] );
339
340 // delete
341 wp_register_ability( $names['delete'], [
342 'label' => $labels['delete'],
343 'description' => sprintf( 'Delete a database-stored ACF %s. This removes the registration only; existing content (posts/terms) is not deleted. Dry run unless confirm:true. Refuses on php/json-registered records. Requires ACF Pro 6.5+.', $klbl ),
344 'category' => 'atarim',
345 'input_schema' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', 'description' => 'Record key or slug to delete.' ], 'confirm' => [ 'type' => 'boolean', 'description' => 'Must be true to delete. Defaults to false (dry run).', 'default' => false ] ], 'required' => [ 'key' ], 'additionalProperties' => false ],
346 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'deleted' => [ 'type' => 'boolean' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
347 'execute_callback' => function( $input = [] ) use ( $self, $kind, $meta ) {
348 $g = $self->guard(); if ( $g ) { return $g; }
349 $key = isset( $input['key'] ) ? (string) $input['key'] : '';
350 if ( $key === '' ) { return [ 'success' => false, 'message' => 'key is required.' ]; }
351 $resolved = $self->resolve_key( $key, $kind );
352 if ( ! $resolved ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
353 $existing = acf_get_internal_post_type( $resolved, $meta['pt'] );
354 if ( ! $existing ) { return [ 'success' => false, 'message' => sprintf( '%s "%s" not found.', ucfirst( $meta['label'] ), $key ) ]; }
355 if ( $self->storage_mode( $existing ) !== 'db' ) {
356 return [ 'success' => false, 'message' => sprintf( '%s "%s" is registered via %s and is read-only.', ucfirst( $meta['label'] ), $key, $self->storage_mode( $existing ) ) ];
357 }
358 if ( empty( $input['confirm'] ) ) {
359 return [ 'success' => true, 'deleted' => false, 'message' => sprintf( 'Dry run: would delete %s "%s". Re-call with confirm:true.', $meta['label'], $resolved ) ];
360 }
361 $deleted = acf_delete_internal_post_type( $resolved, $meta['pt'] );
362 if ( $deleted && function_exists( 'flush_rewrite_rules' ) ) { flush_rewrite_rules( false ); }
363 return [ 'success' => (bool) $deleted, 'deleted' => (bool) $deleted, 'message' => $deleted ? sprintf( 'Deleted %s "%s".', $meta['label'], $resolved ) : 'ACF did not confirm deletion.' ];
364 },
365 'permission_callback' => function() use ( $self ) { return $self->can(); },
366 'meta' => $this->write_meta( true ),
367 ] );
368 }
369
370 /* ---------------------------------------------------------------------
371 * Per-kind registration (names + schemas)
372 * ------------------------------------------------------------------- */
373
374 private function register_post_types() {
375 $create_props = [
376 'post_type' => [ 'type' => 'string', 'description' => 'Post type slug (lowercase, max 20 chars, e.g. "team_member").' ],
377 'singular_label' => [ 'type' => 'string', 'description' => 'Singular label (e.g. "Team Member").' ],
378 'plural_label' => [ 'type' => 'string', 'description' => 'Plural label (e.g. "Team Members"). Used as the title.' ],
379 'public' => [ 'type' => 'boolean', 'description' => 'Public (queryable on the front end). Defaults to ACF default.' ],
380 'hierarchical' => [ 'type' => 'boolean', 'description' => 'Hierarchical (page-like) vs flat (post-like).' ],
381 'active' => [ 'type' => 'boolean', 'description' => 'Active. Defaults to true.' ],
382 'advanced' => [ 'type' => 'object', 'description' => 'Raw ACF post-type definition keys to merge (supports, taxonomies, rewrite, menu_icon, etc.). Mirror the shape from get-acf-post-type.' ],
383 ];
384 $update_props = $create_props;
385 $this->register_kind(
386 'post-type',
387 [ 'list' => 'atarim/list-acf-post-types', 'get' => 'atarim/get-acf-post-type', 'create' => 'atarim/create-acf-post-type', 'update' => 'atarim/update-acf-post-type', 'delete' => 'atarim/delete-acf-post-type' ],
388 [ 'list' => 'List ACF Post Types', 'get' => 'Get ACF Post Type', 'create' => 'Create ACF Post Type', 'update' => 'Update ACF Post Type', 'delete' => 'Delete ACF Post Type' ],
389 $create_props, [ 'post_type', 'plural_label' ], $update_props
390 );
391 }
392
393 private function register_taxonomies() {
394 $create_props = [
395 'taxonomy' => [ 'type' => 'string', 'description' => 'Taxonomy slug (lowercase, max 32 chars, e.g. "genre").' ],
396 'singular_label' => [ 'type' => 'string', 'description' => 'Singular label (e.g. "Genre").' ],
397 'plural_label' => [ 'type' => 'string', 'description' => 'Plural label (e.g. "Genres"). Used as the title.' ],
398 'object_type' => [ 'type' => 'array', 'description' => 'Post type slugs this taxonomy attaches to (e.g. ["post","team_member"]).' ],
399 'hierarchical' => [ 'type' => 'boolean', 'description' => 'Hierarchical (category-like) vs flat (tag-like).' ],
400 'public' => [ 'type' => 'boolean', 'description' => 'Public.' ],
401 'active' => [ 'type' => 'boolean', 'description' => 'Active. Defaults to true.' ],
402 'advanced' => [ 'type' => 'object', 'description' => 'Raw ACF taxonomy definition keys to merge. Mirror get-acf-taxonomy.' ],
403 ];
404 $this->register_kind(
405 'taxonomy',
406 [ 'list' => 'atarim/list-acf-taxonomies', 'get' => 'atarim/get-acf-taxonomy', 'create' => 'atarim/create-acf-taxonomy', 'update' => 'atarim/update-acf-taxonomy', 'delete' => 'atarim/delete-acf-taxonomy' ],
407 [ 'list' => 'List ACF Taxonomies', 'get' => 'Get ACF Taxonomy', 'create' => 'Create ACF Taxonomy', 'update' => 'Update ACF Taxonomy', 'delete' => 'Delete ACF Taxonomy' ],
408 $create_props, [ 'taxonomy', 'plural_label', 'object_type' ], $create_props
409 );
410 }
411
412 private function register_options_pages() {
413 $create_props = [
414 'page_title' => [ 'type' => 'string', 'description' => 'Options page title (e.g. "Theme Settings"). Used as the menu and page title.' ],
415 'menu_slug' => [ 'type' => 'string', 'description' => 'Menu slug. Auto-derived from the title if omitted.' ],
416 'parent_slug' => [ 'type' => 'string', 'description' => 'Parent menu slug to nest under (omit for a top-level page).' ],
417 'capability' => [ 'type' => 'string', 'description' => 'Capability required to view the page. Defaults to edit_posts.' ],
418 'active' => [ 'type' => 'boolean', 'description' => 'Active. Defaults to true.' ],
419 'advanced' => [ 'type' => 'object', 'description' => 'Raw ACF options-page definition keys to merge (icon_url, position, redirect, etc.). Mirror get-acf-options-page.' ],
420 ];
421 $this->register_kind(
422 'options-page',
423 [ 'list' => 'atarim/list-acf-options-pages', 'get' => 'atarim/get-acf-options-page', 'create' => 'atarim/create-acf-options-page', 'update' => 'atarim/update-acf-options-page', 'delete' => 'atarim/delete-acf-options-page' ],
424 [ 'list' => 'List ACF Options Pages', 'get' => 'Get ACF Options Page', 'create' => 'Create ACF Options Page', 'update' => 'Update ACF Options Page', 'delete' => 'Delete ACF Options Page' ],
425 $create_props, [ 'page_title' ], $create_props
426 );
427 }
428 }
429