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