[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> post.php (source)

   1  <?php
   2  /**
   3   * Core Post API
   4   *
   5   * @package WordPress
   6   * @subpackage Post
   7   */
   8  
   9  //
  10  // Post Type Registration
  11  //
  12  
  13  /**
  14   * Creates the initial post types when 'init' action is fired.
  15   *
  16   * See {@see 'init'}.
  17   *
  18   * @since 2.9.0
  19   */
  20  function create_initial_post_types() {
  21      register_post_type(
  22          'post',
  23          array(
  24              'labels'                => array(
  25                  'name_admin_bar' => _x( 'Post', 'add new from admin bar' ),
  26              ),
  27              'public'                => true,
  28              '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
  29              '_edit_link'            => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
  30              'capability_type'       => 'post',
  31              'map_meta_cap'          => true,
  32              'menu_position'         => 5,
  33              'hierarchical'          => false,
  34              'rewrite'               => false,
  35              'query_var'             => false,
  36              'delete_with_user'      => true,
  37              'supports'              => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
  38              'show_in_rest'          => true,
  39              'rest_base'             => 'posts',
  40              'rest_controller_class' => 'WP_REST_Posts_Controller',
  41          )
  42      );
  43  
  44      register_post_type(
  45          'page',
  46          array(
  47              'labels'                => array(
  48                  'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
  49              ),
  50              'public'                => true,
  51              'publicly_queryable'    => false,
  52              '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
  53              '_edit_link'            => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
  54              'capability_type'       => 'page',
  55              'map_meta_cap'          => true,
  56              'menu_position'         => 20,
  57              'hierarchical'          => true,
  58              'rewrite'               => false,
  59              'query_var'             => false,
  60              'delete_with_user'      => true,
  61              'supports'              => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
  62              'show_in_rest'          => true,
  63              'rest_base'             => 'pages',
  64              'rest_controller_class' => 'WP_REST_Posts_Controller',
  65          )
  66      );
  67  
  68      register_post_type(
  69          'attachment',
  70          array(
  71              'labels'                => array(
  72                  'name'           => _x( 'Media', 'post type general name' ),
  73                  'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
  74                  'add_new'        => _x( 'Add New', 'add new media' ),
  75                  'edit_item'      => __( 'Edit Media' ),
  76                  'view_item'      => __( 'View Attachment Page' ),
  77                  'attributes'     => __( 'Attachment Attributes' ),
  78              ),
  79              'public'                => true,
  80              'show_ui'               => true,
  81              '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
  82              '_edit_link'            => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
  83              'capability_type'       => 'post',
  84              'capabilities'          => array(
  85                  'create_posts' => 'upload_files',
  86              ),
  87              'map_meta_cap'          => true,
  88              'hierarchical'          => false,
  89              'rewrite'               => false,
  90              'query_var'             => false,
  91              'show_in_nav_menus'     => false,
  92              'delete_with_user'      => true,
  93              'supports'              => array( 'title', 'author', 'comments' ),
  94              'show_in_rest'          => true,
  95              'rest_base'             => 'media',
  96              'rest_controller_class' => 'WP_REST_Attachments_Controller',
  97          )
  98      );
  99      add_post_type_support( 'attachment:audio', 'thumbnail' );
 100      add_post_type_support( 'attachment:video', 'thumbnail' );
 101  
 102      register_post_type(
 103          'revision',
 104          array(
 105              'labels'           => array(
 106                  'name'          => __( 'Revisions' ),
 107                  'singular_name' => __( 'Revision' ),
 108              ),
 109              'public'           => false,
 110              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 111              '_edit_link'       => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
 112              'capability_type'  => 'post',
 113              'map_meta_cap'     => true,
 114              'hierarchical'     => false,
 115              'rewrite'          => false,
 116              'query_var'        => false,
 117              'can_export'       => false,
 118              'delete_with_user' => true,
 119              'supports'         => array( 'author' ),
 120          )
 121      );
 122  
 123      register_post_type(
 124          'nav_menu_item',
 125          array(
 126              'labels'           => array(
 127                  'name'          => __( 'Navigation Menu Items' ),
 128                  'singular_name' => __( 'Navigation Menu Item' ),
 129              ),
 130              'public'           => false,
 131              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 132              'hierarchical'     => false,
 133              'rewrite'          => false,
 134              'delete_with_user' => false,
 135              'query_var'        => false,
 136          )
 137      );
 138  
 139      register_post_type(
 140          'custom_css',
 141          array(
 142              'labels'           => array(
 143                  'name'          => __( 'Custom CSS' ),
 144                  'singular_name' => __( 'Custom CSS' ),
 145              ),
 146              'public'           => false,
 147              'hierarchical'     => false,
 148              'rewrite'          => false,
 149              'query_var'        => false,
 150              'delete_with_user' => false,
 151              'can_export'       => true,
 152              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 153              'supports'         => array( 'title', 'revisions' ),
 154              'capabilities'     => array(
 155                  'delete_posts'           => 'edit_theme_options',
 156                  'delete_post'            => 'edit_theme_options',
 157                  'delete_published_posts' => 'edit_theme_options',
 158                  'delete_private_posts'   => 'edit_theme_options',
 159                  'delete_others_posts'    => 'edit_theme_options',
 160                  'edit_post'              => 'edit_css',
 161                  'edit_posts'             => 'edit_css',
 162                  'edit_others_posts'      => 'edit_css',
 163                  'edit_published_posts'   => 'edit_css',
 164                  'read_post'              => 'read',
 165                  'read_private_posts'     => 'read',
 166                  'publish_posts'          => 'edit_theme_options',
 167              ),
 168          )
 169      );
 170  
 171      register_post_type(
 172          'customize_changeset',
 173          array(
 174              'labels'           => array(
 175                  'name'               => _x( 'Changesets', 'post type general name' ),
 176                  'singular_name'      => _x( 'Changeset', 'post type singular name' ),
 177                  'menu_name'          => _x( 'Changesets', 'admin menu' ),
 178                  'name_admin_bar'     => _x( 'Changeset', 'add new on admin bar' ),
 179                  'add_new'            => _x( 'Add New', 'Customize Changeset' ),
 180                  'add_new_item'       => __( 'Add New Changeset' ),
 181                  'new_item'           => __( 'New Changeset' ),
 182                  'edit_item'          => __( 'Edit Changeset' ),
 183                  'view_item'          => __( 'View Changeset' ),
 184                  'all_items'          => __( 'All Changesets' ),
 185                  'search_items'       => __( 'Search Changesets' ),
 186                  'not_found'          => __( 'No changesets found.' ),
 187                  'not_found_in_trash' => __( 'No changesets found in Trash.' ),
 188              ),
 189              'public'           => false,
 190              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 191              'map_meta_cap'     => true,
 192              'hierarchical'     => false,
 193              'rewrite'          => false,
 194              'query_var'        => false,
 195              'can_export'       => false,
 196              'delete_with_user' => false,
 197              'supports'         => array( 'title', 'author' ),
 198              'capability_type'  => 'customize_changeset',
 199              'capabilities'     => array(
 200                  'create_posts'           => 'customize',
 201                  'delete_others_posts'    => 'customize',
 202                  'delete_post'            => 'customize',
 203                  'delete_posts'           => 'customize',
 204                  'delete_private_posts'   => 'customize',
 205                  'delete_published_posts' => 'customize',
 206                  'edit_others_posts'      => 'customize',
 207                  'edit_post'              => 'customize',
 208                  'edit_posts'             => 'customize',
 209                  'edit_private_posts'     => 'customize',
 210                  'edit_published_posts'   => 'do_not_allow',
 211                  'publish_posts'          => 'customize',
 212                  'read'                   => 'read',
 213                  'read_post'              => 'customize',
 214                  'read_private_posts'     => 'customize',
 215              ),
 216          )
 217      );
 218  
 219      register_post_type(
 220          'oembed_cache',
 221          array(
 222              'labels'           => array(
 223                  'name'          => __( 'oEmbed Responses' ),
 224                  'singular_name' => __( 'oEmbed Response' ),
 225              ),
 226              'public'           => false,
 227              'hierarchical'     => false,
 228              'rewrite'          => false,
 229              'query_var'        => false,
 230              'delete_with_user' => false,
 231              'can_export'       => false,
 232              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 233              'supports'         => array(),
 234          )
 235      );
 236  
 237      register_post_type(
 238          'user_request',
 239          array(
 240              'labels'           => array(
 241                  'name'          => __( 'User Requests' ),
 242                  'singular_name' => __( 'User Request' ),
 243              ),
 244              'public'           => false,
 245              '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
 246              'hierarchical'     => false,
 247              'rewrite'          => false,
 248              'query_var'        => false,
 249              'can_export'       => false,
 250              'delete_with_user' => false,
 251              'supports'         => array(),
 252          )
 253      );
 254  
 255      register_post_type(
 256          'wp_block',
 257          array(
 258              'labels'                => array(
 259                  'name'                     => _x( 'Blocks', 'post type general name' ),
 260                  'singular_name'            => _x( 'Block', 'post type singular name' ),
 261                  'menu_name'                => _x( 'Blocks', 'admin menu' ),
 262                  'name_admin_bar'           => _x( 'Block', 'add new on admin bar' ),
 263                  'add_new'                  => _x( 'Add New', 'Block' ),
 264                  'add_new_item'             => __( 'Add New Block' ),
 265                  'new_item'                 => __( 'New Block' ),
 266                  'edit_item'                => __( 'Edit Block' ),
 267                  'view_item'                => __( 'View Block' ),
 268                  'all_items'                => __( 'All Blocks' ),
 269                  'search_items'             => __( 'Search Blocks' ),
 270                  'not_found'                => __( 'No blocks found.' ),
 271                  'not_found_in_trash'       => __( 'No blocks found in Trash.' ),
 272                  'filter_items_list'        => __( 'Filter blocks list' ),
 273                  'items_list_navigation'    => __( 'Blocks list navigation' ),
 274                  'items_list'               => __( 'Blocks list' ),
 275                  'item_published'           => __( 'Block published.' ),
 276                  'item_published_privately' => __( 'Block published privately.' ),
 277                  'item_reverted_to_draft'   => __( 'Block reverted to draft.' ),
 278                  'item_scheduled'           => __( 'Block scheduled.' ),
 279                  'item_updated'             => __( 'Block updated.' ),
 280              ),
 281              'public'                => false,
 282              '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
 283              'show_ui'               => true,
 284              'show_in_menu'          => false,
 285              'rewrite'               => false,
 286              'show_in_rest'          => true,
 287              'rest_base'             => 'blocks',
 288              'rest_controller_class' => 'WP_REST_Blocks_Controller',
 289              'capability_type'       => 'block',
 290              'capabilities'          => array(
 291                  // You need to be able to edit posts, in order to read blocks in their raw form.
 292                  'read'                   => 'edit_posts',
 293                  // You need to be able to publish posts, in order to create blocks.
 294                  'create_posts'           => 'publish_posts',
 295                  'edit_posts'             => 'edit_posts',
 296                  'edit_published_posts'   => 'edit_published_posts',
 297                  'delete_published_posts' => 'delete_published_posts',
 298                  'edit_others_posts'      => 'edit_others_posts',
 299                  'delete_others_posts'    => 'delete_others_posts',
 300              ),
 301              'map_meta_cap'          => true,
 302              'supports'              => array(
 303                  'title',
 304                  'editor',
 305              ),
 306          )
 307      );
 308  
 309      register_post_status(
 310          'publish',
 311          array(
 312              'label'       => _x( 'Published', 'post status' ),
 313              'public'      => true,
 314              '_builtin'    => true, /* internal use only. */
 315              /* translators: %s: Number of published posts. */
 316              'label_count' => _n_noop(
 317                  'Published <span class="count">(%s)</span>',
 318                  'Published <span class="count">(%s)</span>'
 319              ),
 320          )
 321      );
 322  
 323      register_post_status(
 324          'future',
 325          array(
 326              'label'       => _x( 'Scheduled', 'post status' ),
 327              'protected'   => true,
 328              '_builtin'    => true, /* internal use only. */
 329              /* translators: %s: Number of scheduled posts. */
 330              'label_count' => _n_noop(
 331                  'Scheduled <span class="count">(%s)</span>',
 332                  'Scheduled <span class="count">(%s)</span>'
 333              ),
 334          )
 335      );
 336  
 337      register_post_status(
 338          'draft',
 339          array(
 340              'label'       => _x( 'Draft', 'post status' ),
 341              'protected'   => true,
 342              '_builtin'    => true, /* internal use only. */
 343              /* translators: %s: Number of draft posts. */
 344              'label_count' => _n_noop(
 345                  'Draft <span class="count">(%s)</span>',
 346                  'Drafts <span class="count">(%s)</span>'
 347              ),
 348          )
 349      );
 350  
 351      register_post_status(
 352          'pending',
 353          array(
 354              'label'       => _x( 'Pending', 'post status' ),
 355              'protected'   => true,
 356              '_builtin'    => true, /* internal use only. */
 357              /* translators: %s: Number of pending posts. */
 358              'label_count' => _n_noop(
 359                  'Pending <span class="count">(%s)</span>',
 360                  'Pending <span class="count">(%s)</span>'
 361              ),
 362          )
 363      );
 364  
 365      register_post_status(
 366          'private',
 367          array(
 368              'label'       => _x( 'Private', 'post status' ),
 369              'private'     => true,
 370              '_builtin'    => true, /* internal use only. */
 371              /* translators: %s: Number of private posts. */
 372              'label_count' => _n_noop(
 373                  'Private <span class="count">(%s)</span>',
 374                  'Private <span class="count">(%s)</span>'
 375              ),
 376          )
 377      );
 378  
 379      register_post_status(
 380          'trash',
 381          array(
 382              'label'                     => _x( 'Trash', 'post status' ),
 383              'internal'                  => true,
 384              '_builtin'                  => true, /* internal use only. */
 385              /* translators: %s: Number of trashed posts. */
 386              'label_count'               => _n_noop(
 387                  'Trash <span class="count">(%s)</span>',
 388                  'Trash <span class="count">(%s)</span>'
 389              ),
 390              'show_in_admin_status_list' => true,
 391          )
 392      );
 393  
 394      register_post_status(
 395          'auto-draft',
 396          array(
 397              'label'    => 'auto-draft',
 398              'internal' => true,
 399              '_builtin' => true, /* internal use only. */
 400          )
 401      );
 402  
 403      register_post_status(
 404          'inherit',
 405          array(
 406              'label'               => 'inherit',
 407              'internal'            => true,
 408              '_builtin'            => true, /* internal use only. */
 409              'exclude_from_search' => false,
 410          )
 411      );
 412  
 413      register_post_status(
 414          'request-pending',
 415          array(
 416              'label'               => _x( 'Pending', 'request status' ),
 417              'internal'            => true,
 418              '_builtin'            => true, /* internal use only. */
 419              /* translators: %s: Number of pending requests. */
 420              'label_count'         => _n_noop(
 421                  'Pending <span class="count">(%s)</span>',
 422                  'Pending <span class="count">(%s)</span>'
 423              ),
 424              'exclude_from_search' => false,
 425          )
 426      );
 427  
 428      register_post_status(
 429          'request-confirmed',
 430          array(
 431              'label'               => _x( 'Confirmed', 'request status' ),
 432              'internal'            => true,
 433              '_builtin'            => true, /* internal use only. */
 434              /* translators: %s: Number of confirmed requests. */
 435              'label_count'         => _n_noop(
 436                  'Confirmed <span class="count">(%s)</span>',
 437                  'Confirmed <span class="count">(%s)</span>'
 438              ),
 439              'exclude_from_search' => false,
 440          )
 441      );
 442  
 443      register_post_status(
 444          'request-failed',
 445          array(
 446              'label'               => _x( 'Failed', 'request status' ),
 447              'internal'            => true,
 448              '_builtin'            => true, /* internal use only. */
 449              /* translators: %s: Number of failed requests. */
 450              'label_count'         => _n_noop(
 451                  'Failed <span class="count">(%s)</span>',
 452                  'Failed <span class="count">(%s)</span>'
 453              ),
 454              'exclude_from_search' => false,
 455          )
 456      );
 457  
 458      register_post_status(
 459          'request-completed',
 460          array(
 461              'label'               => _x( 'Completed', 'request status' ),
 462              'internal'            => true,
 463              '_builtin'            => true, /* internal use only. */
 464              /* translators: %s: Number of completed requests. */
 465              'label_count'         => _n_noop(
 466                  'Completed <span class="count">(%s)</span>',
 467                  'Completed <span class="count">(%s)</span>'
 468              ),
 469              'exclude_from_search' => false,
 470          )
 471      );
 472  }
 473  
 474  /**
 475   * Retrieve attached file path based on attachment ID.
 476   *
 477   * By default the path will go through the 'get_attached_file' filter, but
 478   * passing a true to the $unfiltered argument of get_attached_file() will
 479   * return the file path unfiltered.
 480   *
 481   * The function works by getting the single post meta name, named
 482   * '_wp_attached_file' and returning it. This is a convenience function to
 483   * prevent looking up the meta name and provide a mechanism for sending the
 484   * attached filename through a filter.
 485   *
 486   * @since 2.0.0
 487   *
 488   * @param int  $attachment_id Attachment ID.
 489   * @param bool $unfiltered    Optional. Whether to apply filters. Default false.
 490   * @return string|false The file path to where the attached file should be, false otherwise.
 491   */
 492  function get_attached_file( $attachment_id, $unfiltered = false ) {
 493      $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
 494  
 495      // If the file is relative, prepend upload dir.
 496      if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) ) {
 497          $uploads = wp_get_upload_dir();
 498          if ( false === $uploads['error'] ) {
 499              $file = $uploads['basedir'] . "/$file";
 500          }
 501      }
 502  
 503      if ( $unfiltered ) {
 504          return $file;
 505      }
 506  
 507      /**
 508       * Filters the attached file based on the given ID.
 509       *
 510       * @since 2.1.0
 511       *
 512       * @param string $file          Path to attached file.
 513       * @param int    $attachment_id Attachment ID.
 514       */
 515      return apply_filters( 'get_attached_file', $file, $attachment_id );
 516  }
 517  
 518  /**
 519   * Update attachment file path based on attachment ID.
 520   *
 521   * Used to update the file path of the attachment, which uses post meta name
 522   * '_wp_attached_file' to store the path of the attachment.
 523   *
 524   * @since 2.1.0
 525   *
 526   * @param int    $attachment_id Attachment ID.
 527   * @param string $file          File path for the attachment.
 528   * @return bool True on success, false on failure.
 529   */
 530  function update_attached_file( $attachment_id, $file ) {
 531      if ( ! get_post( $attachment_id ) ) {
 532          return false;
 533      }
 534  
 535      /**
 536       * Filters the path to the attached file to update.
 537       *
 538       * @since 2.1.0
 539       *
 540       * @param string $file          Path to the attached file to update.
 541       * @param int    $attachment_id Attachment ID.
 542       */
 543      $file = apply_filters( 'update_attached_file', $file, $attachment_id );
 544  
 545      $file = _wp_relative_upload_path( $file );
 546      if ( $file ) {
 547          return update_post_meta( $attachment_id, '_wp_attached_file', $file );
 548      } else {
 549          return delete_post_meta( $attachment_id, '_wp_attached_file' );
 550      }
 551  }
 552  
 553  /**
 554   * Return relative path to an uploaded file.
 555   *
 556   * The path is relative to the current upload dir.
 557   *
 558   * @since 2.9.0
 559   * @access private
 560   *
 561   * @param string $path Full path to the file.
 562   * @return string Relative path on success, unchanged path on failure.
 563   */
 564  function _wp_relative_upload_path( $path ) {
 565      $new_path = $path;
 566  
 567      $uploads = wp_get_upload_dir();
 568      if ( 0 === strpos( $new_path, $uploads['basedir'] ) ) {
 569              $new_path = str_replace( $uploads['basedir'], '', $new_path );
 570              $new_path = ltrim( $new_path, '/' );
 571      }
 572  
 573      /**
 574       * Filters the relative path to an uploaded file.
 575       *
 576       * @since 2.9.0
 577       *
 578       * @param string $new_path Relative path to the file.
 579       * @param string $path     Full path to the file.
 580       */
 581      return apply_filters( '_wp_relative_upload_path', $new_path, $path );
 582  }
 583  
 584  /**
 585   * Retrieve all children of the post parent ID.
 586   *
 587   * Normally, without any enhancements, the children would apply to pages. In the
 588   * context of the inner workings of WordPress, pages, posts, and attachments
 589   * share the same table, so therefore the functionality could apply to any one
 590   * of them. It is then noted that while this function does not work on posts, it
 591   * does not mean that it won't work on posts. It is recommended that you know
 592   * what context you wish to retrieve the children of.
 593   *
 594   * Attachments may also be made the child of a post, so if that is an accurate
 595   * statement (which needs to be verified), it would then be possible to get
 596   * all of the attachments for a post. Attachments have since changed since
 597   * version 2.5, so this is most likely inaccurate, but serves generally as an
 598   * example of what is possible.
 599   *
 600   * The arguments listed as defaults are for this function and also of the
 601   * get_posts() function. The arguments are combined with the get_children defaults
 602   * and are then passed to the get_posts() function, which accepts additional arguments.
 603   * You can replace the defaults in this function, listed below and the additional
 604   * arguments listed in the get_posts() function.
 605   *
 606   * The 'post_parent' is the most important argument and important attention
 607   * needs to be paid to the $args parameter. If you pass either an object or an
 608   * integer (number), then just the 'post_parent' is grabbed and everything else
 609   * is lost. If you don't specify any arguments, then it is assumed that you are
 610   * in The Loop and the post parent will be grabbed for from the current post.
 611   *
 612   * The 'post_parent' argument is the ID to get the children. The 'numberposts'
 613   * is the amount of posts to retrieve that has a default of '-1', which is
 614   * used to get all of the posts. Giving a number higher than 0 will only
 615   * retrieve that amount of posts.
 616   *
 617   * The 'post_type' and 'post_status' arguments can be used to choose what
 618   * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
 619   * post types are 'post', 'pages', and 'attachments'. The 'post_status'
 620   * argument will accept any post status within the write administration panels.
 621   *
 622   * @since 2.0.0
 623   *
 624   * @see get_posts()
 625   * @todo Check validity of description.
 626   *
 627   * @global WP_Post $post Global post object.
 628   *
 629   * @param mixed  $args   Optional. User defined arguments for replacing the defaults. Default empty.
 630   * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
 631   *                       a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
 632   * @return array Array of children, where the type of each element is determined by $output parameter.
 633   *               Empty array on failure.
 634   */
 635  function get_children( $args = '', $output = OBJECT ) {
 636      $kids = array();
 637      if ( empty( $args ) ) {
 638          if ( isset( $GLOBALS['post'] ) ) {
 639              $args = array( 'post_parent' => (int) $GLOBALS['post']->post_parent );
 640          } else {
 641              return $kids;
 642          }
 643      } elseif ( is_object( $args ) ) {
 644          $args = array( 'post_parent' => (int) $args->post_parent );
 645      } elseif ( is_numeric( $args ) ) {
 646          $args = array( 'post_parent' => (int) $args );
 647      }
 648  
 649      $defaults = array(
 650          'numberposts' => -1,
 651          'post_type'   => 'any',
 652          'post_status' => 'any',
 653          'post_parent' => 0,
 654      );
 655  
 656      $parsed_args = wp_parse_args( $args, $defaults );
 657  
 658      $children = get_posts( $parsed_args );
 659  
 660      if ( ! $children ) {
 661          return $kids;
 662      }
 663  
 664      if ( ! empty( $parsed_args['fields'] ) ) {
 665          return $children;
 666      }
 667  
 668      update_post_cache( $children );
 669  
 670      foreach ( $children as $key => $child ) {
 671          $kids[ $child->ID ] = $children[ $key ];
 672      }
 673  
 674      if ( $output == OBJECT ) {
 675          return $kids;
 676      } elseif ( $output == ARRAY_A ) {
 677          $weeuns = array();
 678          foreach ( (array) $kids as $kid ) {
 679              $weeuns[ $kid->ID ] = get_object_vars( $kids[ $kid->ID ] );
 680          }
 681          return $weeuns;
 682      } elseif ( $output == ARRAY_N ) {
 683          $babes = array();
 684          foreach ( (array) $kids as $kid ) {
 685              $babes[ $kid->ID ] = array_values( get_object_vars( $kids[ $kid->ID ] ) );
 686          }
 687          return $babes;
 688      } else {
 689          return $kids;
 690      }
 691  }
 692  
 693  /**
 694   * Get extended entry info (<!--more-->).
 695   *
 696   * There should not be any space after the second dash and before the word
 697   * 'more'. There can be text or space(s) after the word 'more', but won't be
 698   * referenced.
 699   *
 700   * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
 701   * the `<!--more-->`. The 'extended' key has the content after the
 702   * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
 703   *
 704   * @since 1.0.0
 705   *
 706   * @param string $post Post content.
 707   * @return array Post before ('main'), after ('extended'), and custom read more ('more_text').
 708   */
 709  function get_extended( $post ) {
 710      //Match the new style more links.
 711      if ( preg_match( '/<!--more(.*?)?-->/', $post, $matches ) ) {
 712          list($main, $extended) = explode( $matches[0], $post, 2 );
 713          $more_text             = $matches[1];
 714      } else {
 715          $main      = $post;
 716          $extended  = '';
 717          $more_text = '';
 718      }
 719  
 720      //  leading and trailing whitespace.
 721      $main      = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $main );
 722      $extended  = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $extended );
 723      $more_text = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $more_text );
 724  
 725      return array(
 726          'main'      => $main,
 727          'extended'  => $extended,
 728          'more_text' => $more_text,
 729      );
 730  }
 731  
 732  /**
 733   * Retrieves post data given a post ID or post object.
 734   *
 735   * See sanitize_post() for optional $filter values. Also, the parameter
 736   * `$post`, must be given as a variable, since it is passed by reference.
 737   *
 738   * @since 1.5.1
 739   *
 740   * @global WP_Post $post Global post object.
 741   *
 742   * @param int|WP_Post|null $post   Optional. Post ID or post object. Defaults to global $post.
 743   * @param string           $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
 744   *                                 a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
 745   * @param string           $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
 746   *                                 or 'display'. Default 'raw'.
 747   * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
 748   *                            When $output is OBJECT, a `WP_Post` instance is returned.
 749   */
 750  function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
 751      if ( empty( $post ) && isset( $GLOBALS['post'] ) ) {
 752          $post = $GLOBALS['post'];
 753      }
 754  
 755      if ( $post instanceof WP_Post ) {
 756          $_post = $post;
 757      } elseif ( is_object( $post ) ) {
 758          if ( empty( $post->filter ) ) {
 759              $_post = sanitize_post( $post, 'raw' );
 760              $_post = new WP_Post( $_post );
 761          } elseif ( 'raw' == $post->filter ) {
 762              $_post = new WP_Post( $post );
 763          } else {
 764              $_post = WP_Post::get_instance( $post->ID );
 765          }
 766      } else {
 767          $_post = WP_Post::get_instance( $post );
 768      }
 769  
 770      if ( ! $_post ) {
 771          return null;
 772      }
 773  
 774      $_post = $_post->filter( $filter );
 775  
 776      if ( $output == ARRAY_A ) {
 777          return $_post->to_array();
 778      } elseif ( $output == ARRAY_N ) {
 779          return array_values( $_post->to_array() );
 780      }
 781  
 782      return $_post;
 783  }
 784  
 785  /**
 786   * Retrieve ancestors of a post.
 787   *
 788   * @since 2.5.0
 789   *
 790   * @param int|WP_Post $post Post ID or post object.
 791   * @return array Ancestor IDs or empty array if none are found.
 792   */
 793  function get_post_ancestors( $post ) {
 794      $post = get_post( $post );
 795  
 796      if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID ) {
 797          return array();
 798      }
 799  
 800      $ancestors = array();
 801  
 802      $id          = $post->post_parent;
 803      $ancestors[] = $id;
 804  
 805      while ( $ancestor = get_post( $id ) ) {
 806          // Loop detection: If the ancestor has been seen before, break.
 807          if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors ) ) {
 808              break;
 809          }
 810  
 811          $id          = $ancestor->post_parent;
 812          $ancestors[] = $id;
 813      }
 814  
 815      return $ancestors;
 816  }
 817  
 818  /**
 819   * Retrieve data from a post field based on Post ID.
 820   *
 821   * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
 822   * etc and based off of the post object property or key names.
 823   *
 824   * The context values are based off of the taxonomy filter functions and
 825   * supported values are found within those functions.
 826   *
 827   * @since 2.3.0
 828   * @since 4.5.0 The `$post` parameter was made optional.
 829   *
 830   * @see sanitize_post_field()
 831   *
 832   * @param string      $field   Post field name.
 833   * @param int|WP_Post $post    Optional. Post ID or post object. Defaults to global $post.
 834   * @param string      $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
 835   *                             or 'display'. Default 'display'.
 836   * @return string The value of the post field on success, empty string on failure.
 837   */
 838  function get_post_field( $field, $post = null, $context = 'display' ) {
 839      $post = get_post( $post );
 840  
 841      if ( ! $post ) {
 842          return '';
 843      }
 844  
 845      if ( ! isset( $post->$field ) ) {
 846          return '';
 847      }
 848  
 849      return sanitize_post_field( $field, $post->$field, $post->ID, $context );
 850  }
 851  
 852  /**
 853   * Retrieve the mime type of an attachment based on the ID.
 854   *
 855   * This function can be used with any post type, but it makes more sense with
 856   * attachments.
 857   *
 858   * @since 2.0.0
 859   *
 860   * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
 861   * @return string|false The mime type on success, false on failure.
 862   */
 863  function get_post_mime_type( $post = null ) {
 864      $post = get_post( $post );
 865  
 866      if ( is_object( $post ) ) {
 867          return $post->post_mime_type;
 868      }
 869  
 870      return false;
 871  }
 872  
 873  /**
 874   * Retrieve the post status based on the post ID.
 875   *
 876   * If the post ID is of an attachment, then the parent post status will be given
 877   * instead.
 878   *
 879   * @since 2.0.0
 880   *
 881   * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post..
 882   * @return string|false Post status on success, false on failure.
 883   */
 884  function get_post_status( $post = null ) {
 885      $post = get_post( $post );
 886  
 887      if ( ! is_object( $post ) ) {
 888          return false;
 889      }
 890  
 891      if ( 'attachment' == $post->post_type ) {
 892          if ( 'private' == $post->post_status ) {
 893              return 'private';
 894          }
 895  
 896          // Unattached attachments are assumed to be published.
 897          if ( ( 'inherit' == $post->post_status ) && ( 0 == $post->post_parent ) ) {
 898              return 'publish';
 899          }
 900  
 901          // Inherit status from the parent.
 902          if ( $post->post_parent && ( $post->ID != $post->post_parent ) ) {
 903              $parent_post_status = get_post_status( $post->post_parent );
 904              if ( 'trash' == $parent_post_status ) {
 905                  return get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
 906              } else {
 907                  return $parent_post_status;
 908              }
 909          }
 910      }
 911  
 912      /**
 913       * Filters the post status.
 914       *
 915       * @since 4.4.0
 916       *
 917       * @param string  $post_status The post status.
 918       * @param WP_Post $post        The post object.
 919       */
 920      return apply_filters( 'get_post_status', $post->post_status, $post );
 921  }
 922  
 923  /**
 924   * Retrieve all of the WordPress supported post statuses.
 925   *
 926   * Posts have a limited set of valid status values, this provides the
 927   * post_status values and descriptions.
 928   *
 929   * @since 2.5.0
 930   *
 931   * @return array List of post statuses.
 932   */
 933  function get_post_statuses() {
 934      $status = array(
 935          'draft'   => __( 'Draft' ),
 936          'pending' => __( 'Pending Review' ),
 937          'private' => __( 'Private' ),
 938          'publish' => __( 'Published' ),
 939      );
 940  
 941      return $status;
 942  }
 943  
 944  /**
 945   * Retrieve all of the WordPress support page statuses.
 946   *
 947   * Pages have a limited set of valid status values, this provides the
 948   * post_status values and descriptions.
 949   *
 950   * @since 2.5.0
 951   *
 952   * @return array List of page statuses.
 953   */
 954  function get_page_statuses() {
 955      $status = array(
 956          'draft'   => __( 'Draft' ),
 957          'private' => __( 'Private' ),
 958          'publish' => __( 'Published' ),
 959      );
 960  
 961      return $status;
 962  }
 963  
 964  /**
 965   * Return statuses for privacy requests.
 966   *
 967   * @since 4.9.6
 968   * @access private
 969   *
 970   * @return array
 971   */
 972  function _wp_privacy_statuses() {
 973      return array(
 974          'request-pending'   => __( 'Pending' ),      // Pending confirmation from user.
 975          'request-confirmed' => __( 'Confirmed' ),    // User has confirmed the action.
 976          'request-failed'    => __( 'Failed' ),       // User failed to confirm the action.
 977          'request-completed' => __( 'Completed' ),    // Admin has handled the request.
 978      );
 979  }
 980  
 981  /**
 982   * Register a post status. Do not use before init.
 983   *
 984   * A simple function for creating or modifying a post status based on the
 985   * parameters given. The function will accept an array (second optional
 986   * parameter), along with a string for the post status name.
 987   *
 988   * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
 989   *
 990   * @since 3.0.0
 991   * @global array $wp_post_statuses Inserts new post status object into the list
 992   *
 993   * @param string $post_status Name of the post status.
 994   * @param array|string $args {
 995   *     Optional. Array or string of post status arguments.
 996   *
 997   *     @type bool|string $label                     A descriptive name for the post status marked
 998   *                                                  for translation. Defaults to value of $post_status.
 999   *     @type bool|array  $label_count               Descriptive text to use for nooped plurals.
1000   *                                                  Default array of $label, twice
1001   *     @type bool        $exclude_from_search       Whether to exclude posts with this post status
1002   *                                                  from search results. Default is value of $internal.
1003   *     @type bool        $_builtin                  Whether the status is built-in. Core-use only.
1004   *                                                  Default false.
1005   *     @type bool        $public                    Whether posts of this status should be shown
1006   *                                                  in the front end of the site. Default false.
1007   *     @type bool        $internal                  Whether the status is for internal use only.
1008   *                                                  Default false.
1009   *     @type bool        $protected                 Whether posts with this status should be protected.
1010   *                                                  Default false.
1011   *     @type bool        $private                   Whether posts with this status should be private.
1012   *                                                  Default false.
1013   *     @type bool        $publicly_queryable        Whether posts with this status should be publicly-
1014   *                                                  queryable. Default is value of $public.
1015   *     @type bool        $show_in_admin_all_list    Whether to include posts in the edit listing for
1016   *                                                  their post type. Default is value of $internal.
1017   *     @type bool        $show_in_admin_status_list Show in the list of statuses with post counts at
1018   *                                                  the top of the edit listings,
1019   *                                                  e.g. All (12) | Published (9) | My Custom Status (2)
1020   *                                                  Default is value of $internal.
1021   * }
1022   * @return object
1023   */
1024  function register_post_status( $post_status, $args = array() ) {
1025      global $wp_post_statuses;
1026  
1027      if ( ! is_array( $wp_post_statuses ) ) {
1028          $wp_post_statuses = array();
1029      }
1030  
1031      // Args prefixed with an underscore are reserved for internal use.
1032      $defaults = array(
1033          'label'                     => false,
1034          'label_count'               => false,
1035          'exclude_from_search'       => null,
1036          '_builtin'                  => false,
1037          'public'                    => null,
1038          'internal'                  => null,
1039          'protected'                 => null,
1040          'private'                   => null,
1041          'publicly_queryable'        => null,
1042          'show_in_admin_status_list' => null,
1043          'show_in_admin_all_list'    => null,
1044      );
1045      $args     = wp_parse_args( $args, $defaults );
1046      $args     = (object) $args;
1047  
1048      $post_status = sanitize_key( $post_status );
1049      $args->name  = $post_status;
1050  
1051      // Set various defaults.
1052      if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private ) {
1053          $args->internal = true;
1054      }
1055  
1056      if ( null === $args->public ) {
1057          $args->public = false;
1058      }
1059  
1060      if ( null === $args->private ) {
1061          $args->private = false;
1062      }
1063  
1064      if ( null === $args->protected ) {
1065          $args->protected = false;
1066      }
1067  
1068      if ( null === $args->internal ) {
1069          $args->internal = false;
1070      }
1071  
1072      if ( null === $args->publicly_queryable ) {
1073          $args->publicly_queryable = $args->public;
1074      }
1075  
1076      if ( null === $args->exclude_from_search ) {
1077          $args->exclude_from_search = $args->internal;
1078      }
1079  
1080      if ( null === $args->show_in_admin_all_list ) {
1081          $args->show_in_admin_all_list = ! $args->internal;
1082      }
1083  
1084      if ( null === $args->show_in_admin_status_list ) {
1085          $args->show_in_admin_status_list = ! $args->internal;
1086      }
1087  
1088      if ( false === $args->label ) {
1089          $args->label = $post_status;
1090      }
1091  
1092      if ( false === $args->label_count ) {
1093          // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingle,WordPress.WP.I18n.NonSingularStringLiteralPlural
1094          $args->label_count = _n_noop( $args->label, $args->label );
1095      }
1096  
1097      $wp_post_statuses[ $post_status ] = $args;
1098  
1099      return $args;
1100  }
1101  
1102  /**
1103   * Retrieve a post status object by name.
1104   *
1105   * @since 3.0.0
1106   *
1107   * @global array $wp_post_statuses List of post statuses.
1108   *
1109   * @see register_post_status()
1110   *
1111   * @param string $post_status The name of a registered post status.
1112   * @return object|null A post status object.
1113   */
1114  function get_post_status_object( $post_status ) {
1115      global $wp_post_statuses;
1116  
1117      if ( empty( $wp_post_statuses[ $post_status ] ) ) {
1118          return null;
1119      }
1120  
1121      return $wp_post_statuses[ $post_status ];
1122  }
1123  
1124  /**
1125   * Get a list of post statuses.
1126   *
1127   * @since 3.0.0
1128   *
1129   * @global array $wp_post_statuses List of post statuses.
1130   *
1131   * @see register_post_status()
1132   *
1133   * @param array|string $args     Optional. Array or string of post status arguments to compare against
1134   *                               properties of the global `$wp_post_statuses objects`. Default empty array.
1135   * @param string       $output   Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
1136   * @param string       $operator Optional. The logical operation to perform. 'or' means only one element
1137   *                               from the array needs to match; 'and' means all elements must match.
1138   *                               Default 'and'.
1139   * @return array A list of post status names or objects.
1140   */
1141  function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
1142      global $wp_post_statuses;
1143  
1144      $field = ( 'names' == $output ) ? 'name' : false;
1145  
1146      return wp_filter_object_list( $wp_post_statuses, $args, $operator, $field );
1147  }
1148  
1149  /**
1150   * Whether the post type is hierarchical.
1151   *
1152   * A false return value might also mean that the post type does not exist.
1153   *
1154   * @since 3.0.0
1155   *
1156   * @see get_post_type_object()
1157   *
1158   * @param string $post_type Post type name
1159   * @return bool Whether post type is hierarchical.
1160   */
1161  function is_post_type_hierarchical( $post_type ) {
1162      if ( ! post_type_exists( $post_type ) ) {
1163          return false;
1164      }
1165  
1166      $post_type = get_post_type_object( $post_type );
1167      return $post_type->hierarchical;
1168  }
1169  
1170  /**
1171   * Determines whether a post type is registered.
1172   *
1173   * For more information on this and similar theme functions, check out
1174   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1175   * Conditional Tags} article in the Theme Developer Handbook.
1176   *
1177   * @since 3.0.0
1178   *
1179   * @see get_post_type_object()
1180   *
1181   * @param string $post_type Post type name.
1182   * @return bool Whether post type is registered.
1183   */
1184  function post_type_exists( $post_type ) {
1185      return (bool) get_post_type_object( $post_type );
1186  }
1187  
1188  /**
1189   * Retrieves the post type of the current post or of a given post.
1190   *
1191   * @since 2.1.0
1192   *
1193   * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
1194   * @return string|false          Post type on success, false on failure.
1195   */
1196  function get_post_type( $post = null ) {
1197      $post = get_post( $post );
1198      if ( $post ) {
1199          return $post->post_type;
1200      }
1201  
1202      return false;
1203  }
1204  
1205  /**
1206   * Retrieves a post type object by name.
1207   *
1208   * @since 3.0.0
1209   * @since 4.6.0 Object returned is now an instance of `WP_Post_Type`.
1210   *
1211   * @global array $wp_post_types List of post types.
1212   *
1213   * @see register_post_type()
1214   *
1215   * @param string $post_type The name of a registered post type.
1216   * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
1217   */
1218  function get_post_type_object( $post_type ) {
1219      global $wp_post_types;
1220  
1221      if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
1222          return null;
1223      }
1224  
1225      return $wp_post_types[ $post_type ];
1226  }
1227  
1228  /**
1229   * Get a list of all registered post type objects.
1230   *
1231   * @since 2.9.0
1232   *
1233   * @global array $wp_post_types List of post types.
1234   *
1235   * @see register_post_type() for accepted arguments.
1236   *
1237   * @param array|string $args     Optional. An array of key => value arguments to match against
1238   *                               the post type objects. Default empty array.
1239   * @param string       $output   Optional. The type of output to return. Accepts post type 'names'
1240   *                               or 'objects'. Default 'names'.
1241   * @param string       $operator Optional. The logical operation to perform. 'or' means only one
1242   *                               element from the array needs to match; 'and' means all elements
1243   *                               must match; 'not' means no elements may match. Default 'and'.
1244   * @return string[]|WP_Post_Type[] An array of post type names or objects.
1245   */
1246  function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
1247      global $wp_post_types;
1248  
1249      $field = ( 'names' == $output ) ? 'name' : false;
1250  
1251      return wp_filter_object_list( $wp_post_types, $args, $operator, $field );
1252  }
1253  
1254  /**
1255   * Registers a post type.
1256   *
1257   * Note: Post type registrations should not be hooked before the
1258   * {@see 'init'} action. Also, any taxonomy connections should be
1259   * registered via the `$taxonomies` argument to ensure consistency
1260   * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
1261   * are used.
1262   *
1263   * Post types can support any number of built-in core features such
1264   * as meta boxes, custom fields, post thumbnails, post statuses,
1265   * comments, and more. See the `$supports` argument for a complete
1266   * list of supported features.
1267   *
1268   * @since 2.9.0
1269   * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
1270   * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
1271   *              screen and post editing screen.
1272   * @since 4.6.0 Post type object returned is now an instance of `WP_Post_Type`.
1273   * @since 4.7.0 Introduced `show_in_rest`, `rest_base` and `rest_controller_class`
1274   *              arguments to register the post type in REST API.
1275   *
1276   * @global array $wp_post_types List of post types.
1277   *
1278   * @param string $post_type Post type key. Must not exceed 20 characters and may
1279   *                          only contain lowercase alphanumeric characters, dashes,
1280   *                          and underscores. See sanitize_key().
1281   * @param array|string $args {
1282   *     Array or string of arguments for registering a post type.
1283   *
1284   *     @type string      $label                 Name of the post type shown in the menu. Usually plural.
1285   *                                              Default is value of $labels['name'].
1286   *     @type array       $labels                An array of labels for this post type. If not set, post
1287   *                                              labels are inherited for non-hierarchical types and page
1288   *                                              labels for hierarchical ones. See get_post_type_labels() for a full
1289   *                                              list of supported labels.
1290   *     @type string      $description           A short descriptive summary of what the post type is.
1291   *                                              Default empty.
1292   *     @type bool        $public                Whether a post type is intended for use publicly either via
1293   *                                              the admin interface or by front-end users. While the default
1294   *                                              settings of $exclude_from_search, $publicly_queryable, $show_ui,
1295   *                                              and $show_in_nav_menus are inherited from public, each does not
1296   *                                              rely on this relationship and controls a very specific intention.
1297   *                                              Default false.
1298   *     @type bool        $hierarchical          Whether the post type is hierarchical (e.g. page). Default false.
1299   *     @type bool        $exclude_from_search   Whether to exclude posts with this post type from front end search
1300   *                                              results. Default is the opposite value of $public.
1301   *     @type bool        $publicly_queryable    Whether queries can be performed on the front end for the post type
1302   *                                              as part of parse_request(). Endpoints would include:
1303   *                                              * ?post_type={post_type_key}
1304   *                                              * ?{post_type_key}={single_post_slug}
1305   *                                              * ?{post_type_query_var}={single_post_slug}
1306   *                                              If not set, the default is inherited from $public.
1307   *     @type bool        $show_ui               Whether to generate and allow a UI for managing this post type in the
1308   *                                              admin. Default is value of $public.
1309   *     @type bool|string $show_in_menu          Where to show the post type in the admin menu. To work, $show_ui
1310   *                                              must be true. If true, the post type is shown in its own top level
1311   *                                              menu. If false, no menu is shown. If a string of an existing top
1312   *                                              level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post
1313   *                                              type will be placed as a sub-menu of that.
1314   *                                              Default is value of $show_ui.
1315   *     @type bool        $show_in_nav_menus     Makes this post type available for selection in navigation menus.
1316   *                                              Default is value $public.
1317   *     @type bool        $show_in_admin_bar     Makes this post type available via the admin bar. Default is value
1318   *                                              of $show_in_menu.
1319   *     @type bool        $show_in_rest          Whether to add the post type route in the REST API 'wp/v2' namespace.
1320   *     @type string      $rest_base             To change the base url of REST API route. Default is $post_type.
1321   *     @type string      $rest_controller_class REST API Controller class name. Default is 'WP_REST_Posts_Controller'.
1322   *     @type int         $menu_position         The position in the menu order the post type should appear. To work,
1323   *                                              $show_in_menu must be true. Default null (at the bottom).
1324   *     @type string      $menu_icon             The url to the icon to be used for this menu. Pass a base64-encoded
1325   *                                              SVG using a data URI, which will be colored to match the color scheme
1326   *                                              -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1327   *                                              of a Dashicons helper class to use a font icon, e.g.
1328   *                                              'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1329   *                                              so an icon can be added via CSS. Defaults to use the posts icon.
1330   *     @type string      $capability_type       The string to use to build the read, edit, and delete capabilities.
1331   *                                              May be passed as an array to allow for alternative plurals when using
1332   *                                              this argument as a base to construct the capabilities, e.g.
1333   *                                              array('story', 'stories'). Default 'post'.
1334   *     @type array       $capabilities          Array of capabilities for this post type. $capability_type is used
1335   *                                              as a base to construct capabilities by default.
1336   *                                              See get_post_type_capabilities().
1337   *     @type bool        $map_meta_cap          Whether to use the internal default meta capability handling.
1338   *                                              Default false.
1339   *     @type array       $supports              Core feature(s) the post type supports. Serves as an alias for calling
1340   *                                              add_post_type_support() directly. Core features include 'title',
1341   *                                              'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1342   *                                              'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1343   *                                              Additionally, the 'revisions' feature dictates whether the post type
1344   *                                              will store revisions, and the 'comments' feature dictates whether the
1345   *                                              comments count will show on the edit screen. Defaults is an array
1346   *                                              containing 'title' and 'editor'.
1347   *     @type callable    $register_meta_box_cb  Provide a callback function that sets up the meta boxes for the
1348   *                                              edit form. Do remove_meta_box() and add_meta_box() calls in the
1349   *                                              callback. Default null.
1350   *     @type array       $taxonomies            An array of taxonomy identifiers that will be registered for the
1351   *                                              post type. Taxonomies can be registered later with register_taxonomy()
1352   *                                              or register_taxonomy_for_object_type().
1353   *                                              Default empty array.
1354   *     @type bool|string $has_archive           Whether there should be post type archives, or if a string, the
1355   *                                              archive slug to use. Will generate the proper rewrite rules if
1356   *                                              $rewrite is enabled. Default false.
1357   *     @type bool|array  $rewrite              {
1358   *         Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1359   *         Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1360   *         passed with any of these keys:
1361   *
1362   *         @type string $slug       Customize the permastruct slug. Defaults to $post_type key.
1363   *         @type bool   $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1364   *                                  Default true.
1365   *         @type bool   $feeds      Whether the feed permastruct should be built for this post type.
1366   *                                  Default is value of $has_archive.
1367   *         @type bool   $pages      Whether the permastruct should provide for pagination. Default true.
1368   *         @type const  $ep_mask    Endpoint mask to assign. If not specified and permalink_epmask is set,
1369   *                                  inherits from $permalink_epmask. If not specified and permalink_epmask
1370   *                                  is not set, defaults to EP_PERMALINK.
1371   *     }
1372   *     @type string|bool $query_var             Sets the query_var key for this post type. Defaults to $post_type
1373   *                                              key. If false, a post type cannot be loaded at
1374   *                                              ?{query_var}={post_slug}. If specified as a string, the query
1375   *                                              ?{query_var_string}={post_slug} will be valid.
1376   *     @type bool        $can_export            Whether to allow this post type to be exported. Default true.
1377   *     @type bool        $delete_with_user      Whether to delete posts of this type when deleting a user. If true,
1378   *                                              posts of this type belonging to the user will be moved to trash
1379   *                                              when then user is deleted. If false, posts of this type belonging
1380   *                                              to the user will *not* be trashed or deleted. If not set (the default),
1381   *                                              posts are trashed if post_type_supports('author'). Otherwise posts
1382   *                                              are not trashed or deleted. Default null.
1383   *     @type bool        $_builtin              FOR INTERNAL USE ONLY! True if this post type is a native or
1384   *                                              "built-in" post_type. Default false.
1385   *     @type string      $_edit_link            FOR INTERNAL USE ONLY! URL segment to use for edit link of
1386   *                                              this post type. Default 'post.php?post=%d'.
1387   * }
1388   * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1389   */
1390  function register_post_type( $post_type, $args = array() ) {
1391      global $wp_post_types;
1392  
1393      if ( ! is_array( $wp_post_types ) ) {
1394          $wp_post_types = array();
1395      }
1396  
1397      // Sanitize post type name
1398      $post_type = sanitize_key( $post_type );
1399  
1400      if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1401          _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1402          return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1403      }
1404  
1405      $post_type_object = new WP_Post_Type( $post_type, $args );
1406      $post_type_object->add_supports();
1407      $post_type_object->add_rewrite_rules();
1408      $post_type_object->register_meta_boxes();
1409  
1410      $wp_post_types[ $post_type ] = $post_type_object;
1411  
1412      $post_type_object->add_hooks();
1413      $post_type_object->register_taxonomies();
1414  
1415      /**
1416       * Fires after a post type is registered.
1417       *
1418       * @since 3.3.0
1419       * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1420       *
1421       * @param string       $post_type        Post type.
1422       * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1423       */
1424      do_action( 'registered_post_type', $post_type, $post_type_object );
1425  
1426      return $post_type_object;
1427  }
1428  
1429  /**
1430   * Unregisters a post type.
1431   *
1432   * Can not be used to unregister built-in post types.
1433   *
1434   * @since 4.5.0
1435   *
1436   * @global array $wp_post_types List of post types.
1437   *
1438   * @param string $post_type Post type to unregister.
1439   * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1440   */
1441  function unregister_post_type( $post_type ) {
1442      global $wp_post_types;
1443  
1444      if ( ! post_type_exists( $post_type ) ) {
1445          return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1446      }
1447  
1448      $post_type_object = get_post_type_object( $post_type );
1449  
1450      // Do not allow unregistering internal post types.
1451      if ( $post_type_object->_builtin ) {
1452          return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1453      }
1454  
1455      $post_type_object->remove_supports();
1456      $post_type_object->remove_rewrite_rules();
1457      $post_type_object->unregister_meta_boxes();
1458      $post_type_object->remove_hooks();
1459      $post_type_object->unregister_taxonomies();
1460  
1461      unset( $wp_post_types[ $post_type ] );
1462  
1463      /**
1464       * Fires after a post type was unregistered.
1465       *
1466       * @since 4.5.0
1467       *
1468       * @param string $post_type Post type key.
1469       */
1470      do_action( 'unregistered_post_type', $post_type );
1471  
1472      return true;
1473  }
1474  
1475  /**
1476   * Build an object with all post type capabilities out of a post type object
1477   *
1478   * Post type capabilities use the 'capability_type' argument as a base, if the
1479   * capability is not set in the 'capabilities' argument array or if the
1480   * 'capabilities' argument is not supplied.
1481   *
1482   * The capability_type argument can optionally be registered as an array, with
1483   * the first value being singular and the second plural, e.g. array('story, 'stories')
1484   * Otherwise, an 's' will be added to the value for the plural form. After
1485   * registration, capability_type will always be a string of the singular value.
1486   *
1487   * By default, seven keys are accepted as part of the capabilities array:
1488   *
1489   * - edit_post, read_post, and delete_post are meta capabilities, which are then
1490   *   generally mapped to corresponding primitive capabilities depending on the
1491   *   context, which would be the post being edited/read/deleted and the user or
1492   *   role being checked. Thus these capabilities would generally not be granted
1493   *   directly to users or roles.
1494   *
1495   * - edit_posts - Controls whether objects of this post type can be edited.
1496   * - edit_others_posts - Controls whether objects of this type owned by other users
1497   *   can be edited. If the post type does not support an author, then this will
1498   *   behave like edit_posts.
1499   * - publish_posts - Controls publishing objects of this post type.
1500   * - read_private_posts - Controls whether private objects can be read.
1501   *
1502   * These four primitive capabilities are checked in core in various locations.
1503   * There are also seven other primitive capabilities which are not referenced
1504   * directly in core, except in map_meta_cap(), which takes the three aforementioned
1505   * meta capabilities and translates them into one or more primitive capabilities
1506   * that must then be checked against the user or role, depending on the context.
1507   *
1508   * - read - Controls whether objects of this post type can be read.
1509   * - delete_posts - Controls whether objects of this post type can be deleted.
1510   * - delete_private_posts - Controls whether private objects can be deleted.
1511   * - delete_published_posts - Controls whether published objects can be deleted.
1512   * - delete_others_posts - Controls whether objects owned by other users can be
1513   *   can be deleted. If the post type does not support an author, then this will
1514   *   behave like delete_posts.
1515   * - edit_private_posts - Controls whether private objects can be edited.
1516   * - edit_published_posts - Controls whether published objects can be edited.
1517   *
1518   * These additional capabilities are only used in map_meta_cap(). Thus, they are
1519   * only assigned by default if the post type is registered with the 'map_meta_cap'
1520   * argument set to true (default is false).
1521   *
1522   * @since 3.0.0
1523   *
1524   * @see register_post_type()
1525   * @see map_meta_cap()
1526   *
1527   * @param object $args Post type registration arguments.
1528   * @return object Object with all the capabilities as member variables.
1529   */
1530  function get_post_type_capabilities( $args ) {
1531      if ( ! is_array( $args->capability_type ) ) {
1532          $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1533      }
1534  
1535      // Singular base for meta capabilities, plural base for primitive capabilities.
1536      list( $singular_base, $plural_base ) = $args->capability_type;
1537  
1538      $default_capabilities = array(
1539          // Meta capabilities
1540          'edit_post'          => 'edit_' . $singular_base,
1541          'read_post'          => 'read_' . $singular_base,
1542          'delete_post'        => 'delete_' . $singular_base,
1543          // Primitive capabilities used outside of map_meta_cap():
1544          'edit_posts'         => 'edit_' . $plural_base,
1545          'edit_others_posts'  => 'edit_others_' . $plural_base,
1546          'publish_posts'      => 'publish_' . $plural_base,
1547          'read_private_posts' => 'read_private_' . $plural_base,
1548      );
1549  
1550      // Primitive capabilities used within map_meta_cap():
1551      if ( $args->map_meta_cap ) {
1552          $default_capabilities_for_mapping = array(
1553              'read'                   => 'read',
1554              'delete_posts'           => 'delete_' . $plural_base,
1555              'delete_private_posts'   => 'delete_private_' . $plural_base,
1556              'delete_published_posts' => 'delete_published_' . $plural_base,
1557              'delete_others_posts'    => 'delete_others_' . $plural_base,
1558              'edit_private_posts'     => 'edit_private_' . $plural_base,
1559              'edit_published_posts'   => 'edit_published_' . $plural_base,
1560          );
1561          $default_capabilities             = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1562      }
1563  
1564      $capabilities = array_merge( $default_capabilities, $args->capabilities );
1565  
1566      // Post creation capability simply maps to edit_posts by default:
1567      if ( ! isset( $capabilities['create_posts'] ) ) {
1568          $capabilities['create_posts'] = $capabilities['edit_posts'];
1569      }
1570  
1571      // Remember meta capabilities for future reference.
1572      if ( $args->map_meta_cap ) {
1573          _post_type_meta_capabilities( $capabilities );
1574      }
1575  
1576      return (object) $capabilities;
1577  }
1578  
1579  /**
1580   * Store or return a list of post type meta caps for map_meta_cap().
1581   *
1582   * @since 3.1.0
1583   * @access private
1584   *
1585   * @global array $post_type_meta_caps Used to store meta capabilities.
1586   *
1587   * @param array $capabilities Post type meta capabilities.
1588   */
1589  function _post_type_meta_capabilities( $capabilities = null ) {
1590      global $post_type_meta_caps;
1591  
1592      foreach ( $capabilities as $core => $custom ) {
1593          if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1594              $post_type_meta_caps[ $custom ] = $core;
1595          }
1596      }
1597  }
1598  
1599  /**
1600   * Builds an object with all post type labels out of a post type object.
1601   *
1602   * Accepted keys of the label array in the post type object:
1603   *
1604   * - `name` - General name for the post type, usually plural. The same and overridden
1605   *          by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1606   * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1607   * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1608   *             When internationalizing this string, please use a {@link https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#disambiguation-by-context gettext context}
1609   *             matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1610   * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1611   * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1612   * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1613   * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1614   * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
1615   * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1616   * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1617   * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1618   *                        'No pages found in Trash'.
1619   * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1620   *                       post types. Default is 'Parent Page:'.
1621   * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1622   * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1623   * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
1624   * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1625   * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1626   *                           'Uploaded to this page'.
1627   * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1628   * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1629   * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1630   * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1631   * - `menu_name` - Label for the menu name. Default is the same as `name`.
1632   * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1633   *                       'Filter pages list'.
1634   * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1635   *                           'Pages list navigation'.
1636   * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1637   * - `item_published` - Label used when an item is published. Default is 'Post published.' / 'Page published.'
1638   * - `item_published_privately` - Label used when an item is published with private visibility.
1639   *                              Default is 'Post published privately.' / 'Page published privately.'
1640   * - `item_reverted_to_draft` - Label used when an item is switched to a draft.
1641   *                            Default is 'Post reverted to draft.' / 'Page reverted to draft.'
1642   * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' /
1643   *                    'Page scheduled.'
1644   * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.'
1645   *
1646   * Above, the first default value is for non-hierarchical post types (like posts)
1647   * and the second one is for hierarchical post types (like pages).
1648   *
1649   * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1650   *
1651   * @since 3.0.0
1652   * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1653   *              and `use_featured_image` labels.
1654   * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1655   *              `items_list_navigation`, and `items_list` labels.
1656   * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1657   * @since 4.7.0 Added the `view_items` and `attributes` labels.
1658   * @since 5.0.0 Added the `item_published`, `item_published_privately`, `item_reverted_to_draft`,
1659   *              `item_scheduled`, and `item_updated` labels.
1660   *
1661   * @access private
1662   *
1663   * @param object|WP_Post_Type $post_type_object Post type object.
1664   * @return object Object with all the labels as member variables.
1665   */
1666  function get_post_type_labels( $post_type_object ) {
1667      $nohier_vs_hier_defaults              = array(
1668          'name'                     => array( _x( 'Posts', 'post type general name' ), _x( 'Pages', 'post type general name' ) ),
1669          'singular_name'            => array( _x( 'Post', 'post type singular name' ), _x( 'Page', 'post type singular name' ) ),
1670          'add_new'                  => array( _x( 'Add New', 'post' ), _x( 'Add New', 'page' ) ),
1671          'add_new_item'             => array( __( 'Add New Post' ), __( 'Add New Page' ) ),
1672          'edit_item'                => array( __( 'Edit Post' ), __( 'Edit Page' ) ),
1673          'new_item'                 => array( __( 'New Post' ), __( 'New Page' ) ),
1674          'view_item'                => array( __( 'View Post' ), __( 'View Page' ) ),
1675          'view_items'               => array( __( 'View Posts' ), __( 'View Pages' ) ),
1676          'search_items'             => array( __( 'Search Posts' ), __( 'Search Pages' ) ),
1677          'not_found'                => array( __( 'No posts found.' ), __( 'No pages found.' ) ),
1678          'not_found_in_trash'       => array( __( 'No posts found in Trash.' ), __( 'No pages found in Trash.' ) ),
1679          'parent_item_colon'        => array( null, __( 'Parent Page:' ) ),
1680          'all_items'                => array( __( 'All Posts' ), __( 'All Pages' ) ),
1681          'archives'                 => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1682          'attributes'               => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ),
1683          'insert_into_item'         => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1684          'uploaded_to_this_item'    => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1685          'featured_image'           => array( _x( 'Featured Image', 'post' ), _x( 'Featured Image', 'page' ) ),
1686          'set_featured_image'       => array( _x( 'Set featured image', 'post' ), _x( 'Set featured image', 'page' ) ),
1687          'remove_featured_image'    => array( _x( 'Remove featured image', 'post' ), _x( 'Remove featured image', 'page' ) ),
1688          'use_featured_image'       => array( _x( 'Use as featured image', 'post' ), _x( 'Use as featured image', 'page' ) ),
1689          'filter_items_list'        => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1690          'items_list_navigation'    => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1691          'items_list'               => array( __( 'Posts list' ), __( 'Pages list' ) ),
1692          'item_published'           => array( __( 'Post published.' ), __( 'Page published.' ) ),
1693          'item_published_privately' => array( __( 'Post published privately.' ), __( 'Page published privately.' ) ),
1694          'item_reverted_to_draft'   => array( __( 'Post reverted to draft.' ), __( 'Page reverted to draft.' ) ),
1695          'item_scheduled'           => array( __( 'Post scheduled.' ), __( 'Page scheduled.' ) ),
1696          'item_updated'             => array( __( 'Post updated.' ), __( 'Page updated.' ) ),
1697      );
1698      $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1699  
1700      $labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1701  
1702      $post_type = $post_type_object->name;
1703  
1704      $default_labels = clone $labels;
1705  
1706      /**
1707       * Filters the labels of a specific post type.
1708       *
1709       * The dynamic portion of the hook name, `$post_type`, refers to
1710       * the post type slug.
1711       *
1712       * @since 3.5.0
1713       *
1714       * @see get_post_type_labels() for the full list of labels.
1715       *
1716       * @param object $labels Object with labels for the post type as member variables.
1717       */
1718      $labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1719  
1720      // Ensure that the filtered labels contain all required default values.
1721      $labels = (object) array_merge( (array) $default_labels, (array) $labels );
1722  
1723      return $labels;
1724  }
1725  
1726  /**
1727   * Build an object with custom-something object (post type, taxonomy) labels
1728   * out of a custom-something object
1729   *
1730   * @since 3.0.0
1731   * @access private
1732   *
1733   * @param object $object                  A custom-something object.
1734   * @param array  $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1735   * @return object Object containing labels for the given custom-something object.
1736   */
1737  function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1738      $object->labels = (array) $object->labels;
1739  
1740      if ( isset( $object->label ) && empty( $object->labels['name'] ) ) {
1741          $object->labels['name'] = $object->label;
1742      }
1743  
1744      if ( ! isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) ) {
1745          $object->labels['singular_name'] = $object->labels['name'];
1746      }
1747  
1748      if ( ! isset( $object->labels['name_admin_bar'] ) ) {
1749          $object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1750      }
1751  
1752      if ( ! isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) ) {
1753          $object->labels['menu_name'] = $object->labels['name'];
1754      }
1755  
1756      if ( ! isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) ) {
1757          $object->labels['all_items'] = $object->labels['menu_name'];
1758      }
1759  
1760      if ( ! isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1761          $object->labels['archives'] = $object->labels['all_items'];
1762      }
1763  
1764      $defaults = array();
1765      foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1766          $defaults[ $key ] = $object->hierarchical ? $value[1] : $value[0];
1767      }
1768      $labels         = array_merge( $defaults, $object->labels );
1769      $object->labels = (object) $object->labels;
1770  
1771      return (object) $labels;
1772  }
1773  
1774  /**
1775   * Add submenus for post types.
1776   *
1777   * @access private
1778   * @since 3.1.0
1779   */
1780  function _add_post_type_submenus() {
1781      foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1782          $ptype_obj = get_post_type_object( $ptype );
1783          // Sub-menus only.
1784          if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true ) {
1785              continue;
1786          }
1787          add_submenu_page( $ptype_obj->show_in_menu, $ptype_obj->labels->name, $ptype_obj->labels->all_items, $ptype_obj->cap->edit_posts, "edit.php?post_type=$ptype" );
1788      }
1789  }
1790  
1791  /**
1792   * Registers support of certain features for a post type.
1793   *
1794   * All core features are directly associated with a functional area of the edit
1795   * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1796   * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1797   * 'thumbnail', 'custom-fields', and 'post-formats'.
1798   *
1799   * Additionally, the 'revisions' feature dictates whether the post type will
1800   * store revisions, and the 'comments' feature dictates whether the comments
1801   * count will show on the edit screen.
1802   *
1803   * Example usage:
1804   *
1805   *     add_post_type_support( 'my_post_type', 'comments' );
1806   *     add_post_type_support( 'my_post_type', array(
1807   *         'author', 'excerpt',
1808   *     ) );
1809   *     add_post_type_support( 'my_post_type', 'my_feature', array(
1810   *         'field' => 'value',
1811   *     ) );
1812   *
1813   * @since 3.0.0
1814   *
1815   * @global array $_wp_post_type_features
1816   *
1817   * @param string       $post_type The post type for which to add the feature.
1818   * @param string|array $feature   The feature being added, accepts an array of
1819   *                                feature strings or a single string.
1820   * @param mixed        ...$args   Optional extra arguments to pass along with certain features.
1821   */
1822  function add_post_type_support( $post_type, $feature, ...$args ) {
1823      global $_wp_post_type_features;
1824  
1825      $features = (array) $feature;
1826      foreach ( $features as $feature ) {
1827          if ( $args ) {
1828              $_wp_post_type_features[ $post_type ][ $feature ] = $args;
1829          } else {
1830              $_wp_post_type_features[ $post_type ][ $feature ] = true;
1831          }
1832      }
1833  }
1834  
1835  /**
1836   * Remove support for a feature from a post type.
1837   *
1838   * @since 3.0.0
1839   *
1840   * @global array $_wp_post_type_features
1841   *
1842   * @param string $post_type The post type for which to remove the feature.
1843   * @param string $feature   The feature being removed.
1844   */
1845  function remove_post_type_support( $post_type, $feature ) {
1846      global $_wp_post_type_features;
1847  
1848      unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1849  }
1850  
1851  /**
1852   * Get all the post type features
1853   *
1854   * @since 3.4.0
1855   *
1856   * @global array $_wp_post_type_features
1857   *
1858   * @param string $post_type The post type.
1859   * @return array Post type supports list.
1860   */
1861  function get_all_post_type_supports( $post_type ) {
1862      global $_wp_post_type_features;
1863  
1864      if ( isset( $_wp_post_type_features[ $post_type ] ) ) {
1865          return $_wp_post_type_features[ $post_type ];
1866      }
1867  
1868      return array();
1869  }
1870  
1871  /**
1872   * Check a post type's support for a given feature.
1873   *
1874   * @since 3.0.0
1875   *
1876   * @global array $_wp_post_type_features
1877   *
1878   * @param string $post_type The post type being checked.
1879   * @param string $feature   The feature being checked.
1880   * @return bool Whether the post type supports the given feature.
1881   */
1882  function post_type_supports( $post_type, $feature ) {
1883      global $_wp_post_type_features;
1884  
1885      return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) );
1886  }
1887  
1888  /**
1889   * Retrieves a list of post type names that support a specific feature.
1890   *
1891   * @since 4.5.0
1892   *
1893   * @global array $_wp_post_type_features Post type features
1894   *
1895   * @param array|string $feature  Single feature or an array of features the post types should support.
1896   * @param string       $operator Optional. The logical operation to perform. 'or' means
1897   *                               only one element from the array needs to match; 'and'
1898   *                               means all elements must match; 'not' means no elements may
1899   *                               match. Default 'and'.
1900   * @return array A list of post type names.
1901   */
1902  function get_post_types_by_support( $feature, $operator = 'and' ) {
1903      global $_wp_post_type_features;
1904  
1905      $features = array_fill_keys( (array) $feature, true );
1906  
1907      return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1908  }
1909  
1910  /**
1911   * Update the post type for the post ID.
1912   *
1913   * The page or post cache will be cleaned for the post ID.
1914   *
1915   * @since 2.5.0
1916   *
1917   * @global wpdb $wpdb WordPress database abstraction object.
1918   *
1919   * @param int    $post_id   Optional. Post ID to change post type. Default 0.
1920   * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1921   *                          name a few. Default 'post'.
1922   * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1923   */
1924  function set_post_type( $post_id = 0, $post_type = 'post' ) {
1925      global $wpdb;
1926  
1927      $post_type = sanitize_post_field( 'post_type', $post_type, $post_id, 'db' );
1928      $return    = $wpdb->update( $wpdb->posts, array( 'post_type' => $post_type ), array( 'ID' => $post_id ) );
1929  
1930      clean_post_cache( $post_id );
1931  
1932      return $return;
1933  }
1934  
1935  /**
1936   * Determines whether a post type is considered "viewable".
1937   *
1938   * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1939   * For all others, the 'publicly_queryable' value will be used.
1940   *
1941   * @since 4.4.0
1942   * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1943   * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1944   *
1945   * @param string|WP_Post_Type $post_type Post type name or object.
1946   * @return bool Whether the post type should be considered viewable.
1947   */
1948  function is_post_type_viewable( $post_type ) {
1949      if ( is_scalar( $post_type ) ) {
1950          $post_type = get_post_type_object( $post_type );
1951          if ( ! $post_type ) {
1952              return false;
1953          }
1954      }
1955  
1956      return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1957  }
1958  
1959  /**
1960   * Retrieves an array of the latest posts, or posts matching the given criteria.
1961   *
1962   * The defaults are as follows:
1963   *
1964   * @since 1.2.0
1965   *
1966   * @see WP_Query::parse_query()
1967   *
1968   * @param array $args {
1969   *     Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1970   *     available arguments.
1971   *
1972   *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of $posts_per_page
1973   *                                        in WP_Query. Accepts -1 for all. Default 5.
1974   *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
1975   *                                        Is an alias of $cat in WP_Query. Default 0.
1976   *     @type array      $include          An array of post IDs to retrieve, sticky posts will be included.
1977   *                                        Is an alias of $post__in in WP_Query. Default empty array.
1978   *     @type array      $exclude          An array of post IDs not to retrieve. Default empty array.
1979   *     @type bool       $suppress_filters Whether to suppress filters. Default true.
1980   * }
1981   * @return WP_Post[]|int[] Array of post objects or post IDs.
1982   */
1983  function get_posts( $args = null ) {
1984      $defaults = array(
1985          'numberposts'      => 5,
1986          'category'         => 0,
1987          'orderby'          => 'date',
1988          'order'            => 'DESC',
1989          'include'          => array(),
1990          'exclude'          => array(),
1991          'meta_key'         => '',
1992          'meta_value'       => '',
1993          'post_type'        => 'post',
1994          'suppress_filters' => true,
1995      );
1996  
1997      $parsed_args = wp_parse_args( $args, $defaults );
1998      if ( empty( $parsed_args['post_status'] ) ) {
1999          $parsed_args['post_status'] = ( 'attachment' == $parsed_args['post_type'] ) ? 'inherit' : 'publish';
2000      }
2001      if ( ! empty( $parsed_args['numberposts'] ) && empty( $parsed_args['posts_per_page'] ) ) {
2002          $parsed_args['posts_per_page'] = $parsed_args['numberposts'];
2003      }
2004      if ( ! empty( $parsed_args['category'] ) ) {
2005          $parsed_args['cat'] = $parsed_args['category'];
2006      }
2007      if ( ! empty( $parsed_args['include'] ) ) {
2008          $incposts                      = wp_parse_id_list( $parsed_args['include'] );
2009          $parsed_args['posts_per_page'] = count( $incposts );  // only the number of posts included
2010          $parsed_args['post__in']       = $incposts;
2011      } elseif ( ! empty( $parsed_args['exclude'] ) ) {
2012          $parsed_args['post__not_in'] = wp_parse_id_list( $parsed_args['exclude'] );
2013      }
2014  
2015      $parsed_args['ignore_sticky_posts'] = true;
2016      $parsed_args['no_found_rows']       = true;
2017  
2018      $get_posts = new WP_Query;
2019      return $get_posts->query( $parsed_args );
2020  
2021  }
2022  
2023  //
2024  // Post meta functions
2025  //
2026  
2027  /**
2028   * Adds a meta field to the given post.
2029   *
2030   * Post meta data is called "Custom Fields" on the Administration Screen.
2031   *
2032   * @since 1.5.0
2033   *
2034   * @param int    $post_id    Post ID.
2035   * @param string $meta_key   Metadata name.
2036   * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
2037   * @param bool   $unique     Optional. Whether the same key should not be added.
2038   *                           Default false.
2039   * @return int|false Meta ID on success, false on failure.
2040   */
2041  function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
2042      // Make sure meta is added to the post, not a revision.
2043      $the_post = wp_is_post_revision( $post_id );
2044      if ( $the_post ) {
2045          $post_id = $the_post;
2046      }
2047  
2048      return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
2049  }
2050  
2051  /**
2052   * Deletes a post meta field for the given post ID.
2053   *
2054   * You can match based on the key, or key and value. Removing based on key and
2055   * value, will keep from removing duplicate metadata with the same key. It also
2056   * allows removing all metadata matching the key, if needed.
2057   *
2058   * @since 1.5.0
2059   *
2060   * @param int    $post_id    Post ID.
2061   * @param string $meta_key   Metadata name.
2062   * @param mixed  $meta_value Optional. Metadata value. Must be serializable if
2063   *                           non-scalar. Default empty.
2064   * @return bool True on success, false on failure.
2065   */
2066  function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
2067      // Make sure meta is added to the post, not a revision.
2068      $the_post = wp_is_post_revision( $post_id );
2069      if ( $the_post ) {
2070          $post_id = $the_post;
2071      }
2072  
2073      return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
2074  }
2075  
2076  /**
2077   * Retrieves a post meta field for the given post ID.
2078   *
2079   * @since 1.5.0
2080   *
2081   * @param int    $post_id Post ID.
2082   * @param string $key     Optional. The meta key to retrieve. By default, returns
2083   *                        data for all keys. Default empty.
2084   * @param bool   $single  Optional. If true, returns only the first value for the specified meta key.
2085   *                        This parameter has no effect if $key is not specified. Default false.
2086   * @return mixed Will be an array if $single is false. Will be value of the meta
2087   *               field if $single is true.
2088   */
2089  function get_post_meta( $post_id, $key = '', $single = false ) {
2090      return get_metadata( 'post', $post_id, $key, $single );
2091  }
2092  
2093  /**
2094   * Updates a post meta field based on the given post ID.
2095   *
2096   * Use the `$prev_value` parameter to differentiate between meta fields with the
2097   * same key and post ID.
2098   *
2099   * If the meta field for the post does not exist, it will be added and its ID returned.
2100   *
2101   * Can be used in place of add_post_meta().
2102   *
2103   * @since 1.5.0
2104   *
2105   * @param int    $post_id    Post ID.
2106   * @param string $meta_key   Metadata key.
2107   * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
2108   * @param mixed  $prev_value Optional. Previous value to check before updating.
2109   * @return int|bool The new meta field ID if a field with the given key didn't exist and was
2110   *                  therefore added, true on successful update, false on failure.
2111   */
2112  function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
2113      // Make sure meta is added to the post, not a revision.
2114      $the_post = wp_is_post_revision( $post_id );
2115      if ( $the_post ) {
2116          $post_id = $the_post;
2117      }
2118  
2119      return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
2120  }
2121  
2122  /**
2123   * Deletes everything from post meta matching the given meta key.
2124   *
2125   * @since 2.3.0
2126   *
2127   * @param string $post_meta_key Key to search for when deleting.
2128   * @return bool Whether the post meta key was deleted from the database.
2129   */
2130  function delete_post_meta_by_key( $post_meta_key ) {
2131      return delete_metadata( 'post', null, $post_meta_key, '', true );
2132  }
2133  
2134  /**
2135   * Registers a meta key for posts.
2136   *
2137   * @since 4.9.8
2138   *
2139   * @param string $post_type Post type to register a meta key for. Pass an empty string
2140   *                          to register the meta key across all existing post types.
2141   * @param string $meta_key  The meta key to register.
2142   * @param array  $args      Data used to describe the meta key when registered. See
2143   *                          {@see register_meta()} for a list of supported arguments.
2144   * @return bool True if the meta key was successfully registered, false if not.
2145   */
2146  function register_post_meta( $post_type, $meta_key, array $args ) {
2147      $args['object_subtype'] = $post_type;
2148  
2149      return register_meta( 'post', $meta_key, $args );
2150  }
2151  
2152  /**
2153   * Unregisters a meta key for posts.
2154   *
2155   * @since 4.9.8
2156   *
2157   * @param string $post_type Post type the meta key is currently registered for. Pass
2158   *                          an empty string if the meta key is registered across all
2159   *                          existing post types.
2160   * @param string $meta_key  The meta key to unregister.
2161   * @return bool True on success, false if the meta key was not previously registered.
2162   */
2163  function unregister_post_meta( $post_type, $meta_key ) {
2164      return unregister_meta_key( 'post', $meta_key, $post_type );
2165  }
2166  
2167  /**
2168   * Retrieve post meta fields, based on post ID.
2169   *
2170   * The post meta fields are retrieved from the cache where possible,
2171   * so the function is optimized to be called more than once.
2172   *
2173   * @since 1.2.0
2174   *
2175   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2176   * @return array Post meta for the given post.
2177   */
2178  function get_post_custom( $post_id = 0 ) {
2179      $post_id = absint( $post_id );
2180      if ( ! $post_id ) {
2181          $post_id = get_the_ID();
2182      }
2183  
2184      return get_post_meta( $post_id );
2185  }
2186  
2187  /**
2188   * Retrieve meta field names for a post.
2189   *
2190   * If there are no meta fields, then nothing (null) will be returned.
2191   *
2192   * @since 1.2.0
2193   *
2194   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2195   * @return array|void Array of the keys, if retrieved.
2196   */
2197  function get_post_custom_keys( $post_id = 0 ) {
2198      $custom = get_post_custom( $post_id );
2199  
2200      if ( ! is_array( $custom ) ) {
2201          return;
2202      }
2203  
2204      $keys = array_keys( $custom );
2205      if ( $keys ) {
2206          return $keys;
2207      }
2208  }
2209  
2210  /**
2211   * Retrieve values for a custom post field.
2212   *
2213   * The parameters must not be considered optional. All of the post meta fields
2214   * will be retrieved and only the meta field key values returned.
2215   *
2216   * @since 1.2.0
2217   *
2218   * @param string $key     Optional. Meta field key. Default empty.
2219   * @param int    $post_id Optional. Post ID. Default is ID of the global $post.
2220   * @return array|null Meta field values.
2221   */
2222  function get_post_custom_values( $key = '', $post_id = 0 ) {
2223      if ( ! $key ) {
2224          return null;
2225      }
2226  
2227      $custom = get_post_custom( $post_id );
2228  
2229      return isset( $custom[ $key ] ) ? $custom[ $key ] : null;
2230  }
2231  
2232  /**
2233   * Determines whether a post is sticky.
2234   *
2235   * Sticky posts should remain at the top of The Loop. If the post ID is not
2236   * given, then The Loop ID for the current post will be used.
2237   *
2238   * For more information on this and similar theme functions, check out
2239   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2240   * Conditional Tags} article in the Theme Developer Handbook.
2241   *
2242   * @since 2.7.0
2243   *
2244   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2245   * @return bool Whether post is sticky.
2246   */
2247  function is_sticky( $post_id = 0 ) {
2248      $post_id = absint( $post_id );
2249  
2250      if ( ! $post_id ) {
2251          $post_id = get_the_ID();
2252      }
2253  
2254      $stickies = get_option( 'sticky_posts' );
2255  
2256      $is_sticky = is_array( $stickies ) && in_array( $post_id, $stickies );
2257  
2258      /**
2259       * Filters whether a post is sticky.
2260       *
2261       * @since 5.3.0
2262       *
2263       * @param bool $is_sticky Whether a post is sticky.
2264       * @param int  $post_id   Post ID.
2265       */
2266      return apply_filters( 'is_sticky', $is_sticky, $post_id );
2267  }
2268  
2269  /**
2270   * Sanitize every post field.
2271   *
2272   * If the context is 'raw', then the post object or array will get minimal
2273   * sanitization of the integer fields.
2274   *
2275   * @since 2.3.0
2276   *
2277   * @see sanitize_post_field()
2278   *
2279   * @param object|WP_Post|array $post    The Post Object or Array
2280   * @param string               $context Optional. How to sanitize post fields.
2281   *                                      Accepts 'raw', 'edit', 'db', or 'display'.
2282   *                                      Default 'display'.
2283   * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
2284   *                              same type as $post).
2285   */
2286  function sanitize_post( $post, $context = 'display' ) {
2287      if ( is_object( $post ) ) {
2288          // Check if post already filtered for this context.
2289          if ( isset( $post->filter ) && $context == $post->filter ) {
2290              return $post;
2291          }
2292          if ( ! isset( $post->ID ) ) {
2293              $post->ID = 0;
2294          }
2295          foreach ( array_keys( get_object_vars( $post ) ) as $field ) {
2296              $post->$field = sanitize_post_field( $field, $post->$field, $post->ID, $context );
2297          }
2298          $post->filter = $context;
2299      } elseif ( is_array( $post ) ) {
2300          // Check if post already filtered for this context.
2301          if ( isset( $post['filter'] ) && $context == $post['filter'] ) {
2302              return $post;
2303          }
2304          if ( ! isset( $post['ID'] ) ) {
2305              $post['ID'] = 0;
2306          }
2307          foreach ( array_keys( $post ) as $field ) {
2308              $post[ $field ] = sanitize_post_field( $field, $post[ $field ], $post['ID'], $context );
2309          }
2310          $post['filter'] = $context;
2311      }
2312      return $post;
2313  }
2314  
2315  /**
2316   * Sanitize post field based on context.
2317   *
2318   * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and
2319   * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
2320   * are treated like 'display' when calling filters.
2321   *
2322   * @since 2.3.0
2323   * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
2324   *
2325   * @param string $field   The Post Object field name.
2326   * @param mixed  $value   The Post Object value.
2327   * @param int    $post_id Post ID.
2328   * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
2329   *                        'db', 'display', 'attribute' and 'js'. Default 'display'.
2330   * @return mixed Sanitized value.
2331   */
2332  function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
2333      $int_fields = array( 'ID', 'post_parent', 'menu_order' );
2334      if ( in_array( $field, $int_fields ) ) {
2335          $value = (int) $value;
2336      }
2337  
2338      // Fields which contain arrays of integers.
2339      $array_int_fields = array( 'ancestors' );
2340      if ( in_array( $field, $array_int_fields ) ) {
2341          $value = array_map( 'absint', $value );
2342          return $value;
2343      }
2344  
2345      if ( 'raw' == $context ) {
2346          return $value;
2347      }
2348  
2349      $prefixed = false;
2350      if ( false !== strpos( $field, 'post_' ) ) {
2351          $prefixed        = true;
2352          $field_no_prefix = str_replace( 'post_', '', $field );
2353      }
2354  
2355      if ( 'edit' == $context ) {
2356          $format_to_edit = array( 'post_content', 'post_excerpt', 'post_title', 'post_password' );
2357  
2358          if ( $prefixed ) {
2359  
2360              /**
2361               * Filters the value of a specific post field to edit.
2362               *
2363               * The dynamic portion of the hook name, `$field`, refers to the post
2364               * field name.
2365               *
2366               * @since 2.3.0
2367               *
2368               * @param mixed $value   Value of the post field.
2369               * @param int   $post_id Post ID.
2370               */
2371              $value = apply_filters( "edit_{$field}", $value, $post_id );
2372  
2373              /**
2374               * Filters the value of a specific post field to edit.
2375               *
2376               * The dynamic portion of the hook name, `$field_no_prefix`, refers to
2377               * the post field name.
2378               *
2379               * @since 2.3.0
2380               *
2381               * @param mixed $value   Value of the post field.
2382               * @param int   $post_id Post ID.
2383               */
2384              $value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
2385          } else {
2386              $value = apply_filters( "edit_post_{$field}", $value, $post_id );
2387          }
2388  
2389          if ( in_array( $field, $format_to_edit ) ) {
2390              if ( 'post_content' == $field ) {
2391                  $value = format_to_edit( $value, user_can_richedit() );
2392              } else {
2393                  $value = format_to_edit( $value );
2394              }
2395          } else {
2396              $value = esc_attr( $value );
2397          }
2398      } elseif ( 'db' == $context ) {
2399          if ( $prefixed ) {
2400  
2401              /**
2402               * Filters the value of a specific post field before saving.
2403               *
2404               * The dynamic portion of the hook name, `$field`, refers to the post
2405               * field name.
2406               *
2407               * @since 2.3.0
2408               *
2409               * @param mixed $value Value of the post field.
2410               */
2411              $value = apply_filters( "pre_{$field}", $value );
2412  
2413              /**
2414               * Filters the value of a specific field before saving.
2415               *
2416               * The dynamic portion of the hook name, `$field_no_prefix`, refers
2417               * to the post field name.
2418               *
2419               * @since 2.3.0
2420               *
2421               * @param mixed $value Value of the post field.
2422               */
2423              $value = apply_filters( "{$field_no_prefix}_save_pre", $value );
2424          } else {
2425              $value = apply_filters( "pre_post_{$field}", $value );
2426  
2427              /**
2428               * Filters the value of a specific post field before saving.
2429               *
2430               * The dynamic portion of the hook name, `$field`, refers to the post
2431               * field name.
2432               *
2433               * @since 2.3.0
2434               *
2435               * @param mixed $value Value of the post field.
2436               */
2437              $value = apply_filters( "{$field}_pre", $value );
2438          }
2439      } else {
2440  
2441          // Use display filters by default.
2442          if ( $prefixed ) {
2443  
2444              /**
2445               * Filters the value of a specific post field for display.
2446               *
2447               * The dynamic portion of the hook name, `$field`, refers to the post
2448               * field name.
2449               *
2450               * @since 2.3.0
2451               *
2452               * @param mixed  $value   Value of the prefixed post field.
2453               * @param int    $post_id Post ID.
2454               * @param string $context Context for how to sanitize the field. Possible
2455               *                        values include 'raw', 'edit', 'db', 'display',
2456               *                        'attribute' and 'js'.
2457               */
2458              $value = apply_filters( "{$field}", $value, $post_id, $context );
2459          } else {
2460              $value = apply_filters( "post_{$field}", $value, $post_id, $context );
2461          }
2462  
2463          if ( 'attribute' == $context ) {
2464              $value = esc_attr( $value );
2465          } elseif ( 'js' == $context ) {
2466              $value = esc_js( $value );
2467          }
2468      }
2469  
2470      return $value;
2471  }
2472  
2473  /**
2474   * Make a post sticky.
2475   *
2476   * Sticky posts should be displayed at the top of the front page.
2477   *
2478   * @since 2.7.0
2479   *
2480   * @param int $post_id Post ID.
2481   */
2482  function stick_post( $post_id ) {
2483      $stickies = get_option( 'sticky_posts' );
2484  
2485      if ( ! is_array( $stickies ) ) {
2486          $stickies = array( $post_id );
2487      }
2488  
2489      if ( ! in_array( $post_id, $stickies ) ) {
2490          $stickies[] = $post_id;
2491      }
2492  
2493      $updated = update_option( 'sticky_posts', $stickies );
2494  
2495      if ( $updated ) {
2496          /**
2497           * Fires once a post has been added to the sticky list.
2498           *
2499           * @since 4.6.0
2500           *
2501           * @param int $post_id ID of the post that was stuck.
2502           */
2503          do_action( 'post_stuck', $post_id );
2504      }
2505  }
2506  
2507  /**
2508   * Un-stick a post.
2509   *
2510   * Sticky posts should be displayed at the top of the front page.
2511   *
2512   * @since 2.7.0
2513   *
2514   * @param int $post_id Post ID.
2515   */
2516  function unstick_post( $post_id ) {
2517      $stickies = get_option( 'sticky_posts' );
2518  
2519      if ( ! is_array( $stickies ) ) {
2520          return;
2521      }
2522  
2523      if ( ! in_array( $post_id, $stickies ) ) {
2524          return;
2525      }
2526  
2527      $offset = array_search( $post_id, $stickies );
2528      if ( false === $offset ) {
2529          return;
2530      }
2531  
2532      array_splice( $stickies, $offset, 1 );
2533  
2534      $updated = update_option( 'sticky_posts', $stickies );
2535  
2536      if ( $updated ) {
2537          /**
2538           * Fires once a post has been removed from the sticky list.
2539           *
2540           * @since 4.6.0
2541           *
2542           * @param int $post_id ID of the post that was unstuck.
2543           */
2544          do_action( 'post_unstuck', $post_id );
2545      }
2546  }
2547  
2548  /**
2549   * Return the cache key for wp_count_posts() based on the passed arguments.
2550   *
2551   * @since 3.9.0
2552   * @access private
2553   *
2554   * @param string $type Optional. Post type to retrieve count Default 'post'.
2555   * @param string $perm Optional. 'readable' or empty. Default empty.
2556   * @return string The cache key.
2557   */
2558  function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2559      $cache_key = 'posts-' . $type;
2560      if ( 'readable' == $perm && is_user_logged_in() ) {
2561          $post_type_object = get_post_type_object( $type );
2562          if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2563              $cache_key .= '_' . $perm . '_' . get_current_user_id();
2564          }
2565      }
2566      return $cache_key;
2567  }
2568  
2569  /**
2570   * Count number of posts of a post type and if user has permissions to view.
2571   *
2572   * This function provides an efficient method of finding the amount of post's
2573   * type a blog has. Another method is to count the amount of items in
2574   * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2575   * when developing for 2.5+, use this function instead.
2576   *
2577   * The $perm parameter checks for 'readable' value and if the user can read
2578   * private posts, it will display that for the user that is signed in.
2579   *
2580   * @since 2.5.0
2581   *
2582   * @global wpdb $wpdb WordPress database abstraction object.
2583   *
2584   * @param string $type Optional. Post type to retrieve count. Default 'post'.
2585   * @param string $perm Optional. 'readable' or empty. Default empty.
2586   * @return object Number of posts for each status.
2587   */
2588  function wp_count_posts( $type = 'post', $perm = '' ) {
2589      global $wpdb;
2590  
2591      if ( ! post_type_exists( $type ) ) {
2592          return new stdClass;
2593      }
2594  
2595      $cache_key = _count_posts_cache_key( $type, $perm );
2596  
2597      $counts = wp_cache_get( $cache_key, 'counts' );
2598      if ( false !== $counts ) {
2599          /** This filter is documented in wp-includes/post.php */
2600          return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2601      }
2602  
2603      $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2604      if ( 'readable' == $perm && is_user_logged_in() ) {
2605          $post_type_object = get_post_type_object( $type );
2606          if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2607              $query .= $wpdb->prepare(
2608                  " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2609                  get_current_user_id()
2610              );
2611          }
2612      }
2613      $query .= ' GROUP BY post_status';
2614  
2615      $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2616      $counts  = array_fill_keys( get_post_stati(), 0 );
2617  
2618      foreach ( $results as $row ) {
2619          $counts[ $row['post_status'] ] = $row['num_posts'];
2620      }
2621  
2622      $counts = (object) $counts;
2623      wp_cache_set( $cache_key, $counts, 'counts' );
2624  
2625      /**
2626       * Modify returned post counts by status for the current post type.
2627       *
2628       * @since 3.7.0
2629       *
2630       * @param object $counts An object containing the current post_type's post
2631       *                       counts by status.
2632       * @param string $type   Post type.
2633       * @param string $perm   The permission to determine if the posts are 'readable'
2634       *                       by the current user.
2635       */
2636      return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2637  }
2638  
2639  /**
2640   * Count number of attachments for the mime type(s).
2641   *
2642   * If you set the optional mime_type parameter, then an array will still be
2643   * returned, but will only have the item you are looking for. It does not give
2644   * you the number of attachments that are children of a post. You can get that
2645   * by counting the number of children that post has.
2646   *
2647   * @since 2.5.0
2648   *
2649   * @global wpdb $wpdb WordPress database abstraction object.
2650   *
2651   * @param string|array $mime_type Optional. Array or comma-separated list of
2652   *                                MIME patterns. Default empty.
2653   * @return object An object containing the attachment counts by mime type.
2654   */
2655  function wp_count_attachments( $mime_type = '' ) {
2656      global $wpdb;
2657  
2658      $and   = wp_post_mime_type_where( $mime_type );
2659      $count = $wpdb->get_results( "SELECT post_mime_type, COUNT( * ) AS num_posts FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status != 'trash' $and GROUP BY post_mime_type", ARRAY_A );
2660  
2661      $counts = array();
2662      foreach ( (array) $count as $row ) {
2663          $counts[ $row['post_mime_type'] ] = $row['num_posts'];
2664      }
2665      $counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and" );
2666  
2667      /**
2668       * Modify returned attachment counts by mime type.
2669       *
2670       * @since 3.7.0
2671       *
2672       * @param object $counts    An object containing the attachment counts by
2673       *                          mime type.
2674       * @param string $mime_type The mime type pattern used to filter the attachments
2675       *                          counted.
2676       */
2677      return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2678  }
2679  
2680  /**
2681   * Get default post mime types.
2682   *
2683   * @since 2.9.0
2684   * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
2685   *
2686   * @return array List of post mime types.
2687   */
2688  function get_post_mime_types() {
2689      $post_mime_types = array(   //    array( adj, noun )
2690          'image'       => array(
2691              __( 'Images' ),
2692              __( 'Manage Images' ),
2693              /* translators: %s: Number of images. */
2694              _n_noop(
2695                  'Image <span class="count">(%s)</span>',
2696                  'Images <span class="count">(%s)</span>'
2697              ),
2698          ),
2699          'audio'       => array(
2700              __( 'Audio' ),
2701              __( 'Manage Audio' ),
2702              /* translators: %s: Number of audio files. */
2703              _n_noop(
2704                  'Audio <span class="count">(%s)</span>',
2705                  'Audio <span class="count">(%s)</span>'
2706              ),
2707          ),
2708          'video'       => array(
2709              __( 'Video' ),
2710              __( 'Manage Video' ),
2711              /* translators: %s: Number of video files. */
2712              _n_noop(
2713                  'Video <span class="count">(%s)</span>',
2714                  'Video <span class="count">(%s)</span>'
2715              ),
2716          ),
2717          'document'    => array(
2718              __( 'Documents' ),
2719              __( 'Manage Documents' ),
2720              /* translators: %s: Number of documents. */
2721              _n_noop(
2722                  'Document <span class="count">(%s)</span>',
2723                  'Documents <span class="count">(%s)</span>'
2724              ),
2725          ),
2726          'spreadsheet' => array(
2727              __( 'Spreadsheets' ),
2728              __( 'Manage Spreadsheets' ),
2729              /* translators: %s: Number of spreadsheets. */
2730              _n_noop(
2731                  'Spreadsheet <span class="count">(%s)</span>',
2732                  'Spreadsheets <span class="count">(%s)</span>'
2733              ),
2734          ),
2735          'archive'     => array(
2736              __( 'Archives' ),
2737              __( 'Manage Archives' ),
2738              /* translators: %s: Number of archives. */
2739              _n_noop(
2740                  'Archive <span class="count">(%s)</span>',
2741                  'Archives <span class="count">(%s)</span>'
2742              ),
2743          ),
2744      );
2745  
2746      $ext_types  = wp_get_ext_types();
2747      $mime_types = wp_get_mime_types();
2748  
2749      foreach ( $post_mime_types as $group => $labels ) {
2750          if ( in_array( $group, array( 'image', 'audio', 'video' ) ) ) {
2751              continue;
2752          }
2753  
2754          if ( ! isset( $ext_types[ $group ] ) ) {
2755              unset( $post_mime_types[ $group ] );
2756              continue;
2757          }
2758  
2759          $group_mime_types = array();
2760          foreach ( $ext_types[ $group ] as $extension ) {
2761              foreach ( $mime_types as $exts => $mime ) {
2762                  if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
2763                      $group_mime_types[] = $mime;
2764                      break;
2765                  }
2766              }
2767          }
2768          $group_mime_types = implode( ',', array_unique( $group_mime_types ) );
2769  
2770          $post_mime_types[ $group_mime_types ] = $labels;
2771          unset( $post_mime_types[ $group ] );
2772      }
2773  
2774      /**
2775       * Filters the default list of post mime types.
2776       *
2777       * @since 2.5.0
2778       *
2779       * @param array $post_mime_types Default list of post mime types.
2780       */
2781      return apply_filters( 'post_mime_types', $post_mime_types );
2782  }
2783  
2784  /**
2785   * Check a MIME-Type against a list.
2786   *
2787   * If the wildcard_mime_types parameter is a string, it must be comma separated
2788   * list. If the real_mime_types is a string, it is also comma separated to
2789   * create the list.
2790   *
2791   * @since 2.5.0
2792   *
2793   * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2794   *                                          or flash (same as *flash*).
2795   * @param string|array $real_mime_types     Real post mime type values.
2796   * @return array array(wildcard=>array(real types)).
2797   */
2798  function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2799      $matches = array();
2800      if ( is_string( $wildcard_mime_types ) ) {
2801          $wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2802      }
2803      if ( is_string( $real_mime_types ) ) {
2804          $real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2805      }
2806  
2807      $patternses = array();
2808      $wild       = '[-._a-z0-9]*';
2809  
2810      foreach ( (array) $wildcard_mime_types as $type ) {
2811          $mimes = array_map( 'trim', explode( ',', $type ) );
2812          foreach ( $mimes as $mime ) {
2813              $regex                 = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2814              $patternses[][ $type ] = "^$regex$";
2815              if ( false === strpos( $mime, '/' ) ) {
2816                  $patternses[][ $type ] = "^$regex/";
2817                  $patternses[][ $type ] = $regex;
2818              }
2819          }
2820      }
2821      asort( $patternses );
2822  
2823      foreach ( $patternses as $patterns ) {
2824          foreach ( $patterns as $type => $pattern ) {
2825              foreach ( (array) $real_mime_types as $real ) {
2826                  if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[ $type ] ) || false === array_search( $real, $matches[ $type ] ) ) ) {
2827                      $matches[ $type ][] = $real;
2828                  }
2829              }
2830          }
2831      }
2832      return $matches;
2833  }
2834  
2835  /**
2836   * Convert MIME types into SQL.
2837   *
2838   * @since 2.5.0
2839   *
2840   * @param string|array $post_mime_types List of mime types or comma separated string
2841   *                                      of mime types.
2842   * @param string       $table_alias     Optional. Specify a table alias, if needed.
2843   *                                      Default empty.
2844   * @return string The SQL AND clause for mime searching.
2845   */
2846  function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2847      $where     = '';
2848      $wildcards = array( '', '%', '%/%' );
2849      if ( is_string( $post_mime_types ) ) {
2850          $post_mime_types = array_map( 'trim', explode( ',', $post_mime_types ) );
2851      }
2852  
2853      $wheres = array();
2854  
2855      foreach ( (array) $post_mime_types as $mime_type ) {
2856          $mime_type = preg_replace( '/\s/', '', $mime_type );
2857          $slashpos  = strpos( $mime_type, '/' );
2858          if ( false !== $slashpos ) {
2859              $mime_group    = preg_replace( '/[^-*.a-zA-Z0-9]/', '', substr( $mime_type, 0, $slashpos ) );
2860              $mime_subgroup = preg_replace( '/[^-*.+a-zA-Z0-9]/', '', substr( $mime_type, $slashpos + 1 ) );
2861              if ( empty( $mime_subgroup ) ) {
2862                  $mime_subgroup = '*';
2863              } else {
2864                  $mime_subgroup = str_replace( '/', '', $mime_subgroup );
2865              }
2866              $mime_pattern = "$mime_group/$mime_subgroup";
2867          } else {
2868              $mime_pattern = preg_replace( '/[^-*.a-zA-Z0-9]/', '', $mime_type );
2869              if ( false === strpos( $mime_pattern, '*' ) ) {
2870                  $mime_pattern .= '/*';
2871              }
2872          }
2873  
2874          $mime_pattern = preg_replace( '/\*+/', '%', $mime_pattern );
2875  
2876          if ( in_array( $mime_type, $wildcards ) ) {
2877              return '';
2878          }
2879  
2880          if ( false !== strpos( $mime_pattern, '%' ) ) {
2881              $wheres[] = empty( $table_alias ) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2882          } else {
2883              $wheres[] = empty( $table_alias ) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2884          }
2885      }
2886      if ( ! empty( $wheres ) ) {
2887          $where = ' AND (' . join( ' OR ', $wheres ) . ') ';
2888      }
2889      return $where;
2890  }
2891  
2892  /**
2893   * Trash or delete a post or page.
2894   *
2895   * When the post and page is permanently deleted, everything that is tied to
2896   * it is deleted also. This includes comments, post meta fields, and terms
2897   * associated with the post.
2898   *
2899   * The post or page is moved to trash instead of permanently deleted unless
2900   * trash is disabled, item is already in the trash, or $force_delete is true.
2901   *
2902   * @since 1.0.0
2903   *
2904   * @global wpdb $wpdb WordPress database abstraction object.
2905   * @see wp_delete_attachment()
2906   * @see wp_trash_post()
2907   *
2908   * @param int  $postid       Optional. Post ID. Default 0.
2909   * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2910   *                           Default false.
2911   * @return WP_Post|false|null Post data on success, false or null on failure.
2912   */
2913  function wp_delete_post( $postid = 0, $force_delete = false ) {
2914      global $wpdb;
2915  
2916      $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $postid ) );
2917  
2918      if ( ! $post ) {
2919          return $post;
2920      }
2921  
2922      $post = get_post( $post );
2923  
2924      if ( ! $force_delete && ( 'post' === $post->post_type || 'page' === $post->post_type ) && 'trash' !== get_post_status( $postid ) && EMPTY_TRASH_DAYS ) {
2925          return wp_trash_post( $postid );
2926      }
2927  
2928      if ( 'attachment' === $post->post_type ) {
2929          return wp_delete_attachment( $postid, $force_delete );
2930      }
2931  
2932      /**
2933       * Filters whether a post deletion should take place.
2934       *
2935       * @since 4.4.0
2936       *
2937       * @param bool    $delete       Whether to go forward with deletion.
2938       * @param WP_Post $post         Post object.
2939       * @param bool    $force_delete Whether to bypass the trash.
2940       */
2941      $check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2942      if ( null !== $check ) {
2943          return $check;
2944      }
2945  
2946      /**
2947       * Fires before a post is deleted, at the start of wp_delete_post().
2948       *
2949       * @since 3.2.0
2950       *
2951       * @see wp_delete_post()
2952       *
2953       * @param int $postid Post ID.
2954       */
2955      do_action( 'before_delete_post', $postid );
2956  
2957      delete_post_meta( $postid, '_wp_trash_meta_status' );
2958      delete_post_meta( $postid, '_wp_trash_meta_time' );
2959  
2960      wp_delete_object_term_relationships( $postid, get_object_taxonomies( $post->post_type ) );
2961  
2962      $parent_data  = array( 'post_parent' => $post->post_parent );
2963      $parent_where = array( 'post_parent' => $postid );
2964  
2965      if ( is_post_type_hierarchical( $post->post_type ) ) {
2966          // Point children of this page to its parent, also clean the cache of affected children.
2967          $children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2968          $children       = $wpdb->get_results( $children_query );
2969          if ( $children ) {
2970              $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2971          }
2972      }
2973  
2974      // Do raw query. wp_get_post_revisions() is filtered.
2975      $revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2976      // Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2977      foreach ( $revision_ids as $revision_id ) {
2978          wp_delete_post_revision( $revision_id );
2979      }
2980  
2981      // Point all attachments to this post up one level.
2982      $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
2983  
2984      wp_defer_comment_counting( true );
2985  
2986      $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ) );
2987      foreach ( $comment_ids as $comment_id ) {
2988          wp_delete_comment( $comment_id, true );
2989      }
2990  
2991      wp_defer_comment_counting( false );
2992  
2993      $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ) );
2994      foreach ( $post_meta_ids as $mid ) {
2995          delete_metadata_by_mid( 'post', $mid );
2996      }
2997  
2998      /**
2999       * Fires immediately before a post is deleted from the database.
3000       *
3001       * @since 1.2.0
3002       *
3003       * @param int $postid Post ID.
3004       */
3005      do_action( 'delete_post', $postid );
3006      $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
3007      if ( ! $result ) {
3008          return false;
3009      }
3010  
3011      /**
3012       * Fires immediately after a post is deleted from the database.
3013       *
3014       * @since 2.2.0
3015       *
3016       * @param int $postid Post ID.
3017       */
3018      do_action( 'deleted_post', $postid );
3019  
3020      clean_post_cache( $post );
3021  
3022      if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
3023          foreach ( $children as $child ) {
3024              clean_post_cache( $child );
3025          }
3026      }
3027  
3028      wp_clear_scheduled_hook( 'publish_future_post', array( $postid ) );
3029  
3030      /**
3031       * Fires after a post is deleted, at the conclusion of wp_delete_post().
3032       *
3033       * @since 3.2.0
3034       *
3035       * @see wp_delete_post()
3036       *
3037       * @param int $postid Post ID.
3038       */
3039      do_action( 'after_delete_post', $postid );
3040  
3041      return $post;
3042  }
3043  
3044  /**
3045   * Reset the page_on_front, show_on_front, and page_for_post settings when
3046   * a linked page is deleted or trashed.
3047   *
3048   * Also ensures the post is no longer sticky.
3049   *
3050   * @since 3.7.0
3051   * @access private
3052   *
3053   * @param int $post_id Post ID.
3054   */
3055  function _reset_front_page_settings_for_post( $post_id ) {
3056      $post = get_post( $post_id );
3057      if ( 'page' == $post->post_type ) {
3058          /*
3059           * If the page is defined in option page_on_front or post_for_posts,
3060           * adjust the corresponding options.
3061           */
3062          if ( get_option( 'page_on_front' ) == $post->ID ) {
3063              update_option( 'show_on_front', 'posts' );
3064              update_option( 'page_on_front', 0 );
3065          }
3066          if ( get_option( 'page_for_posts' ) == $post->ID ) {
3067              update_option( 'page_for_posts', 0 );
3068          }
3069      }
3070      unstick_post( $post->ID );
3071  }
3072  
3073  /**
3074   * Move a post or page to the Trash
3075   *
3076   * If trash is disabled, the post or page is permanently deleted.
3077   *
3078   * @since 2.9.0
3079   *
3080   * @see wp_delete_post()
3081   *
3082   * @param int $post_id Optional. Post ID. Default is ID of the global $post
3083   *                     if EMPTY_TRASH_DAYS equals true.
3084   * @return WP_Post|false|null Post data on success, false or null on failure.
3085   */
3086  function wp_trash_post( $post_id = 0 ) {
3087      if ( ! EMPTY_TRASH_DAYS ) {
3088          return wp_delete_post( $post_id, true );
3089      }
3090  
3091      $post = get_post( $post_id );
3092  
3093      if ( ! $post ) {
3094          return $post;
3095      }
3096  
3097      if ( 'trash' === $post->post_status ) {
3098          return false;
3099      }
3100  
3101      /**
3102       * Filters whether a post trashing should take place.
3103       *
3104       * @since 4.9.0
3105       *
3106       * @param bool    $trash Whether to go forward with trashing.
3107       * @param WP_Post $post  Post object.
3108       */
3109      $check = apply_filters( 'pre_trash_post', null, $post );
3110      if ( null !== $check ) {
3111          return $check;
3112      }
3113  
3114      /**
3115       * Fires before a post is sent to the trash.
3116       *
3117       * @since 3.3.0
3118       *
3119       * @param int $post_id Post ID.
3120       */
3121      do_action( 'wp_trash_post', $post_id );
3122  
3123      add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
3124      add_post_meta( $post_id, '_wp_trash_meta_time', time() );
3125  
3126      $post_updated = wp_update_post(
3127          array(
3128              'ID'          => $post_id,
3129              'post_status' => 'trash',
3130          )
3131      );
3132  
3133      if ( ! $post_updated ) {
3134          return false;
3135      }
3136  
3137      wp_trash_post_comments( $post_id );
3138  
3139      /**
3140       * Fires after a post is sent to the trash.
3141       *
3142       * @since 2.9.0
3143       *
3144       * @param int $post_id Post ID.
3145       */
3146      do_action( 'trashed_post', $post_id );
3147  
3148      return $post;
3149  }
3150  
3151  /**
3152   * Restore a post or page from the Trash.
3153   *
3154   * @since 2.9.0
3155   *
3156   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
3157   * @return WP_Post|false|null Post data on success, false or null on failure.
3158   */
3159  function wp_untrash_post( $post_id = 0 ) {
3160      $post = get_post( $post_id );
3161  
3162      if ( ! $post ) {
3163          return $post;
3164      }
3165  
3166      if ( 'trash' !== $post->post_status ) {
3167          return false;
3168      }
3169  
3170      /**
3171       * Filters whether a post untrashing should take place.
3172       *
3173       * @since 4.9.0
3174       *
3175       * @param bool    $untrash Whether to go forward with untrashing.
3176       * @param WP_Post $post    Post object.
3177       */
3178      $check = apply_filters( 'pre_untrash_post', null, $post );
3179      if ( null !== $check ) {
3180          return $check;
3181      }
3182  
3183      /**
3184       * Fires before a post is restored from the trash.
3185       *
3186       * @since 2.9.0
3187       *
3188       * @param int $post_id Post ID.
3189       */
3190      do_action( 'untrash_post', $post_id );
3191  
3192      $post_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
3193  
3194      delete_post_meta( $post_id, '_wp_trash_meta_status' );
3195      delete_post_meta( $post_id, '_wp_trash_meta_time' );
3196  
3197      $post_updated = wp_update_post(
3198          array(
3199              'ID'          => $post_id,
3200              'post_status' => $post_status,
3201          )
3202      );
3203  
3204      if ( ! $post_updated ) {
3205          return false;
3206      }
3207  
3208      wp_untrash_post_comments( $post_id );
3209  
3210      /**
3211       * Fires after a post is restored from the trash.
3212       *
3213       * @since 2.9.0
3214       *
3215       * @param int $post_id Post ID.
3216       */
3217      do_action( 'untrashed_post', $post_id );
3218  
3219      return $post;
3220  }
3221  
3222  /**
3223   * Moves comments for a post to the trash.
3224   *
3225   * @since 2.9.0
3226   *
3227   * @global wpdb $wpdb WordPress database abstraction object.
3228   *
3229   * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3230   * @return mixed|void False on failure.
3231   */
3232  function wp_trash_post_comments( $post = null ) {
3233      global $wpdb;
3234  
3235      $post = get_post( $post );
3236      if ( empty( $post ) ) {
3237          return;
3238      }
3239  
3240      $post_id = $post->ID;
3241  
3242      /**
3243       * Fires before comments are sent to the trash.
3244       *
3245       * @since 2.9.0
3246       *
3247       * @param int $post_id Post ID.
3248       */
3249      do_action( 'trash_post_comments', $post_id );
3250  
3251      $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
3252      if ( empty( $comments ) ) {
3253          return;
3254      }
3255  
3256      // Cache current status for each comment.
3257      $statuses = array();
3258      foreach ( $comments as $comment ) {
3259          $statuses[ $comment->comment_ID ] = $comment->comment_approved;
3260      }
3261      add_post_meta( $post_id, '_wp_trash_meta_comments_status', $statuses );
3262  
3263      // Set status for all comments to post-trashed.
3264      $result = $wpdb->update( $wpdb->comments, array( 'comment_approved' => 'post-trashed' ), array( 'comment_post_ID' => $post_id ) );
3265  
3266      clean_comment_cache( array_keys( $statuses ) );
3267  
3268      /**
3269       * Fires after comments are sent to the trash.
3270       *
3271       * @since 2.9.0
3272       *
3273       * @param int   $post_id  Post ID.
3274       * @param array $statuses Array of comment statuses.
3275       */
3276      do_action( 'trashed_post_comments', $post_id, $statuses );
3277  
3278      return $result;
3279  }
3280  
3281  /**
3282   * Restore comments for a post from the trash.
3283   *
3284   * @since 2.9.0
3285   *
3286   * @global wpdb $wpdb WordPress database abstraction object.
3287   *
3288   * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3289   * @return true|void
3290   */
3291  function wp_untrash_post_comments( $post = null ) {
3292      global $wpdb;
3293  
3294      $post = get_post( $post );
3295      if ( empty( $post ) ) {
3296          return;
3297      }
3298  
3299      $post_id = $post->ID;
3300  
3301      $statuses = get_post_meta( $post_id, '_wp_trash_meta_comments_status', true );
3302  
3303      if ( empty( $statuses ) ) {
3304          return true;
3305      }
3306  
3307      /**
3308       * Fires before comments are restored for a post from the trash.
3309       *
3310       * @since 2.9.0
3311       *
3312       * @param int $post_id Post ID.
3313       */
3314      do_action( 'untrash_post_comments', $post_id );
3315  
3316      // Restore each comment to its original status.
3317      $group_by_status = array();
3318      foreach ( $statuses as $comment_id => $comment_status ) {
3319          $group_by_status[ $comment_status ][] = $comment_id;
3320      }
3321  
3322      foreach ( $group_by_status as $status => $comments ) {
3323          // Sanity check. This shouldn't happen.
3324          if ( 'post-trashed' == $status ) {
3325              $status = '0';
3326          }
3327          $comments_in = implode( ', ', array_map( 'intval', $comments ) );
3328          $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
3329      }
3330  
3331      clean_comment_cache( array_keys( $statuses ) );
3332  
3333      delete_post_meta( $post_id, '_wp_trash_meta_comments_status' );
3334  
3335      /**
3336       * Fires after comments are restored for a post from the trash.
3337       *
3338       * @since 2.9.0
3339       *
3340       * @param int $post_id Post ID.
3341       */
3342      do_action( 'untrashed_post_comments', $post_id );
3343  }
3344  
3345  /**
3346   * Retrieve the list of categories for a post.
3347   *
3348   * Compatibility layer for themes and plugins. Also an easy layer of abstraction
3349   * away from the complexity of the taxonomy layer.
3350   *
3351   * @since 2.1.0
3352   *
3353   * @see wp_get_object_terms()
3354   *
3355   * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
3356   *                       global $post. Default 0.
3357   * @param array $args    Optional. Category query parameters. Default empty array.
3358   *                       See WP_Term_Query::__construct() for supported arguments.
3359   * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
3360   *                        'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
3361   *                        is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
3362   *                        WP_Error object if 'category' taxonomy doesn't exist.
3363   */
3364  function wp_get_post_categories( $post_id = 0, $args = array() ) {
3365      $post_id = (int) $post_id;
3366  
3367      $defaults = array( 'fields' => 'ids' );
3368      $args     = wp_parse_args( $args, $defaults );
3369  
3370      $cats = wp_get_object_terms( $post_id, 'category', $args );
3371      return $cats;
3372  }
3373  
3374  /**
3375   * Retrieve the tags for a post.
3376   *
3377   * There is only one default for this function, called 'fields' and by default
3378   * is set to 'all'. There are other defaults that can be overridden in
3379   * wp_get_object_terms().
3380   *
3381   * @since 2.3.0
3382   *
3383   * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
3384   *                       global $post. Default 0.
3385   * @param array $args    Optional. Tag query parameters. Default empty array.
3386   *                       See WP_Term_Query::__construct() for supported arguments.
3387   * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
3388   *                        WP_Error object if 'post_tag' taxonomy doesn't exist.
3389   */
3390  function wp_get_post_tags( $post_id = 0, $args = array() ) {
3391      return wp_get_post_terms( $post_id, 'post_tag', $args );
3392  }
3393  
3394  /**
3395   * Retrieves the terms for a post.
3396   *
3397   * @since 2.8.0
3398   *
3399   * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the
3400   *                               global $post. Default 0.
3401   * @param string|array $taxonomy Optional. The taxonomy slug or array of slugs for which
3402   *                               to retrieve terms. Default 'post_tag'.
3403   * @param array        $args     {
3404   *     Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
3405   *
3406   *     @type string $fields Term fields to retrieve. Default 'all'.
3407   * }
3408   * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
3409   *                        WP_Error object if `$taxonomy` doesn't exist.
3410   */
3411  function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
3412      $post_id = (int) $post_id;
3413  
3414      $defaults = array( 'fields' => 'all' );
3415      $args     = wp_parse_args( $args, $defaults );
3416  
3417      $tags = wp_get_object_terms( $post_id, $taxonomy, $args );
3418  
3419      return $tags;
3420  }
3421  
3422  /**
3423   * Retrieve a number of recent posts.
3424   *
3425   * @since 1.0.0
3426   *
3427   * @see get_posts()
3428   *
3429   * @param array  $args   Optional. Arguments to retrieve posts. Default empty array.
3430   * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which correspond to
3431   *                       a WP_Post object or an associative array, respectively. Default ARRAY_A.
3432   * @return array|false Array of recent posts, where the type of each element is determined by $output parameter.
3433   *                     Empty array on failure.
3434   */
3435  function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
3436  
3437      if ( is_numeric( $args ) ) {
3438          _deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
3439          $args = array( 'numberposts' => absint( $args ) );
3440      }
3441  
3442      // Set default arguments.
3443      $defaults = array(
3444          'numberposts'      => 10,
3445          'offset'           => 0,
3446          'category'         => 0,
3447          'orderby'          => 'post_date',
3448          'order'            => 'DESC',
3449          'include'          => '',
3450          'exclude'          => '',
3451          'meta_key'         => '',
3452          'meta_value'       => '',
3453          'post_type'        => 'post',
3454          'post_status'      => 'draft, publish, future, pending, private',
3455          'suppress_filters' => true,
3456      );
3457  
3458      $parsed_args = wp_parse_args( $args, $defaults );
3459  
3460      $results = get_posts( $parsed_args );
3461  
3462      // Backward compatibility. Prior to 3.1 expected posts to be returned in array.
3463      if ( ARRAY_A == $output ) {
3464          foreach ( $results as $key => $result ) {
3465              $results[ $key ] = get_object_vars( $result );
3466          }
3467          return $results ? $results : array();
3468      }
3469  
3470      return $results ? $results : false;
3471  
3472  }
3473  
3474  /**
3475   * Insert or update a post.
3476   *
3477   * If the $postarr parameter has 'ID' set to a value, then post will be updated.
3478   *
3479   * You can set the post date manually, by setting the values for 'post_date'
3480   * and 'post_date_gmt' keys. You can close the comments or open the comments by
3481   * setting the value for 'comment_status' key.
3482   *
3483   * @since 1.0.0
3484   * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
3485   * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
3486   *
3487   * @see sanitize_post()
3488   * @global wpdb $wpdb WordPress database abstraction object.
3489   *
3490   * @param array $postarr {
3491   *     An array of elements that make up a post to update or insert.
3492   *
3493   *     @type int    $ID                    The post ID. If equal to something other than 0,
3494   *                                         the post with that ID will be updated. Default 0.
3495   *     @type int    $post_author           The ID of the user who added the post. Default is
3496   *                                         the current user ID.
3497   *     @type string $post_date             The date of the post. Default is the current time.
3498   *     @type string $post_date_gmt         The date of the post in the GMT timezone. Default is
3499   *                                         the value of `$post_date`.
3500   *     @type mixed  $post_content          The post content. Default empty.
3501   *     @type string $post_content_filtered The filtered post content. Default empty.
3502   *     @type string $post_title            The post title. Default empty.
3503   *     @type string $post_excerpt          The post excerpt. Default empty.
3504   *     @type string $post_status           The post status. Default 'draft'.
3505   *     @type string $post_type             The post type. Default 'post'.
3506   *     @type string $comment_status        Whether the post can accept comments. Accepts 'open' or 'closed'.
3507   *                                         Default is the value of 'default_comment_status' option.
3508   *     @type string $ping_status           Whether the post can accept pings. Accepts 'open' or 'closed'.
3509   *                                         Default is the value of 'default_ping_status' option.
3510   *     @type string $post_password         The password to access the post. Default empty.
3511   *     @type string $post_name             The post name. Default is the sanitized post title
3512   *                                         when creating a new post.
3513   *     @type string $to_ping               Space or carriage return-separated list of URLs to ping.
3514   *                                         Default empty.
3515   *     @type string $pinged                Space or carriage return-separated list of URLs that have
3516   *                                         been pinged. Default empty.
3517   *     @type string $post_modified         The date when the post was last modified. Default is
3518   *                                         the current time.
3519   *     @type string $post_modified_gmt     The date when the post was last modified in the GMT
3520   *                                         timezone. Default is the current time.
3521   *     @type int    $post_parent           Set this for the post it belongs to, if any. Default 0.
3522   *     @type int    $menu_order            The order the post should be displayed in. Default 0.
3523   *     @type string $post_mime_type        The mime type of the post. Default empty.
3524   *     @type string $guid                  Global Unique ID for referencing the post. Default empty.
3525   *     @type array  $post_category         Array of category IDs.
3526   *                                         Defaults to value of the 'default_category' option.
3527   *     @type array  $tags_input            Array of tag names, slugs, or IDs. Default empty.
3528   *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
3529   *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
3530   * }
3531   * @param bool  $wp_error Optional. Whether to return a WP_Error on failure. Default false.
3532   * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
3533   */
3534  function wp_insert_post( $postarr, $wp_error = false ) {
3535      global $wpdb;
3536  
3537      $user_id = get_current_user_id();
3538  
3539      $defaults = array(
3540          'post_author'           => $user_id,
3541          'post_content'          => '',
3542          'post_content_filtered' => '',
3543          'post_title'            => '',
3544          'post_excerpt'          => '',
3545          'post_status'           => 'draft',
3546          'post_type'             => 'post',
3547          'comment_status'        => '',
3548          'ping_status'           => '',
3549          'post_password'         => '',
3550          'to_ping'               => '',
3551          'pinged'                => '',
3552          'post_parent'           => 0,
3553          'menu_order'            => 0,
3554          'guid'                  => '',
3555          'import_id'             => 0,
3556          'context'               => '',
3557      );
3558  
3559      $postarr = wp_parse_args( $postarr, $defaults );
3560  
3561      unset( $postarr['filter'] );
3562  
3563      $postarr = sanitize_post( $postarr, 'db' );
3564  
3565      // Are we updating or creating?
3566      $post_ID = 0;
3567      $update  = false;
3568      $guid    = $postarr['guid'];
3569  
3570      if ( ! empty( $postarr['ID'] ) ) {
3571          $update = true;
3572  
3573          // Get the post ID and GUID.
3574          $post_ID     = $postarr['ID'];
3575          $post_before = get_post( $post_ID );
3576          if ( is_null( $post_before ) ) {
3577              if ( $wp_error ) {
3578                  return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3579              }
3580              return 0;
3581          }
3582  
3583          $guid            = get_post_field( 'guid', $post_ID );
3584          $previous_status = get_post_field( 'post_status', $post_ID );
3585      } else {
3586          $previous_status = 'new';
3587      }
3588  
3589      $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3590  
3591      $post_title   = $postarr['post_title'];
3592      $post_content = $postarr['post_content'];
3593      $post_excerpt = $postarr['post_excerpt'];
3594      if ( isset( $postarr['post_name'] ) ) {
3595          $post_name = $postarr['post_name'];
3596      } elseif ( $update ) {
3597          // For an update, don't modify the post_name if it wasn't supplied as an argument.
3598          $post_name = $post_before->post_name;
3599      }
3600  
3601      $maybe_empty = 'attachment' !== $post_type
3602          && ! $post_content && ! $post_title && ! $post_excerpt
3603          && post_type_supports( $post_type, 'editor' )
3604          && post_type_supports( $post_type, 'title' )
3605          && post_type_supports( $post_type, 'excerpt' );
3606  
3607      /**
3608       * Filters whether the post should be considered "empty".
3609       *
3610       * The post is considered "empty" if both:
3611       * 1. The post type supports the title, editor, and excerpt fields
3612       * 2. The title, editor, and excerpt fields are all empty
3613       *
3614       * Returning a truthy value to the filter will effectively short-circuit
3615       * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
3616       * will be returned instead.
3617       *
3618       * @since 3.3.0
3619       *
3620       * @param bool  $maybe_empty Whether the post should be considered "empty".
3621       * @param array $postarr     Array of post data.
3622       */
3623      if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3624          if ( $wp_error ) {
3625              return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3626          } else {
3627              return 0;
3628          }
3629      }
3630  
3631      $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3632      if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3633          $post_status = 'inherit';
3634      }
3635  
3636      if ( ! empty( $postarr['post_category'] ) ) {
3637          // Filter out empty terms.
3638          $post_category = array_filter( $postarr['post_category'] );
3639      }
3640  
3641      // Make sure we set a valid category.
3642      if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
3643          // 'post' requires at least one category.
3644          if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3645              $post_category = array( get_option( 'default_category' ) );
3646          } else {
3647              $post_category = array();
3648          }
3649      }
3650  
3651      /*
3652       * Don't allow contributors to set the post slug for pending review posts.
3653       *
3654       * For new posts check the primitive capability, for updates check the meta capability.
3655       */
3656      $post_type_object = get_post_type_object( $post_type );
3657  
3658      if ( ! $update && 'pending' === $post_status && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
3659          $post_name = '';
3660      } elseif ( $update && 'pending' === $post_status && ! current_user_can( 'publish_post', $post_ID ) ) {
3661          $post_name = '';
3662      }
3663  
3664      /*
3665       * Create a valid post name. Drafts and pending posts are allowed to have
3666       * an empty post name.
3667       */
3668      if ( empty( $post_name ) ) {
3669          if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3670              $post_name = sanitize_title( $post_title );
3671          } else {
3672              $post_name = '';
3673          }
3674      } else {
3675          // On updates, we need to check to see if it's using the old, fixed sanitization context.
3676          $check_name = sanitize_title( $post_name, '', 'old-save' );
3677          if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3678              $post_name = $check_name;
3679          } else { // new post, or slug has changed.
3680              $post_name = sanitize_title( $post_name );
3681          }
3682      }
3683  
3684      /*
3685       * If the post date is empty (due to having been new or a draft) and status
3686       * is not 'draft' or 'pending', set date to now.
3687       */
3688      if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3689          if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3690              $post_date = current_time( 'mysql' );
3691          } else {
3692              $post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3693          }
3694      } else {
3695          $post_date = $postarr['post_date'];
3696      }
3697  
3698      // Validate the date.
3699      $mm         = substr( $post_date, 5, 2 );
3700      $jj         = substr( $post_date, 8, 2 );
3701      $aa         = substr( $post_date, 0, 4 );
3702      $valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3703      if ( ! $valid_date ) {
3704          if ( $wp_error ) {
3705              return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
3706          } else {
3707              return 0;
3708          }
3709      }
3710  
3711      if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3712          if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3713              $post_date_gmt = get_gmt_from_date( $post_date );
3714          } else {
3715              $post_date_gmt = '0000-00-00 00:00:00';
3716          }
3717      } else {
3718          $post_date_gmt = $postarr['post_date_gmt'];
3719      }
3720  
3721      if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3722          $post_modified     = current_time( 'mysql' );
3723          $post_modified_gmt = current_time( 'mysql', 1 );
3724      } else {
3725          $post_modified     = $post_date;
3726          $post_modified_gmt = $post_date_gmt;
3727      }
3728  
3729      if ( 'attachment' !== $post_type ) {
3730          if ( 'publish' === $post_status ) {
3731              // String comparison to work around far future dates (year 2038+) on 32-bit systems.
3732              if ( $post_date_gmt > gmdate( 'Y-m-d H:i:59' ) ) {
3733                  $post_status = 'future';
3734              }
3735          } elseif ( 'future' === $post_status ) {
3736              if ( $post_date_gmt <= gmdate( 'Y-m-d H:i:59' ) ) {
3737                  $post_status = 'publish';
3738              }
3739          }
3740      }
3741  
3742      // Comment status.
3743      if ( empty( $postarr['comment_status'] ) ) {
3744          if ( $update ) {
3745              $comment_status = 'closed';
3746          } else {
3747              $comment_status = get_default_comment_status( $post_type );
3748          }
3749      } else {
3750          $comment_status = $postarr['comment_status'];
3751      }
3752  
3753      // These variables are needed by compact() later.
3754      $post_content_filtered = $postarr['post_content_filtered'];
3755      $post_author           = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3756      $ping_status           = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3757      $to_ping               = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3758      $pinged                = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3759      $import_id             = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3760  
3761      /*
3762       * The 'wp_insert_post_parent' filter expects all variables to be present.
3763       * Previously, these variables would have already been extracted
3764       */
3765      if ( isset( $postarr['menu_order'] ) ) {
3766          $menu_order = (int) $postarr['menu_order'];
3767      } else {
3768          $menu_order = 0;
3769      }
3770  
3771      $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3772      if ( 'private' == $post_status ) {
3773          $post_password = '';
3774      }
3775  
3776      if ( isset( $postarr['post_parent'] ) ) {
3777          $post_parent = (int) $postarr['post_parent'];
3778      } else {
3779          $post_parent = 0;
3780      }
3781  
3782      $new_postarr = array_merge(
3783          array(
3784              'ID' => $post_ID,
3785          ),
3786          compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
3787      );
3788  
3789      /**
3790       * Filters the post parent -- used to check for and prevent hierarchy loops.
3791       *
3792       * @since 3.1.0
3793       *
3794       * @param int   $post_parent Post parent ID.
3795       * @param int   $post_ID     Post ID.
3796       * @param array $new_postarr Array of parsed post data.
3797       * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
3798       */
3799      $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
3800  
3801      /*
3802       * If the post is being untrashed and it has a desired slug stored in post meta,
3803       * reassign it.
3804       */
3805      if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3806          $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3807          if ( $desired_post_slug ) {
3808              delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3809              $post_name = $desired_post_slug;
3810          }
3811      }
3812  
3813      // If a trashed post has the desired slug, change it and let this post have it.
3814      if ( 'trash' !== $post_status && $post_name ) {
3815          wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3816      }
3817  
3818      // When trashing an existing post, change its slug to allow non-trashed posts to use it.
3819      if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3820          $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3821      }
3822  
3823      $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3824  
3825      // Don't unslash.
3826      $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3827  
3828      // Expected_slashed (everything!).
3829      $data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_content_filtered', 'post_title', 'post_excerpt', 'post_status', 'post_type', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_parent', 'menu_order', 'post_mime_type', 'guid' );
3830  
3831      $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3832  
3833      foreach ( $emoji_fields as $emoji_field ) {
3834          if ( isset( $data[ $emoji_field ] ) ) {
3835              $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3836              if ( 'utf8' === $charset ) {
3837                  $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3838              }
3839          }
3840      }
3841  
3842      if ( 'attachment' === $post_type ) {
3843          /**
3844           * Filters attachment post data before it is updated in or added to the database.
3845           *
3846           * @since 3.9.0
3847           *
3848           * @param array $data    An array of sanitized attachment post data.
3849           * @param array $postarr An array of unsanitized attachment post data.
3850           */
3851          $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3852      } else {
3853          /**
3854           * Filters slashed post data just before it is inserted into the database.
3855           *
3856           * @since 2.7.0
3857           *
3858           * @param array $data    An array of slashed post data.
3859           * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3860           */
3861          $data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3862      }
3863      $data  = wp_unslash( $data );
3864      $where = array( 'ID' => $post_ID );
3865  
3866      if ( $update ) {
3867          /**
3868           * Fires immediately before an existing post is updated in the database.
3869           *
3870           * @since 2.5.0
3871           *
3872           * @param int   $post_ID Post ID.
3873           * @param array $data    Array of unslashed post data.
3874           */
3875          do_action( 'pre_post_update', $post_ID, $data );
3876          if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3877              if ( $wp_error ) {
3878                  return new WP_Error( 'db_update_error', __( 'Could not update post in the database' ), $wpdb->last_error );
3879              } else {
3880                  return 0;
3881              }
3882          }
3883      } else {
3884          // If there is a suggested ID, use it if not already present.
3885          if ( ! empty( $import_id ) ) {
3886              $import_id = (int) $import_id;
3887              if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
3888                  $data['ID'] = $import_id;
3889              }
3890          }
3891          if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3892              if ( $wp_error ) {
3893                  return new WP_Error( 'db_insert_error', __( 'Could not insert post into the database' ), $wpdb->last_error );
3894              } else {
3895                  return 0;
3896              }
3897          }
3898          $post_ID = (int) $wpdb->insert_id;
3899  
3900          // Use the newly generated $post_ID.
3901          $where = array( 'ID' => $post_ID );
3902      }
3903  
3904      if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3905          $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3906          $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3907          clean_post_cache( $post_ID );
3908      }
3909  
3910      if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3911          wp_set_post_categories( $post_ID, $post_category );
3912      }
3913  
3914      if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3915          wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3916      }
3917  
3918      // New-style support for all custom taxonomies.
3919      if ( ! empty( $postarr['tax_input'] ) ) {
3920          foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3921              $taxonomy_obj = get_taxonomy( $taxonomy );
3922              if ( ! $taxonomy_obj ) {
3923                  /* translators: %s: Taxonomy name. */
3924                  _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3925                  continue;
3926              }
3927  
3928              // array = hierarchical, string = non-hierarchical.
3929              if ( is_array( $tags ) ) {
3930                  $tags = array_filter( $tags );
3931              }
3932              if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3933                  wp_set_post_terms( $post_ID, $tags, $taxonomy );
3934              }
3935          }
3936      }
3937  
3938      if ( ! empty( $postarr['meta_input'] ) ) {
3939          foreach ( $postarr['meta_input'] as $field => $value ) {
3940              update_post_meta( $post_ID, $field, $value );
3941          }
3942      }
3943  
3944      $current_guid = get_post_field( 'guid', $post_ID );
3945  
3946      // Set GUID.
3947      if ( ! $update && '' == $current_guid ) {
3948          $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3949      }
3950  
3951      if ( 'attachment' === $postarr['post_type'] ) {
3952          if ( ! empty( $postarr['file'] ) ) {
3953              update_attached_file( $post_ID, $postarr['file'] );
3954          }
3955  
3956          if ( ! empty( $postarr['context'] ) ) {
3957              add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3958          }
3959      }
3960  
3961      // Set or remove featured image.
3962      if ( isset( $postarr['_thumbnail_id'] ) ) {
3963          $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3964          if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3965              if ( wp_attachment_is( 'audio', $post_ID ) ) {
3966                  $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3967              } elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3968                  $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3969              }
3970          }
3971  
3972          if ( $thumbnail_support ) {
3973              $thumbnail_id = intval( $postarr['_thumbnail_id'] );
3974              if ( -1 === $thumbnail_id ) {
3975                  delete_post_thumbnail( $post_ID );
3976              } else {
3977                  set_post_thumbnail( $post_ID, $thumbnail_id );
3978              }
3979          }
3980      }
3981  
3982      clean_post_cache( $post_ID );
3983  
3984      $post = get_post( $post_ID );
3985  
3986      if ( ! empty( $postarr['page_template'] ) ) {
3987          $post->page_template = $postarr['page_template'];
3988          $page_templates      = wp_get_theme()->get_page_templates( $post );
3989          if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
3990              if ( $wp_error ) {
3991                  return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
3992              }
3993              update_post_meta( $post_ID, '_wp_page_template', 'default' );
3994          } else {
3995              update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
3996          }
3997      }
3998  
3999      if ( 'attachment' !== $postarr['post_type'] ) {
4000          wp_transition_post_status( $data['post_status'], $previous_status, $post );
4001      } else {
4002          if ( $update ) {
4003              /**
4004               * Fires once an existing attachment has been updated.
4005               *
4006               * @since 2.0.0
4007               *
4008               * @param int $post_ID Attachment ID.
4009               */
4010              do_action( 'edit_attachment', $post_ID );
4011              $post_after = get_post( $post_ID );
4012  
4013              /**
4014               * Fires once an existing attachment has been updated.
4015               *
4016               * @since 4.4.0
4017               *
4018               * @param int     $post_ID      Post ID.
4019               * @param WP_Post $post_after   Post object following the update.
4020               * @param WP_Post $post_before  Post object before the update.
4021               */
4022              do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
4023          } else {
4024  
4025              /**
4026               * Fires once an attachment has been added.
4027               *
4028               * @since 2.0.0
4029               *
4030               * @param int $post_ID Attachment ID.
4031               */
4032              do_action( 'add_attachment', $post_ID );
4033          }
4034  
4035          return $post_ID;
4036      }
4037  
4038      if ( $update ) {
4039          /**
4040           * Fires once an existing post has been updated.
4041           *
4042           * The dynamic portion of the hook name, `$post->post_type`, refers to
4043           * the post type slug.
4044           *
4045           * @since 5.1.0
4046           *
4047           * @param int     $post_ID Post ID.
4048           * @param WP_Post $post    Post object.
4049           */
4050          do_action( "edit_post_{$post->post_type}", $post_ID, $post );
4051  
4052          /**
4053           * Fires once an existing post has been updated.
4054           *
4055           * @since 1.2.0
4056           *
4057           * @param int     $post_ID Post ID.
4058           * @param WP_Post $post    Post object.
4059           */
4060          do_action( 'edit_post', $post_ID, $post );
4061  
4062          $post_after = get_post( $post_ID );
4063  
4064          /**
4065           * Fires once an existing post has been updated.
4066           *
4067           * @since 3.0.0
4068           *
4069           * @param int     $post_ID      Post ID.
4070           * @param WP_Post $post_after   Post object following the update.
4071           * @param WP_Post $post_before  Post object before the update.
4072           */
4073          do_action( 'post_updated', $post_ID, $post_after, $post_before );
4074      }
4075  
4076      /**
4077       * Fires once a post has been saved.
4078       *
4079       * The dynamic portion of the hook name, `$post->post_type`, refers to
4080       * the post type slug.
4081       *
4082       * @since 3.7.0
4083       *
4084       * @param int     $post_ID Post ID.
4085       * @param WP_Post $post    Post object.
4086       * @param bool    $update  Whether this is an existing post being updated or not.
4087       */
4088      do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
4089  
4090      /**
4091       * Fires once a post has been saved.
4092       *
4093       * @since 1.5.0
4094       *
4095       * @param int     $post_ID Post ID.
4096       * @param WP_Post $post    Post object.
4097       * @param bool    $update  Whether this is an existing post being updated or not.
4098       */
4099      do_action( 'save_post', $post_ID, $post, $update );
4100  
4101      /**
4102       * Fires once a post has been saved.
4103       *
4104       * @since 2.0.0
4105       *
4106       * @param int     $post_ID Post ID.
4107       * @param WP_Post $post    Post object.
4108       * @param bool    $update  Whether this is an existing post being updated or not.
4109       */
4110      do_action( 'wp_insert_post', $post_ID, $post, $update );
4111  
4112      return $post_ID;
4113  }
4114  
4115  /**
4116   * Update a post with new post data.
4117   *
4118   * The date does not have to be set for drafts. You can set the date and it will
4119   * not be overridden.
4120   *
4121   * @since 1.0.0
4122   *
4123   * @param array|object $postarr  Optional. Post data. Arrays are expected to be escaped,
4124   *                               objects are not. Default array.
4125   * @param bool         $wp_error Optional. Allow return of WP_Error on failure. Default false.
4126   * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
4127   */
4128  function wp_update_post( $postarr = array(), $wp_error = false ) {
4129      if ( is_object( $postarr ) ) {
4130          // Non-escaped post was passed.
4131          $postarr = get_object_vars( $postarr );
4132          $postarr = wp_slash( $postarr );
4133      }
4134  
4135      // First, get all of the original fields.
4136      $post = get_post( $postarr['ID'], ARRAY_A );
4137  
4138      if ( is_null( $post ) ) {
4139          if ( $wp_error ) {
4140              return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
4141          }
4142          return 0;
4143      }
4144  
4145      // Escape data pulled from DB.
4146      $post = wp_slash( $post );
4147  
4148      // Passed post category list overwrites existing category list if not empty.
4149      if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
4150              && 0 != count( $postarr['post_category'] ) ) {
4151          $post_cats = $postarr['post_category'];
4152      } else {
4153          $post_cats = $post['post_category'];
4154      }
4155  
4156      // Drafts shouldn't be assigned a date unless explicitly done so by the user.
4157      if ( isset( $post['post_status'] ) && in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) && empty( $postarr['edit_date'] ) &&
4158              ( '0000-00-00 00:00:00' == $post['post_date_gmt'] ) ) {
4159          $clear_date = true;
4160      } else {
4161          $clear_date = false;
4162      }
4163  
4164      // Merge old and new fields with new fields overwriting old ones.
4165      $postarr                  = array_merge( $post, $postarr );
4166      $postarr['post_category'] = $post_cats;
4167      if ( $clear_date ) {
4168          $postarr['post_date']     = current_time( 'mysql' );
4169          $postarr['post_date_gmt'] = '';
4170      }
4171  
4172      if ( $postarr['post_type'] == 'attachment' ) {
4173          return wp_insert_attachment( $postarr, false, 0, $wp_error );
4174      }
4175  
4176      return wp_insert_post( $postarr, $wp_error );
4177  }
4178  
4179  /**
4180   * Publish a post by transitioning the post status.
4181   *
4182   * @since 2.1.0
4183   *
4184   * @global wpdb $wpdb WordPress database abstraction object.
4185   *
4186   * @param int|WP_Post $post Post ID or post object.
4187   */
4188  function wp_publish_post( $post ) {
4189      global $wpdb;
4190  
4191      $post = get_post( $post );
4192      if ( ! $post ) {
4193          return;
4194      }
4195  
4196      if ( 'publish' == $post->post_status ) {
4197          return;
4198      }
4199  
4200      $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
4201  
4202      clean_post_cache( $post->ID );
4203  
4204      $old_status        = $post->post_status;
4205      $post->post_status = 'publish';
4206      wp_transition_post_status( 'publish', $old_status, $post );
4207  
4208      /** This action is documented in wp-includes/post.php */
4209      do_action( "edit_post_{$post->post_type}", $post->ID, $post );
4210  
4211      /** This action is documented in wp-includes/post.php */
4212      do_action( 'edit_post', $post->ID, $post );
4213  
4214      /** This action is documented in wp-includes/post.php */
4215      do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
4216  
4217      /** This action is documented in wp-includes/post.php */
4218      do_action( 'save_post', $post->ID, $post, true );
4219  
4220      /** This action is documented in wp-includes/post.php */
4221      do_action( 'wp_insert_post', $post->ID, $post, true );
4222  }
4223  
4224  /**
4225   * Publish future post and make sure post ID has future post status.
4226   *
4227   * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
4228   * from publishing drafts, etc.
4229   *
4230   * @since 2.5.0
4231   *
4232   * @param int|WP_Post $post_id Post ID or post object.
4233   */
4234  function check_and_publish_future_post( $post_id ) {
4235      $post = get_post( $post_id );
4236  
4237      if ( empty( $post ) ) {
4238          return;
4239      }
4240  
4241      if ( 'future' != $post->post_status ) {
4242          return;
4243      }
4244  
4245      $time = strtotime( $post->post_date_gmt . ' GMT' );
4246  
4247      // Uh oh, someone jumped the gun!
4248      if ( $time > time() ) {
4249          wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
4250          wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
4251          return;
4252      }
4253  
4254      // wp_publish_post() returns no meaningful value.
4255      wp_publish_post( $post_id );
4256  }
4257  
4258  /**
4259   * Computes a unique slug for the post, when given the desired slug and some post details.
4260   *
4261   * @since 2.8.0
4262   *
4263   * @global wpdb       $wpdb       WordPress database abstraction object.
4264   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
4265   *
4266   * @param string $slug        The desired slug (post_name).
4267   * @param int    $post_ID     Post ID.
4268   * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
4269   * @param string $post_type   Post type.
4270   * @param int    $post_parent Post parent ID.
4271   * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
4272   */
4273  function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
4274      if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) || 'user_request' === $post_type ) {
4275          return $slug;
4276      }
4277  
4278      /**
4279       * Filters the post slug before it is generated to be unique.
4280       *
4281       * Returning a non-null value will short-circuit the
4282       * unique slug generation, returning the passed value instead.
4283       *
4284       * @since 5.1.0
4285       *
4286       * @param string $override_slug Short-circuit return value.
4287       * @param string $slug          The desired slug (post_name).
4288       * @param int    $post_ID       Post ID.
4289       * @param string $post_status   The post status.
4290       * @param string $post_type     Post type.
4291       * @param int    $post_parent   Post parent ID.
4292       */
4293      $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
4294      if ( null !== $override_slug ) {
4295          return $override_slug;
4296      }
4297  
4298      global $wpdb, $wp_rewrite;
4299  
4300      $original_slug = $slug;
4301  
4302      $feeds = $wp_rewrite->feeds;
4303      if ( ! is_array( $feeds ) ) {
4304          $feeds = array();
4305      }
4306  
4307      if ( 'attachment' == $post_type ) {
4308          // Attachment slugs must be unique across all types.
4309          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
4310          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
4311  
4312          /**
4313           * Filters whether the post slug would make a bad attachment slug.
4314           *
4315           * @since 3.1.0
4316           *
4317           * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
4318           * @param string $slug     The post slug.
4319           */
4320          if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
4321              $suffix = 2;
4322              do {
4323                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4324                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
4325                  $suffix++;
4326              } while ( $post_name_check );
4327              $slug = $alt_post_name;
4328          }
4329      } elseif ( is_post_type_hierarchical( $post_type ) ) {
4330          if ( 'nav_menu_item' == $post_type ) {
4331              return $slug;
4332          }
4333  
4334          /*
4335           * Page slugs must be unique within their own trees. Pages are in a separate
4336           * namespace than posts so page slugs are allowed to overlap post slugs.
4337           */
4338          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
4339          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
4340  
4341          /**
4342           * Filters whether the post slug would make a bad hierarchical post slug.
4343           *
4344           * @since 3.1.0
4345           *
4346           * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
4347           * @param string $slug        The post slug.
4348           * @param string $post_type   Post type.
4349           * @param int    $post_parent Post parent ID.
4350           */
4351          if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug ) || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) {
4352              $suffix = 2;
4353              do {
4354                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4355                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
4356                  $suffix++;
4357              } while ( $post_name_check );
4358              $slug = $alt_post_name;
4359          }
4360      } else {
4361          // Post slugs must be unique across all posts.
4362          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
4363          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
4364  
4365          // Prevent new post slugs that could result in URLs that conflict with date archives.
4366          $post                        = get_post( $post_ID );
4367          $conflicts_with_date_archive = false;
4368          if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
4369              $slug_num = intval( $slug );
4370  
4371              if ( $slug_num ) {
4372                  $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
4373                  $postname_index = array_search( '%postname%', $permastructs );
4374  
4375                  /*
4376                  * Potential date clashes are as follows:
4377                  *
4378                  * - Any integer in the first permastruct position could be a year.
4379                  * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
4380                  * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
4381                  */
4382                  if ( 0 === $postname_index ||
4383                      ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
4384                      ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
4385                  ) {
4386                      $conflicts_with_date_archive = true;
4387                  }
4388              }
4389          }
4390  
4391          /**
4392           * Filters whether the post slug would be bad as a flat slug.
4393           *
4394           * @since 3.1.0
4395           *
4396           * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
4397           * @param string $slug      The post slug.
4398           * @param string $post_type Post type.
4399           */
4400          if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || $conflicts_with_date_archive || apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type ) ) {
4401              $suffix = 2;
4402              do {
4403                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4404                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
4405                  $suffix++;
4406              } while ( $post_name_check );
4407              $slug = $alt_post_name;
4408          }
4409      }
4410  
4411      /**
4412       * Filters the unique post slug.
4413       *
4414       * @since 3.3.0
4415       *
4416       * @param string $slug          The post slug.
4417       * @param int    $post_ID       Post ID.
4418       * @param string $post_status   The post status.
4419       * @param string $post_type     Post type.
4420       * @param int    $post_parent   Post parent ID
4421       * @param string $original_slug The original post slug.
4422       */
4423      return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
4424  }
4425  
4426  /**
4427   * Truncate a post slug.
4428   *
4429   * @since 3.6.0
4430   * @access private
4431   *
4432   * @see utf8_uri_encode()
4433   *
4434   * @param string $slug   The slug to truncate.
4435   * @param int    $length Optional. Max length of the slug. Default 200 (characters).
4436   * @return string The truncated slug.
4437   */
4438  function _truncate_post_slug( $slug, $length = 200 ) {
4439      if ( strlen( $slug ) > $length ) {
4440          $decoded_slug = urldecode( $slug );
4441          if ( $decoded_slug === $slug ) {
4442              $slug = substr( $slug, 0, $length );
4443          } else {
4444              $slug = utf8_uri_encode( $decoded_slug, $length );
4445          }
4446      }
4447  
4448      return rtrim( $slug, '-' );
4449  }
4450  
4451  /**
4452   * Add tags to a post.
4453   *
4454   * @see wp_set_post_tags()
4455   *
4456   * @since 2.3.0
4457   *
4458   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4459   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4460   *                              separated by commas. Default empty.
4461   * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
4462   */
4463  function wp_add_post_tags( $post_id = 0, $tags = '' ) {
4464      return wp_set_post_tags( $post_id, $tags, true );
4465  }
4466  
4467  /**
4468   * Set the tags for a post.
4469   *
4470   * @since 2.3.0
4471   *
4472   * @see wp_set_object_terms()
4473   *
4474   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4475   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4476   *                              separated by commas. Default empty.
4477   * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
4478   *                              replace the tags with the new tags. Default false.
4479   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4480   */
4481  function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
4482      return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
4483  }
4484  
4485  /**
4486   * Set the terms for a post.
4487   *
4488   * @since 2.8.0
4489   *
4490   * @see wp_set_object_terms()
4491   *
4492   * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
4493   * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
4494   *                               separated by commas. Hierarchical taxonomies must always pass IDs rather
4495   *                               than names so that children with the same names but different parents
4496   *                               aren't confused. Default empty.
4497   * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
4498   * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
4499   *                               replace the terms with the new terms. Default false.
4500   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4501   */
4502  function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
4503      $post_id = (int) $post_id;
4504  
4505      if ( ! $post_id ) {
4506          return false;
4507      }
4508  
4509      if ( empty( $tags ) ) {
4510          $tags = array();
4511      }
4512  
4513      if ( ! is_array( $tags ) ) {
4514          $comma = _x( ',', 'tag delimiter' );
4515          if ( ',' !== $comma ) {
4516              $tags = str_replace( $comma, ',', $tags );
4517          }
4518          $tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
4519      }
4520  
4521      /*
4522       * Hierarchical taxonomies must always pass IDs rather than names so that
4523       * children with the same names but different parents aren't confused.
4524       */
4525      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
4526          $tags = array_unique( array_map( 'intval', $tags ) );
4527      }
4528  
4529      return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
4530  }
4531  
4532  /**
4533   * Set categories for a post.
4534   *
4535   * If the post categories parameter is not set, then the default category is
4536   * going used.
4537   *
4538   * @since 2.1.0
4539   *
4540   * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
4541   *                                   of the global $post. Default 0.
4542   * @param array|int $post_categories Optional. List of category IDs, or the ID of a single category.
4543   *                                   Default empty array.
4544   * @param bool      $append          If true, don't delete existing categories, just add on.
4545   *                                   If false, replace the categories with the new categories.
4546   * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
4547   */
4548  function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
4549      $post_ID     = (int) $post_ID;
4550      $post_type   = get_post_type( $post_ID );
4551      $post_status = get_post_status( $post_ID );
4552      // If $post_categories isn't already an array, make it one:
4553      $post_categories = (array) $post_categories;
4554      if ( empty( $post_categories ) ) {
4555          if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
4556              $post_categories = array( get_option( 'default_category' ) );
4557              $append          = false;
4558          } else {
4559              $post_categories = array();
4560          }
4561      } elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
4562          return true;
4563      }
4564  
4565      return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
4566  }
4567  
4568  /**
4569   * Fires actions related to the transitioning of a post's status.
4570   *
4571   * When a post is saved, the post status is "transitioned" from one status to another,
4572   * though this does not always mean the status has actually changed before and after
4573   * the save. This function fires a number of action hooks related to that transition:
4574   * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
4575   * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
4576   * that the function does not transition the post object in the database.
4577   *
4578   * For instance: When publishing a post for the first time, the post status may transition
4579   * from 'draft' – or some other status – to 'publish'. However, if a post is already
4580   * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
4581   * before and after the transition.
4582   *
4583   * @since 2.3.0
4584   *
4585   * @param string  $new_status Transition to this post status.
4586   * @param string  $old_status Previous post status.
4587   * @param WP_Post $post Post data.
4588   */
4589  function wp_transition_post_status( $new_status, $old_status, $post ) {
4590      /**
4591       * Fires when a post is transitioned from one status to another.
4592       *
4593       * @since 2.3.0
4594       *
4595       * @param string  $new_status New post status.
4596       * @param string  $old_status Old post status.
4597       * @param WP_Post $post       Post object.
4598       */
4599      do_action( 'transition_post_status', $new_status, $old_status, $post );
4600  
4601      /**
4602       * Fires when a post is transitioned from one status to another.
4603       *
4604       * The dynamic portions of the hook name, `$new_status` and `$old status`,
4605       * refer to the old and new post statuses, respectively.
4606       *
4607       * @since 2.3.0
4608       *
4609       * @param WP_Post $post Post object.
4610       */
4611      do_action( "{$old_status}_to_{$new_status}", $post );
4612  
4613      /**
4614       * Fires when a post is transitioned from one status to another.
4615       *
4616       * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
4617       * refer to the new post status and post type, respectively.
4618       *
4619       * Please note: When this action is hooked using a particular post status (like
4620       * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
4621       * first transitioned to that status from something else, as well as upon
4622       * subsequent post updates (old and new status are both the same).
4623       *
4624       * Therefore, if you are looking to only fire a callback when a post is first
4625       * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
4626       *
4627       * @since 2.3.0
4628       *
4629       * @param int     $post_id Post ID.
4630       * @param WP_Post $post    Post object.
4631       */
4632      do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
4633  }
4634  
4635  //
4636  // Comment, trackback, and pingback functions.
4637  //
4638  
4639  /**
4640   * Add a URL to those already pinged.
4641   *
4642   * @since 1.5.0
4643   * @since 4.7.0 `$post_id` can be a WP_Post object.
4644   * @since 4.7.0 `$uri` can be an array of URIs.
4645   *
4646   * @global wpdb $wpdb WordPress database abstraction object.
4647   *
4648   * @param int|WP_Post  $post_id Post object or ID.
4649   * @param string|array $uri     Ping URI or array of URIs.
4650   * @return int|false How many rows were updated.
4651   */
4652  function add_ping( $post_id, $uri ) {
4653      global $wpdb;
4654  
4655      $post = get_post( $post_id );
4656      if ( ! $post ) {
4657          return false;
4658      }
4659  
4660      $pung = trim( $post->pinged );
4661      $pung = preg_split( '/\s/', $pung );
4662  
4663      if ( is_array( $uri ) ) {
4664          $pung = array_merge( $pung, $uri );
4665      } else {
4666          $pung[] = $uri;
4667      }
4668      $new = implode( "\n", $pung );
4669  
4670      /**
4671       * Filters the new ping URL to add for the given post.
4672       *
4673       * @since 2.0.0
4674       *
4675       * @param string $new New ping URL to add.
4676       */
4677      $new = apply_filters( 'add_ping', $new );
4678  
4679      $return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
4680      clean_post_cache( $post->ID );
4681      return $return;
4682  }
4683  
4684  /**
4685   * Retrieve enclosures already enclosed for a post.
4686   *
4687   * @since 1.5.0
4688   *
4689   * @param int $post_id Post ID.
4690   * @return array List of enclosures.
4691   */
4692  function get_enclosed( $post_id ) {
4693      $custom_fields = get_post_custom( $post_id );
4694      $pung          = array();
4695      if ( ! is_array( $custom_fields ) ) {
4696          return $pung;
4697      }
4698  
4699      foreach ( $custom_fields as $key => $val ) {
4700          if ( 'enclosure' != $key || ! is_array( $val ) ) {
4701              continue;
4702          }
4703          foreach ( $val as $enc ) {
4704              $enclosure = explode( "\n", $enc );
4705              $pung[]    = trim( $enclosure[0] );
4706          }
4707      }
4708  
4709      /**
4710       * Filters the list of enclosures already enclosed for the given post.
4711       *
4712       * @since 2.0.0
4713       *
4714       * @param array $pung    Array of enclosures for the given post.
4715       * @param int   $post_id Post ID.
4716       */
4717      return apply_filters( 'get_enclosed', $pung, $post_id );
4718  }
4719  
4720  /**
4721   * Retrieve URLs already pinged for a post.
4722   *
4723   * @since 1.5.0
4724   *
4725   * @since 4.7.0 `$post_id` can be a WP_Post object.
4726   *
4727   * @param int|WP_Post $post_id Post ID or object.
4728   * @return bool|string[] Array of URLs already pinged for the given post, false if the post is not found.
4729   */
4730  function get_pung( $post_id ) {
4731      $post = get_post( $post_id );
4732      if ( ! $post ) {
4733          return false;
4734      }
4735  
4736      $pung = trim( $post->pinged );
4737      $pung = preg_split( '/\s/', $pung );
4738  
4739      /**
4740       * Filters the list of already-pinged URLs for the given post.
4741       *
4742       * @since 2.0.0
4743       *
4744       * @param string[] $pung Array of URLs already pinged for the given post.
4745       */
4746      return apply_filters( 'get_pung', $pung );
4747  }
4748  
4749  /**
4750   * Retrieve URLs that need to be pinged.
4751   *
4752   * @since 1.5.0
4753   * @since 4.7.0 `$post_id` can be a WP_Post object.
4754   *
4755   * @param int|WP_Post $post_id Post Object or ID
4756   * @return array
4757   */
4758  function get_to_ping( $post_id ) {
4759      $post = get_post( $post_id );
4760  
4761      if ( ! $post ) {
4762          return false;
4763      }
4764  
4765      $to_ping = sanitize_trackback_urls( $post->to_ping );
4766      $to_ping = preg_split( '/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY );
4767  
4768      /**
4769       * Filters the list of URLs yet to ping for the given post.
4770       *
4771       * @since 2.0.0
4772       *
4773       * @param array $to_ping List of URLs yet to ping.
4774       */
4775      return apply_filters( 'get_to_ping', $to_ping );
4776  }
4777  
4778  /**
4779   * Do trackbacks for a list of URLs.
4780   *
4781   * @since 1.0.0
4782   *
4783   * @param string $tb_list Comma separated list of URLs.
4784   * @param int    $post_id Post ID.
4785   */
4786  function trackback_url_list( $tb_list, $post_id ) {
4787      if ( ! empty( $tb_list ) ) {
4788          // Get post data.
4789          $postdata = get_post( $post_id, ARRAY_A );
4790  
4791          // Form an excerpt.
4792          $excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4793  
4794          if ( strlen( $excerpt ) > 255 ) {
4795              $excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
4796          }
4797  
4798          $trackback_urls = explode( ',', $tb_list );
4799          foreach ( (array) $trackback_urls as $tb_url ) {
4800              $tb_url = trim( $tb_url );
4801              trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
4802          }
4803      }
4804  }
4805  
4806  //
4807  // Page functions
4808  //
4809  
4810  /**
4811   * Get a list of page IDs.
4812   *
4813   * @since 2.0.0
4814   *
4815   * @global wpdb $wpdb WordPress database abstraction object.
4816   *
4817   * @return array List of page IDs.
4818   */
4819  function get_all_page_ids() {
4820      global $wpdb;
4821  
4822      $page_ids = wp_cache_get( 'all_page_ids', 'posts' );
4823      if ( ! is_array( $page_ids ) ) {
4824          $page_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'page'" );
4825          wp_cache_add( 'all_page_ids', $page_ids, 'posts' );
4826      }
4827  
4828      return $page_ids;
4829  }
4830  
4831  /**
4832   * Retrieves page data given a page ID or page object.
4833   *
4834   * Use get_post() instead of get_page().
4835   *
4836   * @since 1.5.1
4837   * @deprecated 3.5.0 Use get_post()
4838   *
4839   * @param mixed  $page   Page object or page ID. Passed by reference.
4840   * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4841   *                       a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4842   * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4843   *                       'edit', 'db', 'display'. Default 'raw'.
4844   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4845   */
4846  function get_page( $page, $output = OBJECT, $filter = 'raw' ) {
4847      return get_post( $page, $output, $filter );
4848  }
4849  
4850  /**
4851   * Retrieves a page given its path.
4852   *
4853   * @since 2.1.0
4854   *
4855   * @global wpdb $wpdb WordPress database abstraction object.
4856   *
4857   * @param string       $page_path Page path.
4858   * @param string       $output    Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4859   *                                a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4860   * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4861   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4862   */
4863  function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4864      global $wpdb;
4865  
4866      $last_changed = wp_cache_get_last_changed( 'posts' );
4867  
4868      $hash      = md5( $page_path . serialize( $post_type ) );
4869      $cache_key = "get_page_by_path:$hash:$last_changed";
4870      $cached    = wp_cache_get( $cache_key, 'posts' );
4871      if ( false !== $cached ) {
4872          // Special case: '0' is a bad `$page_path`.
4873          if ( '0' === $cached || 0 === $cached ) {
4874              return;
4875          } else {
4876              return get_post( $cached, $output );
4877          }
4878      }
4879  
4880      $page_path     = rawurlencode( urldecode( $page_path ) );
4881      $page_path     = str_replace( '%2F', '/', $page_path );
4882      $page_path     = str_replace( '%20', ' ', $page_path );
4883      $parts         = explode( '/', trim( $page_path, '/' ) );
4884      $parts         = array_map( 'sanitize_title_for_query', $parts );
4885      $escaped_parts = esc_sql( $parts );
4886  
4887      $in_string = "'" . implode( "','", $escaped_parts ) . "'";
4888  
4889      if ( is_array( $post_type ) ) {
4890          $post_types = $post_type;
4891      } else {
4892          $post_types = array( $post_type, 'attachment' );
4893      }
4894  
4895      $post_types          = esc_sql( $post_types );
4896      $post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4897      $sql                 = "
4898          SELECT ID, post_name, post_parent, post_type
4899          FROM $wpdb->posts
4900          WHERE post_name IN ($in_string)
4901          AND post_type IN ($post_type_in_string)
4902      ";
4903  
4904      $pages = $wpdb->get_results( $sql, OBJECT_K );
4905  
4906      $revparts = array_reverse( $parts );
4907  
4908      $foundid = 0;
4909      foreach ( (array) $pages as $page ) {
4910          if ( $page->post_name == $revparts[0] ) {
4911              $count = 0;
4912              $p     = $page;
4913  
4914              /*
4915               * Loop through the given path parts from right to left,
4916               * ensuring each matches the post ancestry.
4917               */
4918              while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4919                  $count++;
4920                  $parent = $pages[ $p->post_parent ];
4921                  if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] ) {
4922                      break;
4923                  }
4924                  $p = $parent;
4925              }
4926  
4927              if ( $p->post_parent == 0 && $count + 1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4928                  $foundid = $page->ID;
4929                  if ( $page->post_type == $post_type ) {
4930                      break;
4931                  }
4932              }
4933          }
4934      }
4935  
4936      // We cache misses as well as hits.
4937      wp_cache_set( $cache_key, $foundid, 'posts' );
4938  
4939      if ( $foundid ) {
4940          return get_post( $foundid, $output );
4941      }
4942  }
4943  
4944  /**
4945   * Retrieve a page given its title.
4946   *
4947   * If more than one post uses the same title, the post with the smallest ID will be returned.
4948   * Be careful: in case of more than one post having the same title, it will check the oldest
4949   * publication date, not the smallest ID.
4950   *
4951   * Because this function uses the MySQL '=' comparison, $page_title will usually be matched
4952   * as case-insensitive with default collation.
4953   *
4954   * @since 2.1.0
4955   * @since 3.0.0 The `$post_type` parameter was added.
4956   *
4957   * @global wpdb $wpdb WordPress database abstraction object.
4958   *
4959   * @param string       $page_title Page title.
4960   * @param string       $output     Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4961   *                                 a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4962   * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
4963   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4964   */
4965  function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4966      global $wpdb;
4967  
4968      if ( is_array( $post_type ) ) {
4969          $post_type           = esc_sql( $post_type );
4970          $post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4971          $sql                 = $wpdb->prepare(
4972              "
4973              SELECT ID
4974              FROM $wpdb->posts
4975              WHERE post_title = %s
4976              AND post_type IN ($post_type_in_string)
4977          ",
4978              $page_title
4979          );
4980      } else {
4981          $sql = $wpdb->prepare(
4982              "
4983              SELECT ID
4984              FROM $wpdb->posts
4985              WHERE post_title = %s
4986              AND post_type = %s
4987          ",
4988              $page_title,
4989              $post_type
4990          );
4991      }
4992  
4993      $page = $wpdb->get_var( $sql );
4994  
4995      if ( $page ) {
4996          return get_post( $page, $output );
4997      }
4998  }
4999  
5000  /**
5001   * Identify descendants of a given page ID in a list of page objects.
5002   *
5003   * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
5004   *
5005   * @since 1.5.1
5006   *
5007   * @param int   $page_id Page ID.
5008   * @param array $pages   List of page objects from which descendants should be identified.
5009   * @return array List of page children.
5010   */
5011  function get_page_children( $page_id, $pages ) {
5012      // Build a hash of ID -> children.
5013      $children = array();
5014      foreach ( (array) $pages as $page ) {
5015          $children[ intval( $page->post_parent ) ][] = $page;
5016      }
5017  
5018      $page_list = array();
5019  
5020      // Start the search by looking at immediate children.
5021      if ( isset( $children[ $page_id ] ) ) {
5022          // Always start at the end of the stack in order to preserve original `$pages` order.
5023          $to_look = array_reverse( $children[ $page_id ] );
5024  
5025          while ( $to_look ) {
5026              $p           = array_pop( $to_look );
5027              $page_list[] = $p;
5028              if ( isset( $children[ $p->ID ] ) ) {
5029                  foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
5030                      // Append to the `$to_look` stack to descend the tree.
5031                      $to_look[] = $child;
5032                  }
5033              }
5034          }
5035      }
5036  
5037      return $page_list;
5038  }
5039  
5040  /**
5041   * Order the pages with children under parents in a flat list.
5042   *
5043   * It uses auxiliary structure to hold parent-children relationships and
5044   * runs in O(N) complexity
5045   *
5046   * @since 2.0.0
5047   *
5048   * @param array $pages   Posts array (passed by reference).
5049   * @param int   $page_id Optional. Parent page ID. Default 0.
5050   * @return array A list arranged by hierarchy. Children immediately follow their parents.
5051   */
5052  function get_page_hierarchy( &