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