class-wp-post-type.php (20820B)
1 <?php 2 /** 3 * Post API: WP_Post_Type class 4 * 5 * @package WordPress 6 * @subpackage Post 7 * @since 4.6.0 8 */ 9 10 /** 11 * Core class used for interacting with post types. 12 * 13 * @since 4.6.0 14 * 15 * @see register_post_type() 16 */ 17 final class WP_Post_Type { 18 /** 19 * Post type key. 20 * 21 * @since 4.6.0 22 * @var string $name 23 */ 24 public $name; 25 26 /** 27 * Name of the post type shown in the menu. Usually plural. 28 * 29 * @since 4.6.0 30 * @var string $label 31 */ 32 public $label; 33 34 /** 35 * Labels object for this post type. 36 * 37 * If not set, post labels are inherited for non-hierarchical types 38 * and page labels for hierarchical ones. 39 * 40 * @see get_post_type_labels() 41 * 42 * @since 4.6.0 43 * @var stdClass $labels 44 */ 45 public $labels; 46 47 /** 48 * A short descriptive summary of what the post type is. 49 * 50 * Default empty. 51 * 52 * @since 4.6.0 53 * @var string $description 54 */ 55 public $description = ''; 56 57 /** 58 * Whether a post type is intended for use publicly either via the admin interface or by front-end users. 59 * 60 * While the default settings of $exclude_from_search, $publicly_queryable, $show_ui, and $show_in_nav_menus 61 * are inherited from public, each does not rely on this relationship and controls a very specific intention. 62 * 63 * Default false. 64 * 65 * @since 4.6.0 66 * @var bool $public 67 */ 68 public $public = false; 69 70 /** 71 * Whether the post type is hierarchical (e.g. page). 72 * 73 * Default false. 74 * 75 * @since 4.6.0 76 * @var bool $hierarchical 77 */ 78 public $hierarchical = false; 79 80 /** 81 * Whether to exclude posts with this post type from front end search 82 * results. 83 * 84 * Default is the opposite value of $public. 85 * 86 * @since 4.6.0 87 * @var bool $exclude_from_search 88 */ 89 public $exclude_from_search = null; 90 91 /** 92 * Whether queries can be performed on the front end for the post type as part of `parse_request()`. 93 * 94 * Endpoints would include: 95 * - `?post_type={post_type_key}` 96 * - `?{post_type_key}={single_post_slug}` 97 * - `?{post_type_query_var}={single_post_slug}` 98 * 99 * Default is the value of $public. 100 * 101 * @since 4.6.0 102 * @var bool $publicly_queryable 103 */ 104 public $publicly_queryable = null; 105 106 /** 107 * Whether to generate and allow a UI for managing this post type in the admin. 108 * 109 * Default is the value of $public. 110 * 111 * @since 4.6.0 112 * @var bool $show_ui 113 */ 114 public $show_ui = null; 115 116 /** 117 * Where to show the post type in the admin menu. 118 * 119 * To work, $show_ui must be true. If true, the post type is shown in its own top level menu. If false, no menu is 120 * shown. If a string of an existing top level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post type 121 * will be placed as a sub-menu of that. 122 * 123 * Default is the value of $show_ui. 124 * 125 * @since 4.6.0 126 * @var bool|string $show_in_menu 127 */ 128 public $show_in_menu = null; 129 130 /** 131 * Makes this post type available for selection in navigation menus. 132 * 133 * Default is the value $public. 134 * 135 * @since 4.6.0 136 * @var bool $show_in_nav_menus 137 */ 138 public $show_in_nav_menus = null; 139 140 /** 141 * Makes this post type available via the admin bar. 142 * 143 * Default is the value of $show_in_menu. 144 * 145 * @since 4.6.0 146 * @var bool $show_in_admin_bar 147 */ 148 public $show_in_admin_bar = null; 149 150 /** 151 * The position in the menu order the post type should appear. 152 * 153 * To work, $show_in_menu must be true. Default null (at the bottom). 154 * 155 * @since 4.6.0 156 * @var int $menu_position 157 */ 158 public $menu_position = null; 159 160 /** 161 * The URL or reference to the icon to be used for this menu. 162 * 163 * Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme. 164 * This should begin with 'data:image/svg+xml;base64,'. Pass the name of a Dashicons helper class 165 * to use a font icon, e.g. 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty 166 * so an icon can be added via CSS. 167 * 168 * Defaults to use the posts icon. 169 * 170 * @since 4.6.0 171 * @var string $menu_icon 172 */ 173 public $menu_icon = null; 174 175 /** 176 * The string to use to build the read, edit, and delete capabilities. 177 * 178 * May be passed as an array to allow for alternative plurals when using 179 * this argument as a base to construct the capabilities, e.g. 180 * array( 'story', 'stories' ). Default 'post'. 181 * 182 * @since 4.6.0 183 * @var string $capability_type 184 */ 185 public $capability_type = 'post'; 186 187 /** 188 * Whether to use the internal default meta capability handling. 189 * 190 * Default false. 191 * 192 * @since 4.6.0 193 * @var bool $map_meta_cap 194 */ 195 public $map_meta_cap = false; 196 197 /** 198 * Provide a callback function that sets up the meta boxes for the edit form. 199 * 200 * Do `remove_meta_box()` and `add_meta_box()` calls in the callback. Default null. 201 * 202 * @since 4.6.0 203 * @var callable $register_meta_box_cb 204 */ 205 public $register_meta_box_cb = null; 206 207 /** 208 * An array of taxonomy identifiers that will be registered for the post type. 209 * 210 * Taxonomies can be registered later with `register_taxonomy()` or `register_taxonomy_for_object_type()`. 211 * 212 * Default empty array. 213 * 214 * @since 4.6.0 215 * @var array $taxonomies 216 */ 217 public $taxonomies = array(); 218 219 /** 220 * Whether there should be post type archives, or if a string, the archive slug to use. 221 * 222 * Will generate the proper rewrite rules if $rewrite is enabled. Default false. 223 * 224 * @since 4.6.0 225 * @var bool|string $has_archive 226 */ 227 public $has_archive = false; 228 229 /** 230 * Sets the query_var key for this post type. 231 * 232 * Defaults to $post_type key. If false, a post type cannot be loaded at `?{query_var}={post_slug}`. 233 * If specified as a string, the query `?{query_var_string}={post_slug}` will be valid. 234 * 235 * @since 4.6.0 236 * @var string|bool $query_var 237 */ 238 public $query_var; 239 240 /** 241 * Whether to allow this post type to be exported. 242 * 243 * Default true. 244 * 245 * @since 4.6.0 246 * @var bool $can_export 247 */ 248 public $can_export = true; 249 250 /** 251 * Whether to delete posts of this type when deleting a user. 252 * 253 * - If true, posts of this type belonging to the user will be moved to Trash when the user is deleted. 254 * - If false, posts of this type belonging to the user will *not* be trashed or deleted. 255 * - If not set (the default), posts are trashed if post type supports the 'author' feature. 256 * Otherwise posts are not trashed or deleted. 257 * 258 * Default null. 259 * 260 * @since 4.6.0 261 * @var bool $delete_with_user 262 */ 263 public $delete_with_user = null; 264 265 /** 266 * Array of blocks to use as the default initial state for an editor session. 267 * 268 * Each item should be an array containing block name and optional attributes. 269 * 270 * Default empty array. 271 * 272 * @link https://developer.wordpress.org/block-editor/developers/block-api/block-templates/ 273 * 274 * @since 5.0.0 275 * @var array $template 276 */ 277 public $template = array(); 278 279 /** 280 * Whether the block template should be locked if $template is set. 281 * 282 * - If set to 'all', the user is unable to insert new blocks, move existing blocks 283 * and delete blocks. 284 * - If set to 'insert', the user is able to move existing blocks but is unable to insert 285 * new blocks and delete blocks. 286 * 287 * Default false. 288 * 289 * @link https://developer.wordpress.org/block-editor/developers/block-api/block-templates/ 290 * 291 * @since 5.0.0 292 * @var string|false $template_lock 293 */ 294 public $template_lock = false; 295 296 /** 297 * Whether this post type is a native or "built-in" post_type. 298 * 299 * Default false. 300 * 301 * @since 4.6.0 302 * @var bool $_builtin 303 */ 304 public $_builtin = false; 305 306 /** 307 * URL segment to use for edit link of this post type. 308 * 309 * Default 'post.php?post=%d'. 310 * 311 * @since 4.6.0 312 * @var string $_edit_link 313 */ 314 public $_edit_link = 'post.php?post=%d'; 315 316 /** 317 * Post type capabilities. 318 * 319 * @since 4.6.0 320 * @var stdClass $cap 321 */ 322 public $cap; 323 324 /** 325 * Triggers the handling of rewrites for this post type. 326 * 327 * Defaults to true, using $post_type as slug. 328 * 329 * @since 4.6.0 330 * @var array|false $rewrite 331 */ 332 public $rewrite; 333 334 /** 335 * The features supported by the post type. 336 * 337 * @since 4.6.0 338 * @var array|bool $supports 339 */ 340 public $supports; 341 342 /** 343 * Whether this post type should appear in the REST API. 344 * 345 * Default false. If true, standard endpoints will be registered with 346 * respect to $rest_base and $rest_controller_class. 347 * 348 * @since 4.7.4 349 * @var bool $show_in_rest 350 */ 351 public $show_in_rest; 352 353 /** 354 * The base path for this post type's REST API endpoints. 355 * 356 * @since 4.7.4 357 * @var string|bool $rest_base 358 */ 359 public $rest_base; 360 361 /** 362 * The controller for this post type's REST API endpoints. 363 * 364 * Custom controllers must extend WP_REST_Controller. 365 * 366 * @since 4.7.4 367 * @var string|bool $rest_controller_class 368 */ 369 public $rest_controller_class; 370 371 /** 372 * The controller instance for this post type's REST API endpoints. 373 * 374 * Lazily computed. Should be accessed using {@see WP_Post_Type::get_rest_controller()}. 375 * 376 * @since 5.3.0 377 * @var WP_REST_Controller $rest_controller 378 */ 379 public $rest_controller; 380 381 /** 382 * Constructor. 383 * 384 * See the register_post_type() function for accepted arguments for `$args`. 385 * 386 * Will populate object properties from the provided arguments and assign other 387 * default properties based on that information. 388 * 389 * @since 4.6.0 390 * 391 * @see register_post_type() 392 * 393 * @param string $post_type Post type key. 394 * @param array|string $args Optional. Array or string of arguments for registering a post type. 395 * Default empty array. 396 */ 397 public function __construct( $post_type, $args = array() ) { 398 $this->name = $post_type; 399 400 $this->set_props( $args ); 401 } 402 403 /** 404 * Sets post type properties. 405 * 406 * See the register_post_type() function for accepted arguments for `$args`. 407 * 408 * @since 4.6.0 409 * 410 * @param array|string $args Array or string of arguments for registering a post type. 411 */ 412 public function set_props( $args ) { 413 $args = wp_parse_args( $args ); 414 415 /** 416 * Filters the arguments for registering a post type. 417 * 418 * @since 4.4.0 419 * 420 * @param array $args Array of arguments for registering a post type. 421 * See the register_post_type() function for accepted arguments. 422 * @param string $post_type Post type key. 423 */ 424 $args = apply_filters( 'register_post_type_args', $args, $this->name ); 425 426 $has_edit_link = ! empty( $args['_edit_link'] ); 427 428 // Args prefixed with an underscore are reserved for internal use. 429 $defaults = array( 430 'labels' => array(), 431 'description' => '', 432 'public' => false, 433 'hierarchical' => false, 434 'exclude_from_search' => null, 435 'publicly_queryable' => null, 436 'show_ui' => null, 437 'show_in_menu' => null, 438 'show_in_nav_menus' => null, 439 'show_in_admin_bar' => null, 440 'menu_position' => null, 441 'menu_icon' => null, 442 'capability_type' => 'post', 443 'capabilities' => array(), 444 'map_meta_cap' => null, 445 'supports' => array(), 446 'register_meta_box_cb' => null, 447 'taxonomies' => array(), 448 'has_archive' => false, 449 'rewrite' => true, 450 'query_var' => true, 451 'can_export' => true, 452 'delete_with_user' => null, 453 'show_in_rest' => false, 454 'rest_base' => false, 455 'rest_controller_class' => false, 456 'template' => array(), 457 'template_lock' => false, 458 '_builtin' => false, 459 '_edit_link' => 'post.php?post=%d', 460 ); 461 462 $args = array_merge( $defaults, $args ); 463 464 $args['name'] = $this->name; 465 466 // If not set, default to the setting for 'public'. 467 if ( null === $args['publicly_queryable'] ) { 468 $args['publicly_queryable'] = $args['public']; 469 } 470 471 // If not set, default to the setting for 'public'. 472 if ( null === $args['show_ui'] ) { 473 $args['show_ui'] = $args['public']; 474 } 475 476 // If not set, default to the setting for 'show_ui'. 477 if ( null === $args['show_in_menu'] || ! $args['show_ui'] ) { 478 $args['show_in_menu'] = $args['show_ui']; 479 } 480 481 // If not set, default to the setting for 'show_in_menu'. 482 if ( null === $args['show_in_admin_bar'] ) { 483 $args['show_in_admin_bar'] = (bool) $args['show_in_menu']; 484 } 485 486 // If not set, default to the setting for 'public'. 487 if ( null === $args['show_in_nav_menus'] ) { 488 $args['show_in_nav_menus'] = $args['public']; 489 } 490 491 // If not set, default to true if not public, false if public. 492 if ( null === $args['exclude_from_search'] ) { 493 $args['exclude_from_search'] = ! $args['public']; 494 } 495 496 // Back compat with quirky handling in version 3.0. #14122. 497 if ( empty( $args['capabilities'] ) 498 && null === $args['map_meta_cap'] && in_array( $args['capability_type'], array( 'post', 'page' ), true ) 499 ) { 500 $args['map_meta_cap'] = true; 501 } 502 503 // If not set, default to false. 504 if ( null === $args['map_meta_cap'] ) { 505 $args['map_meta_cap'] = false; 506 } 507 508 // If there's no specified edit link and no UI, remove the edit link. 509 if ( ! $args['show_ui'] && ! $has_edit_link ) { 510 $args['_edit_link'] = ''; 511 } 512 513 $this->cap = get_post_type_capabilities( (object) $args ); 514 unset( $args['capabilities'] ); 515 516 if ( is_array( $args['capability_type'] ) ) { 517 $args['capability_type'] = $args['capability_type'][0]; 518 } 519 520 if ( false !== $args['query_var'] ) { 521 if ( true === $args['query_var'] ) { 522 $args['query_var'] = $this->name; 523 } else { 524 $args['query_var'] = sanitize_title_with_dashes( $args['query_var'] ); 525 } 526 } 527 528 if ( false !== $args['rewrite'] && ( is_admin() || get_option( 'permalink_structure' ) ) ) { 529 if ( ! is_array( $args['rewrite'] ) ) { 530 $args['rewrite'] = array(); 531 } 532 if ( empty( $args['rewrite']['slug'] ) ) { 533 $args['rewrite']['slug'] = $this->name; 534 } 535 if ( ! isset( $args['rewrite']['with_front'] ) ) { 536 $args['rewrite']['with_front'] = true; 537 } 538 if ( ! isset( $args['rewrite']['pages'] ) ) { 539 $args['rewrite']['pages'] = true; 540 } 541 if ( ! isset( $args['rewrite']['feeds'] ) || ! $args['has_archive'] ) { 542 $args['rewrite']['feeds'] = (bool) $args['has_archive']; 543 } 544 if ( ! isset( $args['rewrite']['ep_mask'] ) ) { 545 if ( isset( $args['permalink_epmask'] ) ) { 546 $args['rewrite']['ep_mask'] = $args['permalink_epmask']; 547 } else { 548 $args['rewrite']['ep_mask'] = EP_PERMALINK; 549 } 550 } 551 } 552 553 foreach ( $args as $property_name => $property_value ) { 554 $this->$property_name = $property_value; 555 } 556 557 $this->labels = get_post_type_labels( $this ); 558 $this->label = $this->labels->name; 559 } 560 561 /** 562 * Sets the features support for the post type. 563 * 564 * @since 4.6.0 565 */ 566 public function add_supports() { 567 if ( ! empty( $this->supports ) ) { 568 foreach ( $this->supports as $feature => $args ) { 569 if ( is_array( $args ) ) { 570 add_post_type_support( $this->name, $feature, $args ); 571 } else { 572 add_post_type_support( $this->name, $args ); 573 } 574 } 575 unset( $this->supports ); 576 } elseif ( false !== $this->supports ) { 577 // Add default features. 578 add_post_type_support( $this->name, array( 'title', 'editor' ) ); 579 } 580 } 581 582 /** 583 * Adds the necessary rewrite rules for the post type. 584 * 585 * @since 4.6.0 586 * 587 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. 588 * @global WP $wp Current WordPress environment instance. 589 */ 590 public function add_rewrite_rules() { 591 global $wp_rewrite, $wp; 592 593 if ( false !== $this->query_var && $wp && is_post_type_viewable( $this ) ) { 594 $wp->add_query_var( $this->query_var ); 595 } 596 597 if ( false !== $this->rewrite && ( is_admin() || get_option( 'permalink_structure' ) ) ) { 598 if ( $this->hierarchical ) { 599 add_rewrite_tag( "%$this->name%", '(.+?)', $this->query_var ? "{$this->query_var}=" : "post_type=$this->name&pagename=" ); 600 } else { 601 add_rewrite_tag( "%$this->name%", '([^/]+)', $this->query_var ? "{$this->query_var}=" : "post_type=$this->name&name=" ); 602 } 603 604 if ( $this->has_archive ) { 605 $archive_slug = true === $this->has_archive ? $this->rewrite['slug'] : $this->has_archive; 606 if ( $this->rewrite['with_front'] ) { 607 $archive_slug = substr( $wp_rewrite->front, 1 ) . $archive_slug; 608 } else { 609 $archive_slug = $wp_rewrite->root . $archive_slug; 610 } 611 612 add_rewrite_rule( "{$archive_slug}/?$", "index.php?post_type=$this->name", 'top' ); 613 if ( $this->rewrite['feeds'] && $wp_rewrite->feeds ) { 614 $feeds = '(' . trim( implode( '|', $wp_rewrite->feeds ) ) . ')'; 615 add_rewrite_rule( "{$archive_slug}/feed/$feeds/?$", "index.php?post_type=$this->name" . '&feed=$matches[1]', 'top' ); 616 add_rewrite_rule( "{$archive_slug}/$feeds/?$", "index.php?post_type=$this->name" . '&feed=$matches[1]', 'top' ); 617 } 618 if ( $this->rewrite['pages'] ) { 619 add_rewrite_rule( "{$archive_slug}/{$wp_rewrite->pagination_base}/([0-9]{1,})/?$", "index.php?post_type=$this->name" . '&paged=$matches[1]', 'top' ); 620 } 621 } 622 623 $permastruct_args = $this->rewrite; 624 $permastruct_args['feed'] = $permastruct_args['feeds']; 625 add_permastruct( $this->name, "{$this->rewrite['slug']}/%$this->name%", $permastruct_args ); 626 } 627 } 628 629 /** 630 * Registers the post type meta box if a custom callback was specified. 631 * 632 * @since 4.6.0 633 */ 634 public function register_meta_boxes() { 635 if ( $this->register_meta_box_cb ) { 636 add_action( 'add_meta_boxes_' . $this->name, $this->register_meta_box_cb, 10, 1 ); 637 } 638 } 639 640 /** 641 * Adds the future post hook action for the post type. 642 * 643 * @since 4.6.0 644 */ 645 public function add_hooks() { 646 add_action( 'future_' . $this->name, '_future_post_hook', 5, 2 ); 647 } 648 649 /** 650 * Registers the taxonomies for the post type. 651 * 652 * @since 4.6.0 653 */ 654 public function register_taxonomies() { 655 foreach ( $this->taxonomies as $taxonomy ) { 656 register_taxonomy_for_object_type( $taxonomy, $this->name ); 657 } 658 } 659 660 /** 661 * Removes the features support for the post type. 662 * 663 * @since 4.6.0 664 * 665 * @global array $_wp_post_type_features Post type features. 666 */ 667 public function remove_supports() { 668 global $_wp_post_type_features; 669 670 unset( $_wp_post_type_features[ $this->name ] ); 671 } 672 673 /** 674 * Removes any rewrite rules, permastructs, and rules for the post type. 675 * 676 * @since 4.6.0 677 * 678 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. 679 * @global WP $wp Current WordPress environment instance. 680 * @global array $post_type_meta_caps Used to remove meta capabilities. 681 */ 682 public function remove_rewrite_rules() { 683 global $wp, $wp_rewrite, $post_type_meta_caps; 684 685 // Remove query var. 686 if ( false !== $this->query_var ) { 687 $wp->remove_query_var( $this->query_var ); 688 } 689 690 // Remove any rewrite rules, permastructs, and rules. 691 if ( false !== $this->rewrite ) { 692 remove_rewrite_tag( "%$this->name%" ); 693 remove_permastruct( $this->name ); 694 foreach ( $wp_rewrite->extra_rules_top as $regex => $query ) { 695 if ( false !== strpos( $query, "index.php?post_type=$this->name" ) ) { 696 unset( $wp_rewrite->extra_rules_top[ $regex ] ); 697 } 698 } 699 } 700 701 // Remove registered custom meta capabilities. 702 foreach ( $this->cap as $cap ) { 703 unset( $post_type_meta_caps[ $cap ] ); 704 } 705 } 706 707 /** 708 * Unregisters the post type meta box if a custom callback was specified. 709 * 710 * @since 4.6.0 711 */ 712 public function unregister_meta_boxes() { 713 if ( $this->register_meta_box_cb ) { 714 remove_action( 'add_meta_boxes_' . $this->name, $this->register_meta_box_cb, 10 ); 715 } 716 } 717 718 /** 719 * Removes the post type from all taxonomies. 720 * 721 * @since 4.6.0 722 */ 723 public function unregister_taxonomies() { 724 foreach ( get_object_taxonomies( $this->name ) as $taxonomy ) { 725 unregister_taxonomy_for_object_type( $taxonomy, $this->name ); 726 } 727 } 728 729 /** 730 * Removes the future post hook action for the post type. 731 * 732 * @since 4.6.0 733 */ 734 public function remove_hooks() { 735 remove_action( 'future_' . $this->name, '_future_post_hook', 5 ); 736 } 737 738 /** 739 * Gets the REST API controller for this post type. 740 * 741 * Will only instantiate the controller class once per request. 742 * 743 * @since 5.3.0 744 * 745 * @return WP_REST_Controller|null The controller instance, or null if the post type 746 * is set not to show in rest. 747 */ 748 public function get_rest_controller() { 749 if ( ! $this->show_in_rest ) { 750 return null; 751 } 752 753 $class = $this->rest_controller_class ? $this->rest_controller_class : WP_REST_Posts_Controller::class; 754 755 if ( ! class_exists( $class ) ) { 756 return null; 757 } 758 759 if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) { 760 return null; 761 } 762 763 if ( ! $this->rest_controller ) { 764 $this->rest_controller = new $class( $this->name ); 765 } 766 767 if ( ! ( $this->rest_controller instanceof $class ) ) { 768 return null; 769 } 770 771 return $this->rest_controller; 772 } 773 }