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