[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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 of $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 include the post type in the REST API. Set this to true
1320   *                                              for the post type to be available in the block editor.
1321   *     @type string      $rest_base             To change the base url of REST API route. Default is $post_type.
1322   *     @type string      $rest_controller_class REST API Controller class name. Default is 'WP_REST_Posts_Controller'.
1323   *     @type int         $menu_position         The position in the menu order the post type should appear. To work,
1324   *                                              $show_in_menu must be true. Default null (at the bottom).
1325   *     @type string      $menu_icon             The url to the icon to be used for this menu. Pass a base64-encoded
1326   *                                              SVG using a data URI, which will be colored to match the color scheme
1327   *                                              -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1328   *                                              of a Dashicons helper class to use a font icon, e.g.
1329   *                                              'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1330   *                                              so an icon can be added via CSS. Defaults to use the posts icon.
1331   *     @type string      $capability_type       The string to use to build the read, edit, and delete capabilities.
1332   *                                              May be passed as an array to allow for alternative plurals when using
1333   *                                              this argument as a base to construct the capabilities, e.g.
1334   *                                              array('story', 'stories'). Default 'post'.
1335   *     @type array       $capabilities          Array of capabilities for this post type. $capability_type is used
1336   *                                              as a base to construct capabilities by default.
1337   *                                              See get_post_type_capabilities().
1338   *     @type bool        $map_meta_cap          Whether to use the internal default meta capability handling.
1339   *                                              Default false.
1340   *     @type array       $supports              Core feature(s) the post type supports. Serves as an alias for calling
1341   *                                              add_post_type_support() directly. Core features include 'title',
1342   *                                              'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1343   *                                              'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1344   *                                              Additionally, the 'revisions' feature dictates whether the post type
1345   *                                              will store revisions, and the 'comments' feature dictates whether the
1346   *                                              comments count will show on the edit screen. Defaults is an array
1347   *                                              containing 'title' and 'editor'.
1348   *     @type callable    $register_meta_box_cb  Provide a callback function that sets up the meta boxes for the
1349   *                                              edit form. Do remove_meta_box() and add_meta_box() calls in the
1350   *                                              callback. Default null.
1351   *     @type array       $taxonomies            An array of taxonomy identifiers that will be registered for the
1352   *                                              post type. Taxonomies can be registered later with register_taxonomy()
1353   *                                              or register_taxonomy_for_object_type().
1354   *                                              Default empty array.
1355   *     @type bool|string $has_archive           Whether there should be post type archives, or if a string, the
1356   *                                              archive slug to use. Will generate the proper rewrite rules if
1357   *                                              $rewrite is enabled. Default false.
1358   *     @type bool|array  $rewrite              {
1359   *         Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1360   *         Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1361   *         passed with any of these keys:
1362   *
1363   *         @type string $slug       Customize the permastruct slug. Defaults to $post_type key.
1364   *         @type bool   $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1365   *                                  Default true.
1366   *         @type bool   $feeds      Whether the feed permastruct should be built for this post type.
1367   *                                  Default is value of $has_archive.
1368   *         @type bool   $pages      Whether the permastruct should provide for pagination. Default true.
1369   *         @type const  $ep_mask    Endpoint mask to assign. If not specified and permalink_epmask is set,
1370   *                                  inherits from $permalink_epmask. If not specified and permalink_epmask
1371   *                                  is not set, defaults to EP_PERMALINK.
1372   *     }
1373   *     @type string|bool $query_var             Sets the query_var key for this post type. Defaults to $post_type
1374   *                                              key. If false, a post type cannot be loaded at
1375   *                                              ?{query_var}={post_slug}. If specified as a string, the query
1376   *                                              ?{query_var_string}={post_slug} will be valid.
1377   *     @type bool        $can_export            Whether to allow this post type to be exported. Default true.
1378   *     @type bool        $delete_with_user      Whether to delete posts of this type when deleting a user. If true,
1379   *                                              posts of this type belonging to the user will be moved to trash
1380   *                                              when then user is deleted. If false, posts of this type belonging
1381   *                                              to the user will *not* be trashed or deleted. If not set (the default),
1382   *                                              posts are trashed if post_type_supports('author'). Otherwise posts
1383   *                                              are not trashed or deleted. Default null.
1384   *     @type bool        $_builtin              FOR INTERNAL USE ONLY! True if this post type is a native or
1385   *                                              "built-in" post_type. Default false.
1386   *     @type string      $_edit_link            FOR INTERNAL USE ONLY! URL segment to use for edit link of
1387   *                                              this post type. Default 'post.php?post=%d'.
1388   * }
1389   * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1390   */
1391  function register_post_type( $post_type, $args = array() ) {
1392      global $wp_post_types;
1393  
1394      if ( ! is_array( $wp_post_types ) ) {
1395          $wp_post_types = array();
1396      }
1397  
1398      // Sanitize post type name
1399      $post_type = sanitize_key( $post_type );
1400  
1401      if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1402          _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1403          return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1404      }
1405  
1406      $post_type_object = new WP_Post_Type( $post_type, $args );
1407      $post_type_object->add_supports();
1408      $post_type_object->add_rewrite_rules();
1409      $post_type_object->register_meta_boxes();
1410  
1411      $wp_post_types[ $post_type ] = $post_type_object;
1412  
1413      $post_type_object->add_hooks();
1414      $post_type_object->register_taxonomies();
1415  
1416      /**
1417       * Fires after a post type is registered.
1418       *
1419       * @since 3.3.0
1420       * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1421       *
1422       * @param string       $post_type        Post type.
1423       * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1424       */
1425      do_action( 'registered_post_type', $post_type, $post_type_object );
1426  
1427      return $post_type_object;
1428  }
1429  
1430  /**
1431   * Unregisters a post type.
1432   *
1433   * Can not be used to unregister built-in post types.
1434   *
1435   * @since 4.5.0
1436   *
1437   * @global array $wp_post_types List of post types.
1438   *
1439   * @param string $post_type Post type to unregister.
1440   * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1441   */
1442  function unregister_post_type( $post_type ) {
1443      global $wp_post_types;
1444  
1445      if ( ! post_type_exists( $post_type ) ) {
1446          return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1447      }
1448  
1449      $post_type_object = get_post_type_object( $post_type );
1450  
1451      // Do not allow unregistering internal post types.
1452      if ( $post_type_object->_builtin ) {
1453          return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1454      }
1455  
1456      $post_type_object->remove_supports();
1457      $post_type_object->remove_rewrite_rules();
1458      $post_type_object->unregister_meta_boxes();
1459      $post_type_object->remove_hooks();
1460      $post_type_object->unregister_taxonomies();
1461  
1462      unset( $wp_post_types[ $post_type ] );
1463  
1464      /**
1465       * Fires after a post type was unregistered.
1466       *
1467       * @since 4.5.0
1468       *
1469       * @param string $post_type Post type key.
1470       */
1471      do_action( 'unregistered_post_type', $post_type );
1472  
1473      return true;
1474  }
1475  
1476  /**
1477   * Build an object with all post type capabilities out of a post type object
1478   *
1479   * Post type capabilities use the 'capability_type' argument as a base, if the
1480   * capability is not set in the 'capabilities' argument array or if the
1481   * 'capabilities' argument is not supplied.
1482   *
1483   * The capability_type argument can optionally be registered as an array, with
1484   * the first value being singular and the second plural, e.g. array('story, 'stories')
1485   * Otherwise, an 's' will be added to the value for the plural form. After
1486   * registration, capability_type will always be a string of the singular value.
1487   *
1488   * By default, seven keys are accepted as part of the capabilities array:
1489   *
1490   * - edit_post, read_post, and delete_post are meta capabilities, which are then
1491   *   generally mapped to corresponding primitive capabilities depending on the
1492   *   context, which would be the post being edited/read/deleted and the user or
1493   *   role being checked. Thus these capabilities would generally not be granted
1494   *   directly to users or roles.
1495   *
1496   * - edit_posts - Controls whether objects of this post type can be edited.
1497   * - edit_others_posts - Controls whether objects of this type owned by other users
1498   *   can be edited. If the post type does not support an author, then this will
1499   *   behave like edit_posts.
1500   * - publish_posts - Controls publishing objects of this post type.
1501   * - read_private_posts - Controls whether private objects can be read.
1502   *
1503   * These four primitive capabilities are checked in core in various locations.
1504   * There are also seven other primitive capabilities which are not referenced
1505   * directly in core, except in map_meta_cap(), which takes the three aforementioned
1506   * meta capabilities and translates them into one or more primitive capabilities
1507   * that must then be checked against the user or role, depending on the context.
1508   *
1509   * - read - Controls whether objects of this post type can be read.
1510   * - delete_posts - Controls whether objects of this post type can be deleted.
1511   * - delete_private_posts - Controls whether private objects can be deleted.
1512   * - delete_published_posts - Controls whether published objects can be deleted.
1513   * - delete_others_posts - Controls whether objects owned by other users can be
1514   *   can be deleted. If the post type does not support an author, then this will
1515   *   behave like delete_posts.
1516   * - edit_private_posts - Controls whether private objects can be edited.
1517   * - edit_published_posts - Controls whether published objects can be edited.
1518   *
1519   * These additional capabilities are only used in map_meta_cap(). Thus, they are
1520   * only assigned by default if the post type is registered with the 'map_meta_cap'
1521   * argument set to true (default is false).
1522   *
1523   * @since 3.0.0
1524   *
1525   * @see register_post_type()
1526   * @see map_meta_cap()
1527   *
1528   * @param object $args Post type registration arguments.
1529   * @return object Object with all the capabilities as member variables.
1530   */
1531  function get_post_type_capabilities( $args ) {
1532      if ( ! is_array( $args->capability_type ) ) {
1533          $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1534      }
1535  
1536      // Singular base for meta capabilities, plural base for primitive capabilities.
1537      list( $singular_base, $plural_base ) = $args->capability_type;
1538  
1539      $default_capabilities = array(
1540          // Meta capabilities
1541          'edit_post'          => 'edit_' . $singular_base,
1542          'read_post'          => 'read_' . $singular_base,
1543          'delete_post'        => 'delete_' . $singular_base,
1544          // Primitive capabilities used outside of map_meta_cap():
1545          'edit_posts'         => 'edit_' . $plural_base,
1546          'edit_others_posts'  => 'edit_others_' . $plural_base,
1547          'publish_posts'      => 'publish_' . $plural_base,
1548          'read_private_posts' => 'read_private_' . $plural_base,
1549      );
1550  
1551      // Primitive capabilities used within map_meta_cap():
1552      if ( $args->map_meta_cap ) {
1553          $default_capabilities_for_mapping = array(
1554              'read'                   => 'read',
1555              'delete_posts'           => 'delete_' . $plural_base,
1556              'delete_private_posts'   => 'delete_private_' . $plural_base,
1557              'delete_published_posts' => 'delete_published_' . $plural_base,
1558              'delete_others_posts'    => 'delete_others_' . $plural_base,
1559              'edit_private_posts'     => 'edit_private_' . $plural_base,
1560              'edit_published_posts'   => 'edit_published_' . $plural_base,
1561          );
1562          $default_capabilities             = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1563      }
1564  
1565      $capabilities = array_merge( $default_capabilities, $args->capabilities );
1566  
1567      // Post creation capability simply maps to edit_posts by default:
1568      if ( ! isset( $capabilities['create_posts'] ) ) {
1569          $capabilities['create_posts'] = $capabilities['edit_posts'];
1570      }
1571  
1572      // Remember meta capabilities for future reference.
1573      if ( $args->map_meta_cap ) {
1574          _post_type_meta_capabilities( $capabilities );
1575      }
1576  
1577      return (object) $capabilities;
1578  }
1579  
1580  /**
1581   * Store or return a list of post type meta caps for map_meta_cap().
1582   *
1583   * @since 3.1.0
1584   * @access private
1585   *
1586   * @global array $post_type_meta_caps Used to store meta capabilities.
1587   *
1588   * @param array $capabilities Post type meta capabilities.
1589   */
1590  function _post_type_meta_capabilities( $capabilities = null ) {
1591      global $post_type_meta_caps;
1592  
1593      foreach ( $capabilities as $core => $custom ) {
1594          if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1595              $post_type_meta_caps[ $custom ] = $core;
1596          }
1597      }
1598  }
1599  
1600  /**
1601   * Builds an object with all post type labels out of a post type object.
1602   *
1603   * Accepted keys of the label array in the post type object:
1604   *
1605   * - `name` - General name for the post type, usually plural. The same and overridden
1606   *          by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1607   * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1608   * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1609   *             When internationalizing this string, please use a {@link https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#disambiguation-by-context gettext context}
1610   *             matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1611   * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1612   * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1613   * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1614   * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1615   * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
1616   * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1617   * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1618   * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1619   *                        'No pages found in Trash'.
1620   * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1621   *                       post types. Default is 'Parent Page:'.
1622   * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1623   * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1624   * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
1625   * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1626   * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1627   *                           'Uploaded to this page'.
1628   * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1629   * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1630   * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1631   * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1632   * - `menu_name` - Label for the menu name. Default is the same as `name`.
1633   * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1634   *                       'Filter pages list'.
1635   * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1636   *                           'Pages list navigation'.
1637   * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1638   * - `item_published` - Label used when an item is published. Default is 'Post published.' / 'Page published.'
1639   * - `item_published_privately` - Label used when an item is published with private visibility.
1640   *                              Default is 'Post published privately.' / 'Page published privately.'
1641   * - `item_reverted_to_draft` - Label used when an item is switched to a draft.
1642   *                            Default is 'Post reverted to draft.' / 'Page reverted to draft.'
1643   * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' /
1644   *                    'Page scheduled.'
1645   * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.'
1646   *
1647   * Above, the first default value is for non-hierarchical post types (like posts)
1648   * and the second one is for hierarchical post types (like pages).
1649   *
1650   * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1651   *
1652   * @since 3.0.0
1653   * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1654   *              and `use_featured_image` labels.
1655   * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1656   *              `items_list_navigation`, and `items_list` labels.
1657   * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1658   * @since 4.7.0 Added the `view_items` and `attributes` labels.
1659   * @since 5.0.0 Added the `item_published`, `item_published_privately`, `item_reverted_to_draft`,
1660   *              `item_scheduled`, and `item_updated` labels.
1661   *
1662   * @access private
1663   *
1664   * @param object|WP_Post_Type $post_type_object Post type object.
1665   * @return object Object with all the labels as member variables.
1666   */
1667  function get_post_type_labels( $post_type_object ) {
1668      $nohier_vs_hier_defaults              = array(
1669          'name'                     => array( _x( 'Posts', 'post type general name' ), _x( 'Pages', 'post type general name' ) ),
1670          'singular_name'            => array( _x( 'Post', 'post type singular name' ), _x( 'Page', 'post type singular name' ) ),
1671          'add_new'                  => array( _x( 'Add New', 'post' ), _x( 'Add New', 'page' ) ),
1672          'add_new_item'             => array( __( 'Add New Post' ), __( 'Add New Page' ) ),
1673          'edit_item'                => array( __( 'Edit Post' ), __( 'Edit Page' ) ),
1674          'new_item'                 => array( __( 'New Post' ), __( 'New Page' ) ),
1675          'view_item'                => array( __( 'View Post' ), __( 'View Page' ) ),
1676          'view_items'               => array( __( 'View Posts' ), __( 'View Pages' ) ),
1677          'search_items'             => array( __( 'Search Posts' ), __( 'Search Pages' ) ),
1678          'not_found'                => array( __( 'No posts found.' ), __( 'No pages found.' ) ),
1679          'not_found_in_trash'       => array( __( 'No posts found in Trash.' ), __( 'No pages found in Trash.' ) ),
1680          'parent_item_colon'        => array( null, __( 'Parent Page:' ) ),
1681          'all_items'                => array( __( 'All Posts' ), __( 'All Pages' ) ),
1682          'archives'                 => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1683          'attributes'               => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ),
1684          'insert_into_item'         => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1685          'uploaded_to_this_item'    => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1686          'featured_image'           => array( _x( 'Featured Image', 'post' ), _x( 'Featured Image', 'page' ) ),
1687          'set_featured_image'       => array( _x( 'Set featured image', 'post' ), _x( 'Set featured image', 'page' ) ),
1688          'remove_featured_image'    => array( _x( 'Remove featured image', 'post' ), _x( 'Remove featured image', 'page' ) ),
1689          'use_featured_image'       => array( _x( 'Use as featured image', 'post' ), _x( 'Use as featured image', 'page' ) ),
1690          'filter_items_list'        => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1691          'items_list_navigation'    => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1692          'items_list'               => array( __( 'Posts list' ), __( 'Pages list' ) ),
1693          'item_published'           => array( __( 'Post published.' ), __( 'Page published.' ) ),
1694          'item_published_privately' => array( __( 'Post published privately.' ), __( 'Page published privately.' ) ),
1695          'item_reverted_to_draft'   => array( __( 'Post reverted to draft.' ), __( 'Page reverted to draft.' ) ),
1696          'item_scheduled'           => array( __( 'Post scheduled.' ), __( 'Page scheduled.' ) ),
1697          'item_updated'             => array( __( 'Post updated.' ), __( 'Page updated.' ) ),
1698      );
1699      $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1700  
1701      $labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1702  
1703      $post_type = $post_type_object->name;
1704  
1705      $default_labels = clone $labels;
1706  
1707      /**
1708       * Filters the labels of a specific post type.
1709       *
1710       * The dynamic portion of the hook name, `$post_type`, refers to
1711       * the post type slug.
1712       *
1713       * @since 3.5.0
1714       *
1715       * @see get_post_type_labels() for the full list of labels.
1716       *
1717       * @param object $labels Object with labels for the post type as member variables.
1718       */
1719      $labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1720  
1721      // Ensure that the filtered labels contain all required default values.
1722      $labels = (object) array_merge( (array) $default_labels, (array) $labels );
1723  
1724      return $labels;
1725  }
1726  
1727  /**
1728   * Build an object with custom-something object (post type, taxonomy) labels
1729   * out of a custom-something object
1730   *
1731   * @since 3.0.0
1732   * @access private
1733   *
1734   * @param object $object                  A custom-something object.
1735   * @param array  $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1736   * @return object Object containing labels for the given custom-something object.
1737   */
1738  function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1739      $object->labels = (array) $object->labels;
1740  
1741      if ( isset( $object->label ) && empty( $object->labels['name'] ) ) {
1742          $object->labels['name'] = $object->label;
1743      }
1744  
1745      if ( ! isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) ) {
1746          $object->labels['singular_name'] = $object->labels['name'];
1747      }
1748  
1749      if ( ! isset( $object->labels['name_admin_bar'] ) ) {
1750          $object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1751      }
1752  
1753      if ( ! isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) ) {
1754          $object->labels['menu_name'] = $object->labels['name'];
1755      }
1756  
1757      if ( ! isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) ) {
1758          $object->labels['all_items'] = $object->labels['menu_name'];
1759      }
1760  
1761      if ( ! isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1762          $object->labels['archives'] = $object->labels['all_items'];
1763      }
1764  
1765      $defaults = array();
1766      foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1767          $defaults[ $key ] = $object->hierarchical ? $value[1] : $value[0];
1768      }
1769      $labels         = array_merge( $defaults, $object->labels );
1770      $object->labels = (object) $object->labels;
1771  
1772      return (object) $labels;
1773  }
1774  
1775  /**
1776   * Add submenus for post types.
1777   *
1778   * @access private
1779   * @since 3.1.0
1780   */
1781  function _add_post_type_submenus() {
1782      foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1783          $ptype_obj = get_post_type_object( $ptype );
1784          // Sub-menus only.
1785          if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true ) {
1786              continue;
1787          }
1788          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" );
1789      }
1790  }
1791  
1792  /**
1793   * Registers support of certain features for a post type.
1794   *
1795   * All core features are directly associated with a functional area of the edit
1796   * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1797   * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1798   * 'thumbnail', 'custom-fields', and 'post-formats'.
1799   *
1800   * Additionally, the 'revisions' feature dictates whether the post type will
1801   * store revisions, and the 'comments' feature dictates whether the comments
1802   * count will show on the edit screen.
1803   *
1804   * Example usage:
1805   *
1806   *     add_post_type_support( 'my_post_type', 'comments' );
1807   *     add_post_type_support( 'my_post_type', array(
1808   *         'author', 'excerpt',
1809   *     ) );
1810   *     add_post_type_support( 'my_post_type', 'my_feature', array(
1811   *         'field' => 'value',
1812   *     ) );
1813   *
1814   * @since 3.0.0
1815   *
1816   * @global array $_wp_post_type_features
1817   *
1818   * @param string       $post_type The post type for which to add the feature.
1819   * @param string|array $feature   The feature being added, accepts an array of
1820   *                                feature strings or a single string.
1821   * @param mixed        ...$args   Optional extra arguments to pass along with certain features.
1822   */
1823  function add_post_type_support( $post_type, $feature, ...$args ) {
1824      global $_wp_post_type_features;
1825  
1826      $features = (array) $feature;
1827      foreach ( $features as $feature ) {
1828          if ( $args ) {
1829              $_wp_post_type_features[ $post_type ][ $feature ] = $args;
1830          } else {
1831              $_wp_post_type_features[ $post_type ][ $feature ] = true;
1832          }
1833      }
1834  }
1835  
1836  /**
1837   * Remove support for a feature from a post type.
1838   *
1839   * @since 3.0.0
1840   *
1841   * @global array $_wp_post_type_features
1842   *
1843   * @param string $post_type The post type for which to remove the feature.
1844   * @param string $feature   The feature being removed.
1845   */
1846  function remove_post_type_support( $post_type, $feature ) {
1847      global $_wp_post_type_features;
1848  
1849      unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1850  }
1851  
1852  /**
1853   * Get all the post type features
1854   *
1855   * @since 3.4.0
1856   *
1857   * @global array $_wp_post_type_features
1858   *
1859   * @param string $post_type The post type.
1860   * @return array Post type supports list.
1861   */
1862  function get_all_post_type_supports( $post_type ) {
1863      global $_wp_post_type_features;
1864  
1865      if ( isset( $_wp_post_type_features[ $post_type ] ) ) {
1866          return $_wp_post_type_features[ $post_type ];
1867      }
1868  
1869      return array();
1870  }
1871  
1872  /**
1873   * Check a post type's support for a given feature.
1874   *
1875   * @since 3.0.0
1876   *
1877   * @global array $_wp_post_type_features
1878   *
1879   * @param string $post_type The post type being checked.
1880   * @param string $feature   The feature being checked.
1881   * @return bool Whether the post type supports the given feature.
1882   */
1883  function post_type_supports( $post_type, $feature ) {
1884      global $_wp_post_type_features;
1885  
1886      return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) );
1887  }
1888  
1889  /**
1890   * Retrieves a list of post type names that support a specific feature.
1891   *
1892   * @since 4.5.0
1893   *
1894   * @global array $_wp_post_type_features Post type features
1895   *
1896   * @param array|string $feature  Single feature or an array of features the post types should support.
1897   * @param string       $operator Optional. The logical operation to perform. 'or' means
1898   *                               only one element from the array needs to match; 'and'
1899   *                               means all elements must match; 'not' means no elements may
1900   *                               match. Default 'and'.
1901   * @return array A list of post type names.
1902   */
1903  function get_post_types_by_support( $feature, $operator = 'and' ) {
1904      global $_wp_post_type_features;
1905  
1906      $features = array_fill_keys( (array) $feature, true );
1907  
1908      return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1909  }
1910  
1911  /**
1912   * Update the post type for the post ID.
1913   *
1914   * The page or post cache will be cleaned for the post ID.
1915   *
1916   * @since 2.5.0
1917   *
1918   * @global wpdb $wpdb WordPress database abstraction object.
1919   *
1920   * @param int    $post_id   Optional. Post ID to change post type. Default 0.
1921   * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1922   *                          name a few. Default 'post'.
1923   * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1924   */
1925  function set_post_type( $post_id = 0, $post_type = 'post' ) {
1926      global $wpdb;
1927  
1928      $post_type = sanitize_post_field( 'post_type', $post_type, $post_id, 'db' );
1929      $return    = $wpdb->update( $wpdb->posts, array( 'post_type' => $post_type ), array( 'ID' => $post_id ) );
1930  
1931      clean_post_cache( $post_id );
1932  
1933      return $return;
1934  }
1935  
1936  /**
1937   * Determines whether a post type is considered "viewable".
1938   *
1939   * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1940   * For all others, the 'publicly_queryable' value will be used.
1941   *
1942   * @since 4.4.0
1943   * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1944   * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1945   *
1946   * @param string|WP_Post_Type $post_type Post type name or object.
1947   * @return bool Whether the post type should be considered viewable.
1948   */
1949  function is_post_type_viewable( $post_type ) {
1950      if ( is_scalar( $post_type ) ) {
1951          $post_type = get_post_type_object( $post_type );
1952          if ( ! $post_type ) {
1953              return false;
1954          }
1955      }
1956  
1957      return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1958  }
1959  
1960  /**
1961   * Retrieves an array of the latest posts, or posts matching the given criteria.
1962   *
1963   * The defaults are as follows:
1964   *
1965   * @since 1.2.0
1966   *
1967   * @see WP_Query::parse_query()
1968   *
1969   * @param array $args {
1970   *     Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1971   *     available arguments.
1972   *
1973   *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of $posts_per_page
1974   *                                        in WP_Query. Accepts -1 for all. Default 5.
1975   *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
1976   *                                        Is an alias of $cat in WP_Query. Default 0.
1977   *     @type array      $include          An array of post IDs to retrieve, sticky posts will be included.
1978   *                                        Is an alias of $post__in in WP_Query. Default empty array.
1979   *     @type array      $exclude          An array of post IDs not to retrieve. Default empty array.
1980   *     @type bool       $suppress_filters Whether to suppress filters. Default true.
1981   * }
1982   * @return WP_Post[]|int[] Array of post objects or post IDs.
1983   */
1984  function get_posts( $args = null ) {
1985      $defaults = array(
1986          'numberposts'      => 5,
1987          'category'         => 0,
1988          'orderby'          => 'date',
1989          'order'            => 'DESC',
1990          'include'          => array(),
1991          'exclude'          => array(),
1992          'meta_key'         => '',
1993          'meta_value'       => '',
1994          'post_type'        => 'post',
1995          'suppress_filters' => true,
1996      );
1997  
1998      $parsed_args = wp_parse_args( $args, $defaults );
1999      if ( empty( $parsed_args['post_status'] ) ) {
2000          $parsed_args['post_status'] = ( 'attachment' == $parsed_args['post_type'] ) ? 'inherit' : 'publish';
2001      }
2002      if ( ! empty( $parsed_args['numberposts'] ) && empty( $parsed_args['posts_per_page'] ) ) {
2003          $parsed_args['posts_per_page'] = $parsed_args['numberposts'];
2004      }
2005      if ( ! empty( $parsed_args['category'] ) ) {
2006          $parsed_args['cat'] = $parsed_args['category'];
2007      }
2008      if ( ! empty( $parsed_args['include'] ) ) {
2009          $incposts                      = wp_parse_id_list( $parsed_args['include'] );
2010          $parsed_args['posts_per_page'] = count( $incposts );  // only the number of posts included
2011          $parsed_args['post__in']       = $incposts;
2012      } elseif ( ! empty( $parsed_args['exclude'] ) ) {
2013          $parsed_args['post__not_in'] = wp_parse_id_list( $parsed_args['exclude'] );
2014      }
2015  
2016      $parsed_args['ignore_sticky_posts'] = true;
2017      $parsed_args['no_found_rows']       = true;
2018  
2019      $get_posts = new WP_Query;
2020      return $get_posts->query( $parsed_args );
2021  
2022  }
2023  
2024  //
2025  // Post meta functions
2026  //
2027  
2028  /**
2029   * Adds a meta field to the given post.
2030   *
2031   * Post meta data is called "Custom Fields" on the Administration Screen.
2032   *
2033   * @since 1.5.0
2034   *
2035   * @param int    $post_id    Post ID.
2036   * @param string $meta_key   Metadata name.
2037   * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
2038   * @param bool   $unique     Optional. Whether the same key should not be added.
2039   *                           Default false.
2040   * @return int|false Meta ID on success, false on failure.
2041   */
2042  function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
2043      // Make sure meta is added to the post, not a revision.
2044      $the_post = wp_is_post_revision( $post_id );
2045      if ( $the_post ) {
2046          $post_id = $the_post;
2047      }
2048  
2049      return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
2050  }
2051  
2052  /**
2053   * Deletes a post meta field for the given post ID.
2054   *
2055   * You can match based on the key, or key and value. Removing based on key and
2056   * value, will keep from removing duplicate metadata with the same key. It also
2057   * allows removing all metadata matching the key, if needed.
2058   *
2059   * @since 1.5.0
2060   *
2061   * @param int    $post_id    Post ID.
2062   * @param string $meta_key   Metadata name.
2063   * @param mixed  $meta_value Optional. Metadata value. Must be serializable if
2064   *                           non-scalar. Default empty.
2065   * @return bool True on success, false on failure.
2066   */
2067  function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
2068      // Make sure meta is added to the post, not a revision.
2069      $the_post = wp_is_post_revision( $post_id );
2070      if ( $the_post ) {
2071          $post_id = $the_post;
2072      }
2073  
2074      return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
2075  }
2076  
2077  /**
2078   * Retrieves a post meta field for the given post ID.
2079   *
2080   * @since 1.5.0
2081   *
2082   * @param int    $post_id Post ID.
2083   * @param string $key     Optional. The meta key to retrieve. By default, returns
2084   *                        data for all keys. Default empty.
2085   * @param bool   $single  Optional. If true, returns only the first value for the specified meta key.
2086   *                        This parameter has no effect if $key is not specified. Default false.
2087   * @return mixed Will be an array if $single is false. Will be value of the meta
2088   *               field if $single is true.
2089   */
2090  function get_post_meta( $post_id, $key = '', $single = false ) {
2091      return get_metadata( 'post', $post_id, $key, $single );
2092  }
2093  
2094  /**
2095   * Updates a post meta field based on the given post ID.
2096   *
2097   * Use the `$prev_value` parameter to differentiate between meta fields with the
2098   * same key and post ID.
2099   *
2100   * If the meta field for the post does not exist, it will be added and its ID returned.
2101   *
2102   * Can be used in place of add_post_meta().
2103   *
2104   * @since 1.5.0
2105   *
2106   * @param int    $post_id    Post ID.
2107   * @param string $meta_key   Metadata key.
2108   * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
2109   * @param mixed  $prev_value Optional. Previous value to check before updating.
2110   * @return int|bool The new meta field ID if a field with the given key didn't exist and was
2111   *                  therefore added, true on successful update, false on failure.
2112   */
2113  function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
2114      // Make sure meta is added to the post, not a revision.
2115      $the_post = wp_is_post_revision( $post_id );
2116      if ( $the_post ) {
2117          $post_id = $the_post;
2118      }
2119  
2120      return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
2121  }
2122  
2123  /**
2124   * Deletes everything from post meta matching the given meta key.
2125   *
2126   * @since 2.3.0
2127   *
2128   * @param string $post_meta_key Key to search for when deleting.
2129   * @return bool Whether the post meta key was deleted from the database.
2130   */
2131  function delete_post_meta_by_key( $post_meta_key ) {
2132      return delete_metadata( 'post', null, $post_meta_key, '', true );
2133  }
2134  
2135  /**
2136   * Registers a meta key for posts.
2137   *
2138   * @since 4.9.8
2139   *
2140   * @param string $post_type Post type to register a meta key for. Pass an empty string
2141   *                          to register the meta key across all existing post types.
2142   * @param string $meta_key  The meta key to register.
2143   * @param array  $args      Data used to describe the meta key when registered. See
2144   *                          {@see register_meta()} for a list of supported arguments.
2145   * @return bool True if the meta key was successfully registered, false if not.
2146   */
2147  function register_post_meta( $post_type, $meta_key, array $args ) {
2148      $args['object_subtype'] = $post_type;
2149  
2150      return register_meta( 'post', $meta_key, $args );
2151  }
2152  
2153  /**
2154   * Unregisters a meta key for posts.
2155   *
2156   * @since 4.9.8
2157   *
2158   * @param string $post_type Post type the meta key is currently registered for. Pass
2159   *                          an empty string if the meta key is registered across all
2160   *                          existing post types.
2161   * @param string $meta_key  The meta key to unregister.
2162   * @return bool True on success, false if the meta key was not previously registered.
2163   */
2164  function unregister_post_meta( $post_type, $meta_key ) {
2165      return unregister_meta_key( 'post', $meta_key, $post_type );
2166  }
2167  
2168  /**
2169   * Retrieve post meta fields, based on post ID.
2170   *
2171   * The post meta fields are retrieved from the cache where possible,
2172   * so the function is optimized to be called more than once.
2173   *
2174   * @since 1.2.0
2175   *
2176   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2177   * @return array Post meta for the given post.
2178   */
2179  function get_post_custom( $post_id = 0 ) {
2180      $post_id = absint( $post_id );
2181      if ( ! $post_id ) {
2182          $post_id = get_the_ID();
2183      }
2184  
2185      return get_post_meta( $post_id );
2186  }
2187  
2188  /**
2189   * Retrieve meta field names for a post.
2190   *
2191   * If there are no meta fields, then nothing (null) will be returned.
2192   *
2193   * @since 1.2.0
2194   *
2195   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2196   * @return array|void Array of the keys, if retrieved.
2197   */
2198  function get_post_custom_keys( $post_id = 0 ) {
2199      $custom = get_post_custom( $post_id );
2200  
2201      if ( ! is_array( $custom ) ) {
2202          return;
2203      }
2204  
2205      $keys = array_keys( $custom );
2206      if ( $keys ) {
2207          return $keys;
2208      }
2209  }
2210  
2211  /**
2212   * Retrieve values for a custom post field.
2213   *
2214   * The parameters must not be considered optional. All of the post meta fields
2215   * will be retrieved and only the meta field key values returned.
2216   *
2217   * @since 1.2.0
2218   *
2219   * @param string $key     Optional. Meta field key. Default empty.
2220   * @param int    $post_id Optional. Post ID. Default is ID of the global $post.
2221   * @return array|null Meta field values.
2222   */
2223  function get_post_custom_values( $key = '', $post_id = 0 ) {
2224      if ( ! $key ) {
2225          return null;
2226      }
2227  
2228      $custom = get_post_custom( $post_id );
2229  
2230      return isset( $custom[ $key ] ) ? $custom[ $key ] : null;
2231  }
2232  
2233  /**
2234   * Determines whether a post is sticky.
2235   *
2236   * Sticky posts should remain at the top of The Loop. If the post ID is not
2237   * given, then The Loop ID for the current post will be used.
2238   *
2239   * For more information on this and similar theme functions, check out
2240   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2241   * Conditional Tags} article in the Theme Developer Handbook.
2242   *
2243   * @since 2.7.0
2244   *
2245   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2246   * @return bool Whether post is sticky.
2247   */
2248  function is_sticky( $post_id = 0 ) {
2249      $post_id = absint( $post_id );
2250  
2251      if ( ! $post_id ) {
2252          $post_id = get_the_ID();
2253      }
2254  
2255      $stickies = get_option( 'sticky_posts' );
2256  
2257      $is_sticky = is_array( $stickies ) && in_array( $post_id, $stickies );
2258  
2259      /**
2260       * Filters whether a post is sticky.
2261       *
2262       * @since 5.3.0
2263       *
2264       * @param bool $is_sticky Whether a post is sticky.
2265       * @param int  $post_id   Post ID.
2266       */
2267      return apply_filters( 'is_sticky', $is_sticky, $post_id );
2268  }
2269  
2270  /**
2271   * Sanitize every post field.
2272   *
2273   * If the context is 'raw', then the post object or array will get minimal
2274   * sanitization of the integer fields.
2275   *
2276   * @since 2.3.0
2277   *
2278   * @see sanitize_post_field()
2279   *
2280   * @param object|WP_Post|array $post    The Post Object or Array
2281   * @param string               $context Optional. How to sanitize post fields.
2282   *                                      Accepts 'raw', 'edit', 'db', or 'display'.
2283   *                                      Default 'display'.
2284   * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
2285   *                              same type as $post).
2286   */
2287  function sanitize_post( $post, $context = 'display' ) {
2288      if ( is_object( $post ) ) {
2289          // Check if post already filtered for this context.
2290          if ( isset( $post->filter ) && $context == $post->filter ) {
2291              return $post;
2292          }
2293          if ( ! isset( $post->ID ) ) {
2294              $post->ID = 0;
2295          }
2296          foreach ( array_keys( get_object_vars( $post ) ) as $field ) {
2297              $post->$field = sanitize_post_field( $field, $post->$field, $post->ID, $context );
2298          }
2299          $post->filter = $context;
2300      } elseif ( is_array( $post ) ) {
2301          // Check if post already filtered for this context.
2302          if ( isset( $post['filter'] ) && $context == $post['filter'] ) {
2303              return $post;
2304          }
2305          if ( ! isset( $post['ID'] ) ) {
2306              $post['ID'] = 0;
2307          }
2308          foreach ( array_keys( $post ) as $field ) {
2309              $post[ $field ] = sanitize_post_field( $field, $post[ $field ], $post['ID'], $context );
2310          }
2311          $post['filter'] = $context;
2312      }
2313      return $post;
2314  }
2315  
2316  /**
2317   * Sanitize post field based on context.
2318   *
2319   * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and
2320   * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
2321   * are treated like 'display' when calling filters.
2322   *
2323   * @since 2.3.0
2324   * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
2325   *
2326   * @param string $field   The Post Object field name.
2327   * @param mixed  $value   The Post Object value.
2328   * @param int    $post_id Post ID.
2329   * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
2330   *                        'db', 'display', 'attribute' and 'js'. Default 'display'.
2331   * @return mixed Sanitized value.
2332   */
2333  function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
2334      $int_fields = array( 'ID', 'post_parent', 'menu_order' );
2335      if ( in_array( $field, $int_fields ) ) {
2336          $value = (int) $value;
2337      }
2338  
2339      // Fields which contain arrays of integers.
2340      $array_int_fields = array( 'ancestors' );
2341      if ( in_array( $field, $array_int_fields ) ) {
2342          $value = array_map( 'absint', $value );
2343          return $value;
2344      }
2345  
2346      if ( 'raw' == $context ) {
2347          return $value;
2348      }
2349  
2350      $prefixed = false;
2351      if ( false !== strpos( $field, 'post_' ) ) {
2352          $prefixed        = true;
2353          $field_no_prefix = str_replace( 'post_', '', $field );
2354      }
2355  
2356      if ( 'edit' == $context ) {
2357          $format_to_edit = array( 'post_content', 'post_excerpt', 'post_title', 'post_password' );
2358  
2359          if ( $prefixed ) {
2360  
2361              /**
2362               * Filters the value of a specific post field to edit.
2363               *
2364               * The dynamic portion of the hook name, `$field`, refers to the post
2365               * field name.
2366               *
2367               * @since 2.3.0
2368               *
2369               * @param mixed $value   Value of the post field.
2370               * @param int   $post_id Post ID.
2371               */
2372              $value = apply_filters( "edit_{$field}", $value, $post_id );
2373  
2374              /**
2375               * Filters the value of a specific post field to edit.
2376               *
2377               * The dynamic portion of the hook name, `$field_no_prefix`, refers to
2378               * the post field name.
2379               *
2380               * @since 2.3.0
2381               *
2382               * @param mixed $value   Value of the post field.
2383               * @param int   $post_id Post ID.
2384               */
2385              $value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
2386          } else {
2387              $value = apply_filters( "edit_post_{$field}", $value, $post_id );
2388          }
2389  
2390          if ( in_array( $field, $format_to_edit ) ) {
2391              if ( 'post_content' == $field ) {
2392                  $value = format_to_edit( $value, user_can_richedit() );
2393              } else {
2394                  $value = format_to_edit( $value );
2395              }
2396          } else {
2397              $value = esc_attr( $value );
2398          }
2399      } elseif ( 'db' == $context ) {
2400          if ( $prefixed ) {
2401  
2402              /**
2403               * Filters the value of a specific post field before saving.
2404               *
2405               * The dynamic portion of the hook name, `$field`, refers to the post
2406               * field name.
2407               *
2408               * @since 2.3.0
2409               *
2410               * @param mixed $value Value of the post field.
2411               */
2412              $value = apply_filters( "pre_{$field}", $value );
2413  
2414              /**
2415               * Filters the value of a specific field before saving.
2416               *
2417               * The dynamic portion of the hook name, `$field_no_prefix`, refers
2418               * to the post field name.
2419               *
2420               * @since 2.3.0
2421               *
2422               * @param mixed $value Value of the post field.
2423               */
2424              $value = apply_filters( "{$field_no_prefix}_save_pre", $value );
2425          } else {
2426              $value = apply_filters( "pre_post_{$field}", $value );
2427  
2428              /**
2429               * Filters the value of a specific post field before saving.
2430               *
2431               * The dynamic portion of the hook name, `$field`, refers to the post
2432               * field name.
2433               *
2434               * @since 2.3.0
2435               *
2436               * @param mixed $value Value of the post field.
2437               */
2438              $value = apply_filters( "{$field}_pre", $value );
2439          }
2440      } else {
2441  
2442          // Use display filters by default.
2443          if ( $prefixed ) {
2444  
2445              /**
2446               * Filters the value of a specific post field for display.
2447               *
2448               * The dynamic portion of the hook name, `$field`, refers to the post
2449               * field name.
2450               *
2451               * @since 2.3.0
2452               *
2453               * @param mixed  $value   Value of the prefixed post field.
2454               * @param int    $post_id Post ID.
2455               * @param string $context Context for how to sanitize the field. Possible
2456               *                        values include 'raw', 'edit', 'db', 'display',
2457               *                        'attribute' and 'js'.
2458               */
2459              $value = apply_filters( "{$field}", $value, $post_id, $context );
2460          } else {
2461              $value = apply_filters( "post_{$field}", $value, $post_id, $context );
2462          }
2463  
2464          if ( 'attribute' == $context ) {
2465              $value = esc_attr( $value );
2466          } elseif ( 'js' == $context ) {
2467              $value = esc_js( $value );
2468          }
2469      }
2470  
2471      return $value;
2472  }
2473  
2474  /**
2475   * Make a post sticky.
2476   *
2477   * Sticky posts should be displayed at the top of the front page.
2478   *
2479   * @since 2.7.0
2480   *
2481   * @param int $post_id Post ID.
2482   */
2483  function stick_post( $post_id ) {
2484      $stickies = get_option( 'sticky_posts' );
2485  
2486      if ( ! is_array( $stickies ) ) {
2487          $stickies = array( $post_id );
2488      }
2489  
2490      if ( ! in_array( $post_id, $stickies ) ) {
2491          $stickies[] = $post_id;
2492      }
2493  
2494      $updated = update_option( 'sticky_posts', $stickies );
2495  
2496      if ( $updated ) {
2497          /**
2498           * Fires once a post has been added to the sticky list.
2499           *
2500           * @since 4.6.0
2501           *
2502           * @param int $post_id ID of the post that was stuck.
2503           */
2504          do_action( 'post_stuck', $post_id );
2505      }
2506  }
2507  
2508  /**
2509   * Un-stick a post.
2510   *
2511   * Sticky posts should be displayed at the top of the front page.
2512   *
2513   * @since 2.7.0
2514   *
2515   * @param int $post_id Post ID.
2516   */
2517  function unstick_post( $post_id ) {
2518      $stickies = get_option( 'sticky_posts' );
2519  
2520      if ( ! is_array( $stickies ) ) {
2521          return;
2522      }
2523  
2524      if ( ! in_array( $post_id, $stickies ) ) {
2525          return;
2526      }
2527  
2528      $offset = array_search( $post_id, $stickies );
2529      if ( false === $offset ) {
2530          return;
2531      }
2532  
2533      array_splice( $stickies, $offset, 1 );
2534  
2535      $updated = update_option( 'sticky_posts', $stickies );
2536  
2537      if ( $updated ) {
2538          /**
2539           * Fires once a post has been removed from the sticky list.
2540           *
2541           * @since 4.6.0
2542           *
2543           * @param int $post_id ID of the post that was unstuck.
2544           */
2545          do_action( 'post_unstuck', $post_id );
2546      }
2547  }
2548  
2549  /**
2550   * Return the cache key for wp_count_posts() based on the passed arguments.
2551   *
2552   * @since 3.9.0
2553   * @access private
2554   *
2555   * @param string $type Optional. Post type to retrieve count Default 'post'.
2556   * @param string $perm Optional. 'readable' or empty. Default empty.
2557   * @return string The cache key.
2558   */
2559  function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2560      $cache_key = 'posts-' . $type;
2561      if ( 'readable' == $perm && is_user_logged_in() ) {
2562          $post_type_object = get_post_type_object( $type );
2563          if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2564              $cache_key .= '_' . $perm . '_' . get_current_user_id();
2565          }
2566      }
2567      return $cache_key;
2568  }
2569  
2570  /**
2571   * Count number of posts of a post type and if user has permissions to view.
2572   *
2573   * This function provides an efficient method of finding the amount of post's
2574   * type a blog has. Another method is to count the amount of items in
2575   * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2576   * when developing for 2.5+, use this function instead.
2577   *
2578   * The $perm parameter checks for 'readable' value and if the user can read
2579   * private posts, it will display that for the user that is signed in.
2580   *
2581   * @since 2.5.0
2582   *
2583   * @global wpdb $wpdb WordPress database abstraction object.
2584   *
2585   * @param string $type Optional. Post type to retrieve count. Default 'post'.
2586   * @param string $perm Optional. 'readable' or empty. Default empty.
2587   * @return object Number of posts for each status.
2588   */
2589  function wp_count_posts( $type = 'post', $perm = '' ) {
2590      global $wpdb;
2591  
2592      if ( ! post_type_exists( $type ) ) {
2593          return new stdClass;
2594      }
2595  
2596      $cache_key = _count_posts_cache_key( $type, $perm );
2597  
2598      $counts = wp_cache_get( $cache_key, 'counts' );
2599      if ( false !== $counts ) {
2600          /** This filter is documented in wp-includes/post.php */
2601          return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2602      }
2603  
2604      $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2605      if ( 'readable' == $perm && is_user_logged_in() ) {
2606          $post_type_object = get_post_type_object( $type );
2607          if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2608              $query .= $wpdb->prepare(
2609                  " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2610                  get_current_user_id()
2611              );
2612          }
2613      }
2614      $query .= ' GROUP BY post_status';
2615  
2616      $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2617      $counts  = array_fill_keys( get_post_stati(), 0 );
2618  
2619      foreach ( $results as $row ) {
2620          $counts[ $row['post_status'] ] = $row['num_posts'];
2621      }
2622  
2623      $counts = (object) $counts;
2624      wp_cache_set( $cache_key, $counts, 'counts' );
2625  
2626      /**
2627       * Modify returned post counts by status for the current post type.
2628       *
2629       * @since 3.7.0
2630       *
2631       * @param object $counts An object containing the current post_type's post
2632       *                       counts by status.
2633       * @param string $type   Post type.
2634       * @param string $perm   The permission to determine if the posts are 'readable'
2635       *                       by the current user.
2636       */
2637      return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2638  }
2639  
2640  /**
2641   * Count number of attachments for the mime type(s).
2642   *
2643   * If you set the optional mime_type parameter, then an array will still be
2644   * returned, but will only have the item you are looking for. It does not give
2645   * you the number of attachments that are children of a post. You can get that
2646   * by counting the number of children that post has.
2647   *
2648   * @since 2.5.0
2649   *
2650   * @global wpdb $wpdb WordPress database abstraction object.
2651   *
2652   * @param string|array $mime_type Optional. Array or comma-separated list of
2653   *                                MIME patterns. Default empty.
2654   * @return object An object containing the attachment counts by mime type.
2655   */
2656  function wp_count_attachments( $mime_type = '' ) {
2657      global $wpdb;
2658  
2659      $and   = wp_post_mime_type_where( $mime_type );
2660      $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 );
2661  
2662      $counts = array();
2663      foreach ( (array) $count as $row ) {
2664          $counts[ $row['post_mime_type'] ] = $row['num_posts'];
2665      }
2666      $counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and" );
2667  
2668      /**
2669       * Modify returned attachment counts by mime type.
2670       *
2671       * @since 3.7.0
2672       *
2673       * @param object $counts    An object containing the attachment counts by
2674       *                          mime type.
2675       * @param string $mime_type The mime type pattern used to filter the attachments
2676       *                          counted.
2677       */
2678      return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2679  }
2680  
2681  /**
2682   * Get default post mime types.
2683   *
2684   * @since 2.9.0
2685   * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
2686   *
2687   * @return array List of post mime types.
2688   */
2689  function get_post_mime_types() {
2690      $post_mime_types = array(   //    array( adj, noun )
2691          'image'       => array(
2692              __( 'Images' ),
2693              __( 'Manage Images' ),
2694              /* translators: %s: Number of images. */
2695              _n_noop(
2696                  'Image <span class="count">(%s)</span>',
2697                  'Images <span class="count">(%s)</span>'
2698              ),
2699          ),
2700          'audio'       => array(
2701              __( 'Audio' ),
2702              __( 'Manage Audio' ),
2703              /* translators: %s: Number of audio files. */
2704              _n_noop(
2705                  'Audio <span class="count">(%s)</span>',
2706                  'Audio <span class="count">(%s)</span>'
2707              ),
2708          ),
2709          'video'       => array(
2710              __( 'Video' ),
2711              __( 'Manage Video' ),
2712              /* translators: %s: Number of video files. */
2713              _n_noop(
2714                  'Video <span class="count">(%s)</span>',
2715                  'Video <span class="count">(%s)</span>'
2716              ),
2717          ),
2718          'document'    => array(
2719              __( 'Documents' ),
2720              __( 'Manage Documents' ),
2721              /* translators: %s: Number of documents. */
2722              _n_noop(
2723                  'Document <span class="count">(%s)</span>',
2724                  'Documents <span class="count">(%s)</span>'
2725              ),
2726          ),
2727          'spreadsheet' => array(
2728              __( 'Spreadsheets' ),
2729              __( 'Manage Spreadsheets' ),
2730              /* translators: %s: Number of spreadsheets. */
2731              _n_noop(
2732                  'Spreadsheet <span class="count">(%s)</span>',
2733                  'Spreadsheets <span class="count">(%s)</span>'
2734              ),
2735          ),
2736          'archive'     => array(
2737              __( 'Archives' ),
2738              __( 'Manage Archives' ),
2739              /* translators: %s: Number of archives. */
2740              _n_noop(
2741                  'Archive <span class="count">(%s)</span>',
2742                  'Archives <span class="count">(%s)</span>'
2743              ),
2744          ),
2745      );
2746  
2747      $ext_types  = wp_get_ext_types();
2748      $mime_types = wp_get_mime_types();
2749  
2750      foreach ( $post_mime_types as $group => $labels ) {
2751          if ( in_array( $group, array( 'image', 'audio', 'video' ) ) ) {
2752              continue;
2753          }
2754  
2755          if ( ! isset( $ext_types[ $group ] ) ) {
2756              unset( $post_mime_types[ $group ] );
2757              continue;
2758          }
2759  
2760          $group_mime_types = array();
2761          foreach ( $ext_types[ $group ] as $extension ) {
2762              foreach ( $mime_types as $exts => $mime ) {
2763                  if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
2764                      $group_mime_types[] = $mime;
2765                      break;
2766                  }
2767              }
2768          }
2769          $group_mime_types = implode( ',', array_unique( $group_mime_types ) );
2770  
2771          $post_mime_types[ $group_mime_types ] = $labels;
2772          unset( $post_mime_types[ $group ] );
2773      }
2774  
2775      /**
2776       * Filters the default list of post mime types.
2777       *
2778       * @since 2.5.0
2779       *
2780       * @param array $post_mime_types Default list of post mime types.
2781       */
2782      return apply_filters( 'post_mime_types', $post_mime_types );
2783  }
2784  
2785  /**
2786   * Check a MIME-Type against a list.
2787   *
2788   * If the wildcard_mime_types parameter is a string, it must be comma separated
2789   * list. If the real_mime_types is a string, it is also comma separated to
2790   * create the list.
2791   *
2792   * @since 2.5.0
2793   *
2794   * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2795   *                                          or flash (same as *flash*).
2796   * @param string|array $real_mime_types     Real post mime type values.
2797   * @return array array(wildcard=>array(real types)).
2798   */
2799  function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2800      $matches = array();
2801      if ( is_string( $wildcard_mime_types ) ) {
2802          $wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2803      }
2804      if ( is_string( $real_mime_types ) ) {
2805          $real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2806      }
2807  
2808      $patternses = array();
2809      $wild       = '[-._a-z0-9]*';
2810  
2811      foreach ( (array) $wildcard_mime_types as $type ) {
2812          $mimes = array_map( 'trim', explode( ',', $type ) );
2813          foreach ( $mimes as $mime ) {
2814              $regex                 = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2815              $patternses[][ $type ] = "^$regex$";
2816              if ( false === strpos( $mime, '/' ) ) {
2817                  $patternses[][ $type ] = "^$regex/";
2818                  $patternses[][ $type ] = $regex;
2819              }
2820          }
2821      }
2822      asort( $patternses );
2823  
2824      foreach ( $patternses as $patterns ) {
2825          foreach ( $patterns as $type => $pattern ) {
2826              foreach ( (array) $real_mime_types as $real ) {
2827                  if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[ $type ] ) || false === array_search( $real, $matches[ $type ] ) ) ) {
2828                      $matches[ $type ][] = $real;
2829                  }
2830              }
2831          }
2832      }
2833      return $matches;
2834  }
2835  
2836  /**
2837   * Convert MIME types into SQL.
2838   *
2839   * @since 2.5.0
2840   *
2841   * @param string|array $post_mime_types List of mime types or comma separated string
2842   *                                      of mime types.
2843   * @param string       $table_alias     Optional. Specify a table alias, if needed.
2844   *                                      Default empty.
2845   * @return string The SQL AND clause for mime searching.
2846   */
2847  function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2848      $where     = '';
2849      $wildcards = array( '', '%', '%/%' );
2850      if ( is_string( $post_mime_types ) ) {
2851          $post_mime_types = array_map( 'trim', explode( ',', $post_mime_types ) );
2852      }
2853  
2854      $wheres = array();
2855  
2856      foreach ( (array) $post_mime_types as $mime_type ) {
2857          $mime_type = preg_replace( '/\s/', '', $mime_type );
2858          $slashpos  = strpos( $mime_type, '/' );
2859          if ( false !== $slashpos ) {
2860              $mime_group    = preg_replace( '/[^-*.a-zA-Z0-9]/', '', substr( $mime_type, 0, $slashpos ) );
2861              $mime_subgroup = preg_replace( '/[^-*.+a-zA-Z0-9]/', '', substr( $mime_type, $slashpos + 1 ) );
2862              if ( empty( $mime_subgroup ) ) {
2863                  $mime_subgroup = '*';
2864              } else {
2865                  $mime_subgroup = str_replace( '/', '', $mime_subgroup );
2866              }
2867              $mime_pattern = "$mime_group/$mime_subgroup";
2868          } else {
2869              $mime_pattern = preg_replace( '/[^-*.a-zA-Z0-9]/', '', $mime_type );
2870              if ( false === strpos( $mime_pattern, '*' ) ) {
2871                  $mime_pattern .= '/*';
2872              }
2873          }
2874  
2875          $mime_pattern = preg_replace( '/\*+/', '%', $mime_pattern );
2876  
2877          if ( in_array( $mime_type, $wildcards ) ) {
2878              return '';
2879          }
2880  
2881          if ( false !== strpos( $mime_pattern, '%' ) ) {
2882              $wheres[] = empty( $table_alias ) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2883          } else {
2884              $wheres[] = empty( $table_alias ) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2885          }
2886      }
2887      if ( ! empty( $wheres ) ) {
2888          $where = ' AND (' . join( ' OR ', $wheres ) . ') ';
2889      }
2890      return $where;
2891  }
2892  
2893  /**
2894   * Trash or delete a post or page.
2895   *
2896   * When the post and page is permanently deleted, everything that is tied to
2897   * it is deleted also. This includes comments, post meta fields, and terms
2898   * associated with the post.
2899   *
2900   * The post or page is moved to trash instead of permanently deleted unless
2901   * trash is disabled, item is already in the trash, or $force_delete is true.
2902   *
2903   * @since 1.0.0
2904   *
2905   * @global wpdb $wpdb WordPress database abstraction object.
2906   * @see wp_delete_attachment()
2907   * @see wp_trash_post()
2908   *
2909   * @param int  $postid       Optional. Post ID. Default 0.
2910   * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2911   *                           Default false.
2912   * @return WP_Post|false|null Post data on success, false or null on failure.
2913   */
2914  function wp_delete_post( $postid = 0, $force_delete = false ) {
2915      global $wpdb;
2916  
2917      $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $postid ) );
2918  
2919      if ( ! $post ) {
2920          return $post;
2921      }
2922  
2923      $post = get_post( $post );
2924  
2925      if ( ! $force_delete && ( 'post' === $post->post_type || 'page' === $post->post_type ) && 'trash' !== get_post_status( $postid ) && EMPTY_TRASH_DAYS ) {
2926          return wp_trash_post( $postid );
2927      }
2928  
2929      if ( 'attachment' === $post->post_type ) {
2930          return wp_delete_attachment( $postid, $force_delete );
2931      }
2932  
2933      /**
2934       * Filters whether a post deletion should take place.
2935       *
2936       * @since 4.4.0
2937       *
2938       * @param bool|null $delete       Whether to go forward with deletion.
2939       * @param WP_Post   $post         Post object.
2940       * @param bool      $force_delete Whether to bypass the trash.
2941       */
2942      $check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2943      if ( null !== $check ) {
2944          return $check;
2945      }
2946  
2947      /**
2948       * Fires before a post is deleted, at the start of wp_delete_post().
2949       *
2950       * @since 3.2.0
2951       *
2952       * @see wp_delete_post()
2953       *
2954       * @param int $postid Post ID.
2955       */
2956      do_action( 'before_delete_post', $postid );
2957  
2958      delete_post_meta( $postid, '_wp_trash_meta_status' );
2959      delete_post_meta( $postid, '_wp_trash_meta_time' );
2960  
2961      wp_delete_object_term_relationships( $postid, get_object_taxonomies( $post->post_type ) );
2962  
2963      $parent_data  = array( 'post_parent' => $post->post_parent );
2964      $parent_where = array( 'post_parent' => $postid );
2965  
2966      if ( is_post_type_hierarchical( $post->post_type ) ) {
2967          // Point children of this page to its parent, also clean the cache of affected children.
2968          $children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2969          $children       = $wpdb->get_results( $children_query );
2970          if ( $children ) {
2971              $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2972          }
2973      }
2974  
2975      // Do raw query. wp_get_post_revisions() is filtered.
2976      $revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2977      // Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2978      foreach ( $revision_ids as $revision_id ) {
2979          wp_delete_post_revision( $revision_id );
2980      }
2981  
2982      // Point all attachments to this post up one level.
2983      $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
2984  
2985      wp_defer_comment_counting( true );
2986  
2987      $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ) );
2988      foreach ( $comment_ids as $comment_id ) {
2989          wp_delete_comment( $comment_id, true );
2990      }
2991  
2992      wp_defer_comment_counting( false );
2993  
2994      $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ) );
2995      foreach ( $post_meta_ids as $mid ) {
2996          delete_metadata_by_mid( 'post', $mid );
2997      }
2998  
2999      /**
3000       * Fires immediately before a post is deleted from the database.
3001       *
3002       * @since 1.2.0
3003       *
3004       * @param int $postid Post ID.
3005       */
3006      do_action( 'delete_post', $postid );
3007      $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
3008      if ( ! $result ) {
3009          return false;
3010      }
3011  
3012      /**
3013       * Fires immediately after a post is deleted from the database.
3014       *
3015       * @since 2.2.0
3016       *
3017       * @param int $postid Post ID.
3018       */
3019      do_action( 'deleted_post', $postid );
3020  
3021      clean_post_cache( $post );
3022  
3023      if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
3024          foreach ( $children as $child ) {
3025              clean_post_cache( $child );
3026          }
3027      }
3028  
3029      wp_clear_scheduled_hook( 'publish_future_post', array( $postid ) );
3030  
3031      /**
3032       * Fires after a post is deleted, at the conclusion of wp_delete_post().
3033       *
3034       * @since 3.2.0
3035       *
3036       * @see wp_delete_post()
3037       *
3038       * @param int $postid Post ID.
3039       */
3040      do_action( 'after_delete_post', $postid );
3041  
3042      return $post;
3043  }
3044  
3045  /**
3046   * Reset the page_on_front, show_on_front, and page_for_post settings when
3047   * a linked page is deleted or trashed.
3048   *
3049   * Also ensures the post is no longer sticky.
3050   *
3051   * @since 3.7.0
3052   * @access private
3053   *
3054   * @param int $post_id Post ID.
3055   */
3056  function _reset_front_page_settings_for_post( $post_id ) {
3057      $post = get_post( $post_id );
3058      if ( 'page' == $post->post_type ) {
3059          /*
3060           * If the page is defined in option page_on_front or post_for_posts,
3061           * adjust the corresponding options.
3062           */
3063          if ( get_option( 'page_on_front' ) == $post->ID ) {
3064              update_option( 'show_on_front', 'posts' );
3065              update_option( 'page_on_front', 0 );
3066          }
3067          if ( get_option( 'page_for_posts' ) == $post->ID ) {
3068              update_option( 'page_for_posts', 0 );
3069          }
3070      }
3071      unstick_post( $post->ID );
3072  }
3073  
3074  /**
3075   * Move a post or page to the Trash
3076   *
3077   * If trash is disabled, the post or page is permanently deleted.
3078   *
3079   * @since 2.9.0
3080   *
3081   * @see wp_delete_post()
3082   *
3083   * @param int $post_id Optional. Post ID. Default is ID of the global $post
3084   *                     if EMPTY_TRASH_DAYS equals true.
3085   * @return WP_Post|false|null Post data on success, false or null on failure.
3086   */
3087  function wp_trash_post( $post_id = 0 ) {
3088      if ( ! EMPTY_TRASH_DAYS ) {
3089          return wp_delete_post( $post_id, true );
3090      }
3091  
3092      $post = get_post( $post_id );
3093  
3094      if ( ! $post ) {
3095          return $post;
3096      }
3097  
3098      if ( 'trash' === $post->post_status ) {
3099          return false;
3100      }
3101  
3102      /**
3103       * Filters whether a post trashing should take place.
3104       *
3105       * @since 4.9.0
3106       *
3107       * @param bool|null $trash Whether to go forward with trashing.
3108       * @param WP_Post   $post  Post object.
3109       */
3110      $check = apply_filters( 'pre_trash_post', null, $post );
3111      if ( null !== $check ) {
3112          return $check;
3113      }
3114  
3115      /**
3116       * Fires before a post is sent to the trash.
3117       *
3118       * @since 3.3.0
3119       *
3120       * @param int $post_id Post ID.
3121       */
3122      do_action( 'wp_trash_post', $post_id );
3123  
3124      add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
3125      add_post_meta( $post_id, '_wp_trash_meta_time', time() );
3126  
3127      $post_updated = wp_update_post(
3128          array(
3129              'ID'          => $post_id,
3130              'post_status' => 'trash',
3131          )
3132      );
3133  
3134      if ( ! $post_updated ) {
3135          return false;
3136      }
3137  
3138      wp_trash_post_comments( $post_id );
3139  
3140      /**
3141       * Fires after a post is sent to the trash.
3142       *
3143       * @since 2.9.0
3144       *
3145       * @param int $post_id Post ID.
3146       */
3147      do_action( 'trashed_post', $post_id );
3148  
3149      return $post;
3150  }
3151  
3152  /**
3153   * Restore a post or page from the Trash.
3154   *
3155   * @since 2.9.0
3156   *
3157   * @param int $post_id Optional. Post ID. Default is ID of the global $post.
3158   * @return WP_Post|false|null Post data on success, false or null on failure.
3159   */
3160  function wp_untrash_post( $post_id = 0 ) {
3161      $post = get_post( $post_id );
3162  
3163      if ( ! $post ) {
3164          return $post;
3165      }
3166  
3167      if ( 'trash' !== $post->post_status ) {
3168          return false;
3169      }
3170  
3171      /**
3172       * Filters whether a post untrashing should take place.
3173       *
3174       * @since 4.9.0
3175       *
3176       * @param bool|null $untrash Whether to go forward with untrashing.
3177       * @param WP_Post   $post    Post object.
3178       */
3179      $check = apply_filters( 'pre_untrash_post', null, $post );
3180      if ( null !== $check ) {
3181          return $check;
3182      }
3183  
3184      /**
3185       * Fires before a post is restored from the trash.
3186       *
3187       * @since 2.9.0
3188       *
3189       * @param int $post_id Post ID.
3190       */
3191      do_action( 'untrash_post', $post_id );
3192  
3193      $post_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
3194  
3195      delete_post_meta( $post_id, '_wp_trash_meta_status' );
3196      delete_post_meta( $post_id, '_wp_trash_meta_time' );
3197  
3198      $post_updated = wp_update_post(
3199          array(
3200              'ID'          => $post_id,
3201              'post_status' => $post_status,
3202          )
3203      );
3204  
3205      if ( ! $post_updated ) {
3206          return false;
3207      }
3208  
3209      wp_untrash_post_comments( $post_id );
3210  
3211      /**
3212       * Fires after a post is restored from the trash.
3213       *
3214       * @since 2.9.0
3215       *
3216       * @param int $post_id Post ID.
3217       */
3218      do_action( 'untrashed_post', $post_id );
3219  
3220      return $post;
3221  }
3222  
3223  /**
3224   * Moves comments for a post to the trash.
3225   *
3226   * @since 2.9.0
3227   *
3228   * @global wpdb $wpdb WordPress database abstraction object.
3229   *
3230   * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3231   * @return mixed|void False on failure.
3232   */
3233  function wp_trash_post_comments( $post = null ) {
3234      global $wpdb;
3235  
3236      $post = get_post( $post );
3237      if ( empty( $post ) ) {
3238          return;
3239      }
3240  
3241      $post_id = $post->ID;
3242  
3243      /**
3244       * Fires before comments are sent to the trash.
3245       *
3246       * @since 2.9.0
3247       *
3248       * @param int $post_id Post ID.
3249       */
3250      do_action( 'trash_post_comments', $post_id );
3251  
3252      $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
3253      if ( empty( $comments ) ) {
3254          return;
3255      }
3256  
3257      // Cache current status for each comment.
3258      $statuses = array();
3259      foreach ( $comments as $comment ) {
3260          $statuses[ $comment->comment_ID ] = $comment->comment_approved;
3261      }
3262      add_post_meta( $post_id, '_wp_trash_meta_comments_status', $statuses );
3263  
3264      // Set status for all comments to post-trashed.
3265      $result = $wpdb->update( $wpdb->comments, array( 'comment_approved' => 'post-trashed' ), array( 'comment_post_ID' => $post_id ) );
3266  
3267      clean_comment_cache( array_keys( $statuses ) );
3268  
3269      /**
3270       * Fires after comments are sent to the trash.
3271       *
3272       * @since 2.9.0
3273       *
3274       * @param int   $post_id  Post ID.
3275       * @param array $statuses Array of comment statuses.
3276       */
3277      do_action( 'trashed_post_comments', $post_id, $statuses );
3278  
3279      return $result;
3280  }
3281  
3282  /**
3283   * Restore comments for a post from the trash.
3284   *
3285   * @since 2.9.0
3286   *
3287   * @global wpdb $wpdb WordPress database abstraction object.
3288   *
3289   * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3290   * @return true|void
3291   */
3292  function wp_untrash_post_comments( $post = null ) {
3293      global $wpdb;
3294  
3295      $post = get_post( $post );
3296      if ( empty( $post ) ) {
3297          return;
3298      }
3299  
3300      $post_id = $post->ID;
3301  
3302      $statuses = get_post_meta( $post_id, '_wp_trash_meta_comments_status', true );
3303  
3304      if ( empty( $statuses ) ) {
3305          return true;
3306      }
3307  
3308      /**
3309       * Fires before comments are restored for a post from the trash.
3310       *
3311       * @since 2.9.0
3312       *
3313       * @param int $post_id Post ID.
3314       */
3315      do_action( 'untrash_post_comments', $post_id );
3316  
3317      // Restore each comment to its original status.
3318      $group_by_status = array();
3319      foreach ( $statuses as $comment_id => $comment_status ) {
3320          $group_by_status[ $comment_status ][] = $comment_id;
3321      }
3322  
3323      foreach ( $group_by_status as $status => $comments ) {
3324          // Sanity check. This shouldn't happen.
3325          if ( 'post-trashed' == $status ) {
3326              $status = '0';
3327          }
3328          $comments_in = implode( ', ', array_map( 'intval', $comments ) );
3329          $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
3330      }
3331  
3332      clean_comment_cache( array_keys( $statuses ) );
3333  
3334      delete_post_meta( $post_id, '_wp_trash_meta_comments_status' );
3335  
3336      /**
3337       * Fires after comments are restored for a post from the trash.
3338       *
3339       * @since 2.9.0
3340       *
3341       * @param int $post_id Post ID.
3342       */
3343      do_action( 'untrashed_post_comments', $post_id );
3344  }
3345  
3346  /**
3347   * Retrieve the list of categories for a post.
3348   *
3349   * Compatibility layer for themes and plugins. Also an easy layer of abstraction
3350   * away from the complexity of the taxonomy layer.
3351   *
3352   * @since 2.1.0
3353   *
3354   * @see wp_get_object_terms()
3355   *
3356   * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
3357   *                       global $post. Default 0.
3358   * @param array $args    Optional. Category query parameters. Default empty array.
3359   *                       See WP_Term_Query::__construct() for supported arguments.
3360   * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
3361   *                        'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
3362   *                        is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
3363   *                        WP_Error object if 'category' taxonomy doesn't exist.
3364   */
3365  function wp_get_post_categories( $post_id = 0, $args = array() ) {
3366      $post_id = (int) $post_id;
3367  
3368      $defaults = array( 'fields' => 'ids' );
3369      $args     = wp_parse_args( $args, $defaults );
3370  
3371      $cats = wp_get_object_terms( $post_id, 'category', $args );
3372      return $cats;
3373  }
3374  
3375  /**
3376   * Retrieve the tags for a post.
3377   *
3378   * There is only one default for this function, called 'fields' and by default
3379   * is set to 'all'. There are other defaults that can be overridden in
3380   * wp_get_object_terms().
3381   *
3382   * @since 2.3.0
3383   *
3384   * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
3385   *                       global $post. Default 0.
3386   * @param array $args    Optional. Tag query parameters. Default empty array.
3387   *                       See WP_Term_Query::__construct() for supported arguments.
3388   * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
3389   *                        WP_Error object if 'post_tag' taxonomy doesn't exist.
3390   */
3391  function wp_get_post_tags( $post_id = 0, $args = array() ) {
3392      return wp_get_post_terms( $post_id, 'post_tag', $args );
3393  }
3394  
3395  /**
3396   * Retrieves the terms for a post.
3397   *
3398   * @since 2.8.0
3399   *
3400   * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the
3401   *                               global $post. Default 0.
3402   * @param string|array $taxonomy Optional. The taxonomy slug or array of slugs for which
3403   *                               to retrieve terms. Default 'post_tag'.
3404   * @param array        $args     {
3405   *     Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
3406   *
3407   *     @type string $fields Term fields to retrieve. Default 'all'.
3408   * }
3409   * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
3410   *                        WP_Error object if `$taxonomy` doesn't exist.
3411   */
3412  function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
3413      $post_id = (int) $post_id;
3414  
3415      $defaults = array( 'fields' => 'all' );
3416      $args     = wp_parse_args( $args, $defaults );
3417  
3418      $tags = wp_get_object_terms( $post_id, $taxonomy, $args );
3419  
3420      return $tags;
3421  }
3422  
3423  /**
3424   * Retrieve a number of recent posts.
3425   *
3426   * @since 1.0.0
3427   *
3428   * @see get_posts()
3429   *
3430   * @param array  $args   Optional. Arguments to retrieve posts. Default empty array.
3431   * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which correspond to
3432   *                       a WP_Post object or an associative array, respectively. Default ARRAY_A.
3433   * @return array|false Array of recent posts, where the type of each element is determined by $output parameter.
3434   *                     Empty array on failure.
3435   */
3436  function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
3437  
3438      if ( is_numeric( $args ) ) {
3439          _deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
3440          $args = array( 'numberposts' => absint( $args ) );
3441      }
3442  
3443      // Set default arguments.
3444      $defaults = array(
3445          'numberposts'      => 10,
3446          'offset'           => 0,
3447          'category'         => 0,
3448          'orderby'          => 'post_date',
3449          'order'            => 'DESC',
3450          'include'          => '',
3451          'exclude'          => '',
3452          'meta_key'         => '',
3453          'meta_value'       => '',
3454          'post_type'        => 'post',
3455          'post_status'      => 'draft, publish, future, pending, private',
3456          'suppress_filters' => true,
3457      );
3458  
3459      $parsed_args = wp_parse_args( $args, $defaults );
3460  
3461      $results = get_posts( $parsed_args );
3462  
3463      // Backward compatibility. Prior to 3.1 expected posts to be returned in array.
3464      if ( ARRAY_A == $output ) {
3465          foreach ( $results as $key => $result ) {
3466              $results[ $key ] = get_object_vars( $result );
3467          }
3468          return $results ? $results : array();
3469      }
3470  
3471      return $results ? $results : false;
3472  
3473  }
3474  
3475  /**
3476   * Insert or update a post.
3477   *
3478   * If the $postarr parameter has 'ID' set to a value, then post will be updated.
3479   *
3480   * You can set the post date manually, by setting the values for 'post_date'
3481   * and 'post_date_gmt' keys. You can close the comments or open the comments by
3482   * setting the value for 'comment_status' key.
3483   *
3484   * @since 1.0.0
3485   * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
3486   * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
3487   *
3488   * @see sanitize_post()
3489   * @global wpdb $wpdb WordPress database abstraction object.
3490   *
3491   * @param array $postarr {
3492   *     An array of elements that make up a post to update or insert.
3493   *
3494   *     @type int    $ID                    The post ID. If equal to something other than 0,
3495   *                                         the post with that ID will be updated. Default 0.
3496   *     @type int    $post_author           The ID of the user who added the post. Default is
3497   *                                         the current user ID.
3498   *     @type string $post_date             The date of the post. Default is the current time.
3499   *     @type string $post_date_gmt         The date of the post in the GMT timezone. Default is
3500   *                                         the value of `$post_date`.
3501   *     @type mixed  $post_content          The post content. Default empty.
3502   *     @type string $post_content_filtered The filtered post content. Default empty.
3503   *     @type string $post_title            The post title. Default empty.
3504   *     @type string $post_excerpt          The post excerpt. Default empty.
3505   *     @type string $post_status           The post status. Default 'draft'.
3506   *     @type string $post_type             The post type. Default 'post'.
3507   *     @type string $comment_status        Whether the post can accept comments. Accepts 'open' or 'closed'.
3508   *                                         Default is the value of 'default_comment_status' option.
3509   *     @type string $ping_status           Whether the post can accept pings. Accepts 'open' or 'closed'.
3510   *                                         Default is the value of 'default_ping_status' option.
3511   *     @type string $post_password         The password to access the post. Default empty.
3512   *     @type string $post_name             The post name. Default is the sanitized post title
3513   *                                         when creating a new post.
3514   *     @type string $to_ping               Space or carriage return-separated list of URLs to ping.
3515   *                                         Default empty.
3516   *     @type string $pinged                Space or carriage return-separated list of URLs that have
3517   *                                         been pinged. Default empty.
3518   *     @type string $post_modified         The date when the post was last modified. Default is
3519   *                                         the current time.
3520   *     @type string $post_modified_gmt     The date when the post was last modified in the GMT
3521   *                                         timezone. Default is the current time.
3522   *     @type int    $post_parent           Set this for the post it belongs to, if any. Default 0.
3523   *     @type int    $menu_order            The order the post should be displayed in. Default 0.
3524   *     @type string $post_mime_type        The mime type of the post. Default empty.
3525   *     @type string $guid                  Global Unique ID for referencing the post. Default empty.
3526   *     @type array  $post_category         Array of category IDs.
3527   *                                         Defaults to value of the 'default_category' option.
3528   *     @type array  $tags_input            Array of tag names, slugs, or IDs. Default empty.
3529   *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
3530   *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
3531   * }
3532   * @param bool  $wp_error Optional. Whether to return a WP_Error on failure. Default false.
3533   * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
3534   */
3535  function wp_insert_post( $postarr, $wp_error = false ) {
3536      global $wpdb;
3537  
3538      $user_id = get_current_user_id();
3539  
3540      $defaults = array(
3541          'post_author'           => $user_id,
3542          'post_content'          => '',
3543          'post_content_filtered' => '',
3544          'post_title'            => '',
3545          'post_excerpt'          => '',
3546          'post_status'           => 'draft',
3547          'post_type'             => 'post',
3548          'comment_status'        => '',
3549          'ping_status'           => '',
3550          'post_password'         => '',
3551          'to_ping'               => '',
3552          'pinged'                => '',
3553          'post_parent'           => 0,
3554          'menu_order'            => 0,
3555          'guid'                  => '',
3556          'import_id'             => 0,
3557          'context'               => '',
3558      );
3559  
3560      $postarr = wp_parse_args( $postarr, $defaults );
3561  
3562      unset( $postarr['filter'] );
3563  
3564      $postarr = sanitize_post( $postarr, 'db' );
3565  
3566      // Are we updating or creating?
3567      $post_ID = 0;
3568      $update  = false;
3569      $guid    = $postarr['guid'];
3570  
3571      if ( ! empty( $postarr['ID'] ) ) {
3572          $update = true;
3573  
3574          // Get the post ID and GUID.
3575          $post_ID     = $postarr['ID'];
3576          $post_before = get_post( $post_ID );
3577          if ( is_null( $post_before ) ) {
3578              if ( $wp_error ) {
3579                  return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3580              }
3581              return 0;
3582          }
3583  
3584          $guid            = get_post_field( 'guid', $post_ID );
3585          $previous_status = get_post_field( 'post_status', $post_ID );
3586      } else {
3587          $previous_status = 'new';
3588      }
3589  
3590      $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3591  
3592      $post_title   = $postarr['post_title'];
3593      $post_content = $postarr['post_content'];
3594      $post_excerpt = $postarr['post_excerpt'];
3595      if ( isset( $postarr['post_name'] ) ) {
3596          $post_name = $postarr['post_name'];
3597      } elseif ( $update ) {
3598          // For an update, don't modify the post_name if it wasn't supplied as an argument.
3599          $post_name = $post_before->post_name;
3600      }
3601  
3602      $maybe_empty = 'attachment' !== $post_type
3603          && ! $post_content && ! $post_title && ! $post_excerpt
3604          && post_type_supports( $post_type, 'editor' )
3605          && post_type_supports( $post_type, 'title' )
3606          && post_type_supports( $post_type, 'excerpt' );
3607  
3608      /**
3609       * Filters whether the post should be considered "empty".
3610       *
3611       * The post is considered "empty" if both:
3612       * 1. The post type supports the title, editor, and excerpt fields
3613       * 2. The title, editor, and excerpt fields are all empty
3614       *
3615       * Returning a truthy value to the filter will effectively short-circuit
3616       * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
3617       * will be returned instead.
3618       *
3619       * @since 3.3.0
3620       *
3621       * @param bool  $maybe_empty Whether the post should be considered "empty".
3622       * @param array $postarr     Array of post data.
3623       */
3624      if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3625          if ( $wp_error ) {
3626              return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3627          } else {
3628              return 0;
3629          }
3630      }
3631  
3632      $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3633      if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3634          $post_status = 'inherit';
3635      }
3636  
3637      if ( ! empty( $postarr['post_category'] ) ) {
3638          // Filter out empty terms.
3639          $post_category = array_filter( $postarr['post_category'] );
3640      }
3641  
3642      // Make sure we set a valid category.
3643      if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
3644          // 'post' requires at least one category.
3645          if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3646              $post_category = array( get_option( 'default_category' ) );
3647          } else {
3648              $post_category = array();
3649          }
3650      }
3651  
3652      /*
3653       * Don't allow contributors to set the post slug for pending review posts.
3654       *
3655       * For new posts check the primitive capability, for updates check the meta capability.
3656       */
3657      $post_type_object = get_post_type_object( $post_type );
3658  
3659      if ( ! $update && 'pending' === $post_status && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
3660          $post_name = '';
3661      } elseif ( $update && 'pending' === $post_status && ! current_user_can( 'publish_post', $post_ID ) ) {
3662          $post_name = '';
3663      }
3664  
3665      /*
3666       * Create a valid post name. Drafts and pending posts are allowed to have
3667       * an empty post name.
3668       */
3669      if ( empty( $post_name ) ) {
3670          if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3671              $post_name = sanitize_title( $post_title );
3672          } else {
3673              $post_name = '';
3674          }
3675      } else {
3676          // On updates, we need to check to see if it's using the old, fixed sanitization context.
3677          $check_name = sanitize_title( $post_name, '', 'old-save' );
3678          if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3679              $post_name = $check_name;
3680          } else { // new post, or slug has changed.
3681              $post_name = sanitize_title( $post_name );
3682          }
3683      }
3684  
3685      /*
3686       * If the post date is empty (due to having been new or a draft) and status
3687       * is not 'draft' or 'pending', set date to now.
3688       */
3689      if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3690          if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3691              $post_date = current_time( 'mysql' );
3692          } else {
3693              $post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3694          }
3695      } else {
3696          $post_date = $postarr['post_date'];
3697      }
3698  
3699      // Validate the date.
3700      $mm         = substr( $post_date, 5, 2 );
3701      $jj         = substr( $post_date, 8, 2 );
3702      $aa         = substr( $post_date, 0, 4 );
3703      $valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3704      if ( ! $valid_date ) {
3705          if ( $wp_error ) {
3706              return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
3707          } else {
3708              return 0;
3709          }
3710      }
3711  
3712      if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3713          if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3714              $post_date_gmt = get_gmt_from_date( $post_date );
3715          } else {
3716              $post_date_gmt = '0000-00-00 00:00:00';
3717          }
3718      } else {
3719          $post_date_gmt = $postarr['post_date_gmt'];
3720      }
3721  
3722      if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3723          $post_modified     = current_time( 'mysql' );
3724          $post_modified_gmt = current_time( 'mysql', 1 );
3725      } else {
3726          $post_modified     = $post_date;
3727          $post_modified_gmt = $post_date_gmt;
3728      }
3729  
3730      if ( 'attachment' !== $post_type ) {
3731          if ( 'publish' === $post_status ) {
3732              // String comparison to work around far future dates (year 2038+) on 32-bit systems.
3733              if ( $post_date_gmt > gmdate( 'Y-m-d H:i:59' ) ) {
3734                  $post_status = 'future';
3735              }
3736          } elseif ( 'future' === $post_status ) {
3737              if ( $post_date_gmt <= gmdate( 'Y-m-d H:i:59' ) ) {
3738                  $post_status = 'publish';
3739              }
3740          }
3741      }
3742  
3743      // Comment status.
3744      if ( empty( $postarr['comment_status'] ) ) {
3745          if ( $update ) {
3746              $comment_status = 'closed';
3747          } else {
3748              $comment_status = get_default_comment_status( $post_type );
3749          }
3750      } else {
3751          $comment_status = $postarr['comment_status'];
3752      }
3753  
3754      // These variables are needed by compact() later.
3755      $post_content_filtered = $postarr['post_content_filtered'];
3756      $post_author           = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3757      $ping_status           = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3758      $to_ping               = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3759      $pinged                = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3760      $import_id             = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3761  
3762      /*
3763       * The 'wp_insert_post_parent' filter expects all variables to be present.
3764       * Previously, these variables would have already been extracted
3765       */
3766      if ( isset( $postarr['menu_order'] ) ) {
3767          $menu_order = (int) $postarr['menu_order'];
3768      } else {
3769          $menu_order = 0;
3770      }
3771  
3772      $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3773      if ( 'private' == $post_status ) {
3774          $post_password = '';
3775      }
3776  
3777      if ( isset( $postarr['post_parent'] ) ) {
3778          $post_parent = (int) $postarr['post_parent'];
3779      } else {
3780          $post_parent = 0;
3781      }
3782  
3783      $new_postarr = array_merge(
3784          array(
3785              'ID' => $post_ID,
3786          ),
3787          compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
3788      );
3789  
3790      /**
3791       * Filters the post parent -- used to check for and prevent hierarchy loops.
3792       *
3793       * @since 3.1.0
3794       *
3795       * @param int   $post_parent Post parent ID.
3796       * @param int   $post_ID     Post ID.
3797       * @param array $new_postarr Array of parsed post data.
3798       * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
3799       */
3800      $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
3801  
3802      /*
3803       * If the post is being untrashed and it has a desired slug stored in post meta,
3804       * reassign it.
3805       */
3806      if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3807          $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3808          if ( $desired_post_slug ) {
3809              delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3810              $post_name = $desired_post_slug;
3811          }
3812      }
3813  
3814      // If a trashed post has the desired slug, change it and let this post have it.
3815      if ( 'trash' !== $post_status && $post_name ) {
3816          wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3817      }
3818  
3819      // When trashing an existing post, change its slug to allow non-trashed posts to use it.
3820      if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3821          $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3822      }
3823  
3824      $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3825  
3826      // Don't unslash.
3827      $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3828  
3829      // Expected_slashed (everything!).
3830      $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' );
3831  
3832      $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3833  
3834      foreach ( $emoji_fields as $emoji_field ) {
3835          if ( isset( $data[ $emoji_field ] ) ) {
3836              $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3837              if ( 'utf8' === $charset ) {
3838                  $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3839              }
3840          }
3841      }
3842  
3843      if ( 'attachment' === $post_type ) {
3844          /**
3845           * Filters attachment post data before it is updated in or added to the database.
3846           *
3847           * @since 3.9.0
3848           *
3849           * @param array $data    An array of sanitized attachment post data.
3850           * @param array $postarr An array of unsanitized attachment post data.
3851           */
3852          $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3853      } else {
3854          /**
3855           * Filters slashed post data just before it is inserted into the database.
3856           *
3857           * @since 2.7.0
3858           *
3859           * @param array $data    An array of slashed post data.
3860           * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3861           */
3862          $data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3863      }
3864      $data  = wp_unslash( $data );
3865      $where = array( 'ID' => $post_ID );
3866  
3867      if ( $update ) {
3868          /**
3869           * Fires immediately before an existing post is updated in the database.
3870           *
3871           * @since 2.5.0
3872           *
3873           * @param int   $post_ID Post ID.
3874           * @param array $data    Array of unslashed post data.
3875           */
3876          do_action( 'pre_post_update', $post_ID, $data );
3877          if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3878              if ( $wp_error ) {
3879                  return new WP_Error( 'db_update_error', __( 'Could not update post in the database' ), $wpdb->last_error );
3880              } else {
3881                  return 0;
3882              }
3883          }
3884      } else {
3885          // If there is a suggested ID, use it if not already present.
3886          if ( ! empty( $import_id ) ) {
3887              $import_id = (int) $import_id;
3888              if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
3889                  $data['ID'] = $import_id;
3890              }
3891          }
3892          if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3893              if ( $wp_error ) {
3894                  return new WP_Error( 'db_insert_error', __( 'Could not insert post into the database' ), $wpdb->last_error );
3895              } else {
3896                  return 0;
3897              }
3898          }
3899          $post_ID = (int) $wpdb->insert_id;
3900  
3901          // Use the newly generated $post_ID.
3902          $where = array( 'ID' => $post_ID );
3903      }
3904  
3905      if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3906          $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3907          $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3908          clean_post_cache( $post_ID );
3909      }
3910  
3911      if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3912          wp_set_post_categories( $post_ID, $post_category );
3913      }
3914  
3915      if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3916          wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3917      }
3918  
3919      // New-style support for all custom taxonomies.
3920      if ( ! empty( $postarr['tax_input'] ) ) {
3921          foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3922              $taxonomy_obj = get_taxonomy( $taxonomy );
3923              if ( ! $taxonomy_obj ) {
3924                  /* translators: %s: Taxonomy name. */
3925                  _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3926                  continue;
3927              }
3928  
3929              // array = hierarchical, string = non-hierarchical.
3930              if ( is_array( $tags ) ) {
3931                  $tags = array_filter( $tags );
3932              }
3933              if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3934                  wp_set_post_terms( $post_ID, $tags, $taxonomy );
3935              }
3936          }
3937      }
3938  
3939      if ( ! empty( $postarr['meta_input'] ) ) {
3940          foreach ( $postarr['meta_input'] as $field => $value ) {
3941              update_post_meta( $post_ID, $field, $value );
3942          }
3943      }
3944  
3945      $current_guid = get_post_field( 'guid', $post_ID );
3946  
3947      // Set GUID.
3948      if ( ! $update && '' == $current_guid ) {
3949          $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3950      }
3951  
3952      if ( 'attachment' === $postarr['post_type'] ) {
3953          if ( ! empty( $postarr['file'] ) ) {
3954              update_attached_file( $post_ID, $postarr['file'] );
3955          }
3956  
3957          if ( ! empty( $postarr['context'] ) ) {
3958              add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3959          }
3960      }
3961  
3962      // Set or remove featured image.
3963      if ( isset( $postarr['_thumbnail_id'] ) ) {
3964          $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3965          if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3966              if ( wp_attachment_is( 'audio', $post_ID ) ) {
3967                  $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3968              } elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3969                  $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3970              }
3971          }
3972  
3973          if ( $thumbnail_support ) {
3974              $thumbnail_id = intval( $postarr['_thumbnail_id'] );
3975              if ( -1 === $thumbnail_id ) {
3976                  delete_post_thumbnail( $post_ID );
3977              } else {
3978                  set_post_thumbnail( $post_ID, $thumbnail_id );
3979              }
3980          }
3981      }
3982  
3983      clean_post_cache( $post_ID );
3984  
3985      $post = get_post( $post_ID );
3986  
3987      if ( ! empty( $postarr['page_template'] ) ) {
3988          $post->page_template = $postarr['page_template'];
3989          $page_templates      = wp_get_theme()->get_page_templates( $post );
3990          if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
3991              if ( $wp_error ) {
3992                  return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
3993              }
3994              update_post_meta( $post_ID, '_wp_page_template', 'default' );
3995          } else {
3996              update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
3997          }
3998      }
3999  
4000      if ( 'attachment' !== $postarr['post_type'] ) {
4001          wp_transition_post_status( $data['post_status'], $previous_status, $post );
4002      } else {
4003          if ( $update ) {
4004              /**
4005               * Fires once an existing attachment has been updated.
4006               *
4007               * @since 2.0.0
4008               *
4009               * @param int $post_ID Attachment ID.
4010               */
4011              do_action( 'edit_attachment', $post_ID );
4012              $post_after = get_post( $post_ID );
4013  
4014              /**
4015               * Fires once an existing attachment has been updated.
4016               *
4017               * @since 4.4.0
4018               *
4019               * @param int     $post_ID      Post ID.
4020               * @param WP_Post $post_after   Post object following the update.
4021               * @param WP_Post $post_before  Post object before the update.
4022               */
4023              do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
4024          } else {
4025  
4026              /**
4027               * Fires once an attachment has been added.
4028               *
4029               * @since 2.0.0
4030               *
4031               * @param int $post_ID Attachment ID.
4032               */
4033              do_action( 'add_attachment', $post_ID );
4034          }
4035  
4036          return $post_ID;
4037      }
4038  
4039      if ( $update ) {
4040          /**
4041           * Fires once an existing post has been updated.
4042           *
4043           * The dynamic portion of the hook name, `$post->post_type`, refers to
4044           * the post type slug.
4045           *
4046           * @since 5.1.0
4047           *
4048           * @param int     $post_ID Post ID.
4049           * @param WP_Post $post    Post object.
4050           */
4051          do_action( "edit_post_{$post->post_type}", $post_ID, $post );
4052  
4053          /**
4054           * Fires once an existing post has been updated.
4055           *
4056           * @since 1.2.0
4057           *
4058           * @param int     $post_ID Post ID.
4059           * @param WP_Post $post    Post object.
4060           */
4061          do_action( 'edit_post', $post_ID, $post );
4062  
4063          $post_after = get_post( $post_ID );
4064  
4065          /**
4066           * Fires once an existing post has been updated.
4067           *
4068           * @since 3.0.0
4069           *
4070           * @param int     $post_ID      Post ID.
4071           * @param WP_Post $post_after   Post object following the update.
4072           * @param WP_Post $post_before  Post object before the update.
4073           */
4074          do_action( 'post_updated', $post_ID, $post_after, $post_before );
4075      }
4076  
4077      /**
4078       * Fires once a post has been saved.
4079       *
4080       * The dynamic portion of the hook name, `$post->post_type`, refers to
4081       * the post type slug.
4082       *
4083       * @since 3.7.0
4084       *
4085       * @param int     $post_ID Post ID.
4086       * @param WP_Post $post    Post object.
4087       * @param bool    $update  Whether this is an existing post being updated or not.
4088       */
4089      do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
4090  
4091      /**
4092       * Fires once a post has been saved.
4093       *
4094       * @since 1.5.0
4095       *
4096       * @param int     $post_ID Post ID.
4097       * @param WP_Post $post    Post object.
4098       * @param bool    $update  Whether this is an existing post being updated or not.
4099       */
4100      do_action( 'save_post', $post_ID, $post, $update );
4101  
4102      /**
4103       * Fires once a post has been saved.
4104       *
4105       * @since 2.0.0
4106       *
4107       * @param int     $post_ID Post ID.
4108       * @param WP_Post $post    Post object.
4109       * @param bool    $update  Whether this is an existing post being updated or not.
4110       */
4111      do_action( 'wp_insert_post', $post_ID, $post, $update );
4112  
4113      return $post_ID;
4114  }
4115  
4116  /**
4117   * Update a post with new post data.
4118   *
4119   * The date does not have to be set for drafts. You can set the date and it will
4120   * not be overridden.
4121   *
4122   * @since 1.0.0
4123   *
4124   * @param array|object $postarr  Optional. Post data. Arrays are expected to be escaped,
4125   *                               objects are not. Default array.
4126   * @param bool         $wp_error Optional. Allow return of WP_Error on failure. Default false.
4127   * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
4128   */
4129  function wp_update_post( $postarr = array(), $wp_error = false ) {
4130      if ( is_object( $postarr ) ) {
4131          // Non-escaped post was passed.
4132          $postarr = get_object_vars( $postarr );
4133          $postarr = wp_slash( $postarr );
4134      }
4135  
4136      // First, get all of the original fields.
4137      $post = get_post( $postarr['ID'], ARRAY_A );
4138  
4139      if ( is_null( $post ) ) {
4140          if ( $wp_error ) {
4141              return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
4142          }
4143          return 0;
4144      }
4145  
4146      // Escape data pulled from DB.
4147      $post = wp_slash( $post );
4148  
4149      // Passed post category list overwrites existing category list if not empty.
4150      if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
4151              && 0 != count( $postarr['post_category'] ) ) {
4152          $post_cats = $postarr['post_category'];
4153      } else {
4154          $post_cats = $post['post_category'];
4155      }
4156  
4157      // Drafts shouldn't be assigned a date unless explicitly done so by the user.
4158      if ( isset( $post['post_status'] ) && in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) && empty( $postarr['edit_date'] ) &&
4159              ( '0000-00-00 00:00:00' == $post['post_date_gmt'] ) ) {
4160          $clear_date = true;
4161      } else {
4162          $clear_date = false;
4163      }
4164  
4165      // Merge old and new fields with new fields overwriting old ones.
4166      $postarr                  = array_merge( $post, $postarr );
4167      $postarr['post_category'] = $post_cats;
4168      if ( $clear_date ) {
4169          $postarr['post_date']     = current_time( 'mysql' );
4170          $postarr['post_date_gmt'] = '';
4171      }
4172  
4173      if ( $postarr['post_type'] == 'attachment' ) {
4174          return wp_insert_attachment( $postarr, false, 0, $wp_error );
4175      }
4176  
4177      return wp_insert_post( $postarr, $wp_error );
4178  }
4179  
4180  /**
4181   * Publish a post by transitioning the post status.
4182   *
4183   * @since 2.1.0
4184   *
4185   * @global wpdb $wpdb WordPress database abstraction object.
4186   *
4187   * @param int|WP_Post $post Post ID or post object.
4188   */
4189  function wp_publish_post( $post ) {
4190      global $wpdb;
4191  
4192      $post = get_post( $post );
4193      if ( ! $post ) {
4194          return;
4195      }
4196  
4197      if ( 'publish' == $post->post_status ) {
4198          return;
4199      }
4200  
4201      $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
4202  
4203      clean_post_cache( $post->ID );
4204  
4205      $old_status        = $post->post_status;
4206      $post->post_status = 'publish';
4207      wp_transition_post_status( 'publish', $old_status, $post );
4208  
4209      /** This action is documented in wp-includes/post.php */
4210      do_action( "edit_post_{$post->post_type}", $post->ID, $post );
4211  
4212      /** This action is documented in wp-includes/post.php */
4213      do_action( 'edit_post', $post->ID, $post );
4214  
4215      /** This action is documented in wp-includes/post.php */
4216      do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
4217  
4218      /** This action is documented in wp-includes/post.php */
4219      do_action( 'save_post', $post->ID, $post, true );
4220  
4221      /** This action is documented in wp-includes/post.php */
4222      do_action( 'wp_insert_post', $post->ID, $post, true );
4223  }
4224  
4225  /**
4226   * Publish future post and make sure post ID has future post status.
4227   *
4228   * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
4229   * from publishing drafts, etc.
4230   *
4231   * @since 2.5.0
4232   *
4233   * @param int|WP_Post $post_id Post ID or post object.
4234   */
4235  function check_and_publish_future_post( $post_id ) {
4236      $post = get_post( $post_id );
4237  
4238      if ( empty( $post ) ) {
4239          return;
4240      }
4241  
4242      if ( 'future' != $post->post_status ) {
4243          return;
4244      }
4245  
4246      $time = strtotime( $post->post_date_gmt . ' GMT' );
4247  
4248      // Uh oh, someone jumped the gun!
4249      if ( $time > time() ) {
4250          wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
4251          wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
4252          return;
4253      }
4254  
4255      // wp_publish_post() returns no meaningful value.
4256      wp_publish_post( $post_id );
4257  }
4258  
4259  /**
4260   * Computes a unique slug for the post, when given the desired slug and some post details.
4261   *
4262   * @since 2.8.0
4263   *
4264   * @global wpdb       $wpdb       WordPress database abstraction object.
4265   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
4266   *
4267   * @param string $slug        The desired slug (post_name).
4268   * @param int    $post_ID     Post ID.
4269   * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
4270   * @param string $post_type   Post type.
4271   * @param int    $post_parent Post parent ID.
4272   * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
4273   */
4274  function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
4275      if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) || 'user_request' === $post_type ) {
4276          return $slug;
4277      }
4278  
4279      /**
4280       * Filters the post slug before it is generated to be unique.
4281       *
4282       * Returning a non-null value will short-circuit the
4283       * unique slug generation, returning the passed value instead.
4284       *
4285       * @since 5.1.0
4286       *
4287       * @param string|null $override_slug Short-circuit return value.
4288       * @param string      $slug          The desired slug (post_name).
4289       * @param int         $post_ID       Post ID.
4290       * @param string      $post_status   The post status.
4291       * @param string      $post_type     Post type.
4292       * @param int         $post_parent   Post parent ID.
4293       */
4294      $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
4295      if ( null !== $override_slug ) {
4296          return $override_slug;
4297      }
4298  
4299      global $wpdb, $wp_rewrite;
4300  
4301      $original_slug = $slug;
4302  
4303      $feeds = $wp_rewrite->feeds;
4304      if ( ! is_array( $feeds ) ) {
4305          $feeds = array();
4306      }
4307  
4308      if ( 'attachment' == $post_type ) {
4309          // Attachment slugs must be unique across all types.
4310          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
4311          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
4312  
4313          /**
4314           * Filters whether the post slug would make a bad attachment slug.
4315           *
4316           * @since 3.1.0
4317           *
4318           * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
4319           * @param string $slug     The post slug.
4320           */
4321          if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
4322              $suffix = 2;
4323              do {
4324                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4325                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
4326                  $suffix++;
4327              } while ( $post_name_check );
4328              $slug = $alt_post_name;
4329          }
4330      } elseif ( is_post_type_hierarchical( $post_type ) ) {
4331          if ( 'nav_menu_item' == $post_type ) {
4332              return $slug;
4333          }
4334  
4335          /*
4336           * Page slugs must be unique within their own trees. Pages are in a separate
4337           * namespace than posts so page slugs are allowed to overlap post slugs.
4338           */
4339          $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";
4340          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
4341  
4342          /**
4343           * Filters whether the post slug would make a bad hierarchical post slug.
4344           *
4345           * @since 3.1.0
4346           *
4347           * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
4348           * @param string $slug        The post slug.
4349           * @param string $post_type   Post type.
4350           * @param int    $post_parent Post parent ID.
4351           */
4352          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 ) ) {
4353              $suffix = 2;
4354              do {
4355                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4356                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
4357                  $suffix++;
4358              } while ( $post_name_check );
4359              $slug = $alt_post_name;
4360          }
4361      } else {
4362          // Post slugs must be unique across all posts.
4363          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
4364          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
4365  
4366          // Prevent new post slugs that could result in URLs that conflict with date archives.
4367          $post                        = get_post( $post_ID );
4368          $conflicts_with_date_archive = false;
4369          if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
4370              $slug_num = intval( $slug );
4371  
4372              if ( $slug_num ) {
4373                  $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
4374                  $postname_index = array_search( '%postname%', $permastructs );
4375  
4376                  /*
4377                  * Potential date clashes are as follows:
4378                  *
4379                  * - Any integer in the first permastruct position could be a year.
4380                  * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
4381                  * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
4382                  */
4383                  if ( 0 === $postname_index ||
4384                      ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
4385                      ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
4386                  ) {
4387                      $conflicts_with_date_archive = true;
4388                  }
4389              }
4390          }
4391  
4392          /**
4393           * Filters whether the post slug would be bad as a flat slug.
4394           *
4395           * @since 3.1.0
4396           *
4397           * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
4398           * @param string $slug      The post slug.
4399           * @param string $post_type Post type.
4400           */
4401          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 ) ) {
4402              $suffix = 2;
4403              do {
4404                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4405                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
4406                  $suffix++;
4407              } while ( $post_name_check );
4408              $slug = $alt_post_name;
4409          }
4410      }
4411  
4412      /**
4413       * Filters the unique post slug.
4414       *
4415       * @since 3.3.0
4416       *
4417       * @param string $slug          The post slug.
4418       * @param int    $post_ID       Post ID.
4419       * @param string $post_status   The post status.
4420       * @param string $post_type     Post type.
4421       * @param int    $post_parent   Post parent ID
4422       * @param string $original_slug The original post slug.
4423       */
4424      return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
4425  }
4426  
4427  /**
4428   * Truncate a post slug.
4429   *
4430   * @since 3.6.0
4431   * @access private
4432   *
4433   * @see utf8_uri_encode()
4434   *
4435   * @param string $slug   The slug to truncate.
4436   * @param int    $length Optional. Max length of the slug. Default 200 (characters).
4437   * @return string The truncated slug.
4438   */
4439  function _truncate_post_slug( $slug, $length = 200 ) {
4440      if ( strlen( $slug ) > $length ) {
4441          $decoded_slug = urldecode( $slug );
4442          if ( $decoded_slug === $slug ) {
4443              $slug = substr( $slug, 0, $length );
4444          } else {
4445              $slug = utf8_uri_encode( $decoded_slug, $length );
4446          }
4447      }
4448  
4449      return rtrim( $slug, '-' );
4450  }
4451  
4452  /**
4453   * Add tags to a post.
4454   *
4455   * @see wp_set_post_tags()
4456   *
4457   * @since 2.3.0
4458   *
4459   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4460   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4461   *                              separated by commas. Default empty.
4462   * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
4463   */
4464  function wp_add_post_tags( $post_id = 0, $tags = '' ) {
4465      return wp_set_post_tags( $post_id, $tags, true );
4466  }
4467  
4468  /**
4469   * Set the tags for a post.
4470   *
4471   * @since 2.3.0
4472   *
4473   * @see wp_set_object_terms()
4474   *
4475   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4476   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4477   *                              separated by commas. Default empty.
4478   * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
4479   *                              replace the tags with the new tags. Default false.
4480   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4481   */
4482  function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
4483      return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
4484  }
4485  
4486  /**
4487   * Set the terms for a post.
4488   *
4489   * @since 2.8.0
4490   *
4491   * @see wp_set_object_terms()
4492   *
4493   * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
4494   * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
4495   *                               separated by commas. Hierarchical taxonomies must always pass IDs rather
4496   *                               than names so that children with the same names but different parents
4497   *                               aren't confused. Default empty.
4498   * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
4499   * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
4500   *                               replace the terms with the new terms. Default false.
4501   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4502   */
4503  function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
4504      $post_id = (int) $post_id;
4505  
4506      if ( ! $post_id ) {
4507          return false;
4508      }
4509  
4510      if ( empty( $tags ) ) {
4511          $tags = array();
4512      }
4513  
4514      if ( ! is_array( $tags ) ) {
4515          $comma = _x( ',', 'tag delimiter' );
4516          if ( ',' !== $comma ) {
4517              $tags = str_replace( $comma, ',', $tags );
4518          }
4519          $tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
4520      }
4521  
4522      /*
4523       * Hierarchical taxonomies must always pass IDs rather than names so that
4524       * children with the same names but different parents aren't confused.
4525       */
4526      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
4527          $tags = array_unique( array_map( 'intval', $tags ) );
4528      }
4529  
4530      return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
4531  }
4532  
4533  /**
4534   * Set categories for a post.
4535   *
4536   * If the post categories parameter is not set, then the default category is
4537   * going used.
4538   *
4539   * @since 2.1.0
4540   *
4541   * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
4542   *                                   of the global $post. Default 0.
4543   * @param array|int $post_categories Optional. List of category IDs, or the ID of a single category.
4544   *                                   Default empty array.
4545   * @param bool      $append          If true, don't delete existing categories, just add on.
4546   *                                   If false, replace the categories with the new categories.
4547   * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
4548   */
4549  function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
4550      $post_ID     = (int) $post_ID;
4551      $post_type   = get_post_type( $post_ID );
4552      $post_status = get_post_status( $post_ID );
4553      // If $post_categories isn't already an array, make it one:
4554      $post_categories = (array) $post_categories;
4555      if ( empty( $post_categories ) ) {
4556          if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
4557              $post_categories = array( get_option( 'default_category' ) );
4558              $append          = false;
4559          } else {
4560              $post_categories = array();
4561          }
4562      } elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
4563          return true;
4564      }
4565  
4566      return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
4567  }
4568  
4569  /**
4570   * Fires actions related to the transitioning of a post's status.
4571   *
4572   * When a post is saved, the post status is "transitioned" from one status to another,
4573   * though this does not always mean the status has actually changed before and after
4574   * the save. This function fires a number of action hooks related to that transition:
4575   * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
4576   * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
4577   * that the function does not transition the post object in the database.
4578   *
4579   * For instance: When publishing a post for the first time, the post status may transition
4580   * from 'draft' – or some other status – to 'publish'. However, if a post is already
4581   * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
4582   * before and after the transition.
4583   *
4584   * @since 2.3.0
4585   *
4586   * @param string  $new_status Transition to this post status.
4587   * @param string  $old_status Previous post status.
4588   * @param WP_Post $post Post data.
4589   */
4590  function wp_transition_post_status( $new_status, $old_status, $post ) {
4591      /**
4592       * Fires when a post is transitioned from one status to another.
4593       *
4594       * @since 2.3.0
4595       *
4596       * @param string  $new_status New post status.
4597       * @param string  $old_status Old post status.
4598       * @param WP_Post $post       Post object.
4599       */
4600      do_action( 'transition_post_status', $new_status, $old_status, $post );
4601  
4602      /**
4603       * Fires when a post is transitioned from one status to another.
4604       *
4605       * The dynamic portions of the hook name, `$new_status` and `$old status`,
4606       * refer to the old and new post statuses, respectively.
4607       *
4608       * @since 2.3.0
4609       *
4610       * @param WP_Post $post Post object.
4611       */
4612      do_action( "{$old_status}_to_{$new_status}", $post );
4613  
4614      /**
4615       * Fires when a post is transitioned from one status to another.
4616       *
4617       * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
4618       * refer to the new post status and post type, respectively.
4619       *
4620       * Please note: When this action is hooked using a particular post status (like
4621       * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
4622       * first transitioned to that status from something else, as well as upon
4623       * subsequent post updates (old and new status are both the same).
4624       *
4625       * Therefore, if you are looking to only fire a callback when a post is first
4626       * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
4627       *
4628       * @since 2.3.0
4629       *
4630       * @param int     $post_id Post ID.
4631       * @param WP_Post $post    Post object.
4632       */
4633      do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
4634  }
4635  
4636  //
4637  // Comment, trackback, and pingback functions.
4638  //
4639  
4640  /**
4641   * Add a URL to those already pinged.
4642   *
4643   * @since 1.5.0
4644   * @since 4.7.0 `$post_id` can be a WP_Post object.
4645   * @since 4.7.0 `$uri` can be an array of URIs.
4646   *
4647   * @global wpdb $wpdb WordPress database abstraction object.
4648   *
4649   * @param int|WP_Post  $post_id Post object or ID.
4650   * @param string|array $uri     Ping URI or array of URIs.
4651   * @return int|false How many rows were updated.
4652   */
4653  function add_ping( $post_id, $uri ) {
4654      global $wpdb;
4655  
4656      $post = get_post( $post_id );
4657      if ( ! $post ) {
4658          return false;
4659      }
4660  
4661      $pung = trim( $post->pinged );
4662      $pung = preg_split( '/\s/', $pung );
4663  
4664      if ( is_array( $uri ) ) {
4665          $pung = array_merge( $pung, $uri );
4666      } else {
4667          $pung[] = $uri;
4668      }
4669      $new = implode( "\n", $pung );
4670  
4671      /**
4672       * Filters the new ping URL to add for the given post.
4673       *
4674       * @since 2.0.0
4675       *
4676       * @param string $new New ping URL to add.
4677       */
4678      $new = apply_filters( 'add_ping', $new );
4679  
4680      $return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
4681      clean_post_cache( $post->ID );
4682      return $return;
4683  }
4684  
4685  /**
4686   * Retrieve enclosures already enclosed for a post.
4687   *
4688   * @since 1.5.0
4689   *
4690   * @param int $post_id Post ID.
4691   * @return array List of enclosures.
4692   */
4693  function get_enclosed( $post_id ) {
4694      $custom_fields = get_post_custom( $post_id );
4695      $pung          = array();
4696      if ( ! is_array( $custom_fields ) ) {
4697          return $pung;
4698      }
4699  
4700      foreach ( $custom_fields as $key => $val ) {
4701          if ( 'enclosure' != $key || ! is_array( $val ) ) {
4702              continue;
4703          }
4704          foreach ( $val as $enc ) {
4705              $enclosure = explode( "\n", $enc );
4706              $pung[]    = trim( $enclosure[0] );
4707          }
4708      }
4709  
4710      /**
4711       * Filters the list of enclosures already enclosed for the given post.
4712       *
4713       * @since 2.0.0
4714       *
4715       * @param array $pung    Array of enclosures for the given post.
4716       * @param int   $post_id Post ID.
4717       */
4718      return apply_filters( 'get_enclosed', $pung, $post_id );
4719  }
4720  
4721  /**
4722   * Retrieve URLs already pinged for a post.
4723   *
4724   * @since 1.5.0
4725   *
4726   * @since 4.7.0 `$post_id` can be a WP_Post object.
4727   *
4728   * @param int|WP_Post $post_id Post ID or object.
4729   * @return bool|string[] Array of URLs already pinged for the given post, false if the post is not found.
4730   */
4731  function get_pung( $post_id ) {
4732      $post = get_post( $post_id );
4733      if ( ! $post ) {
4734          return false;
4735      }
4736  
4737      $pung = trim( $post->pinged );
4738      $pung = preg_split( '/\s/', $pung );
4739  
4740      /**
4741       * Filters the list of already-pinged URLs for the given post.
4742       *
4743       * @since 2.0.0
4744       *
4745       * @param string[] $pung Array of URLs already pinged for the given post.
4746       */
4747      return apply_filters( 'get_pung', $pung );
4748  }
4749  
4750  /**
4751   * Retrieve URLs that need to be pinged.
4752   *
4753   * @since 1.5.0
4754   * @since 4.7.0 `$post_id` can be a WP_Post object.
4755   *
4756   * @param int|WP_Post $post_id Post Object or ID
4757   * @return array
4758   */
4759  function get_to_ping( $post_id ) {
4760      $post = get_post( $post_id );
4761  
4762      if ( ! $post ) {
4763          return false;
4764      }
4765  
4766      $to_ping = sanitize_trackback_urls( $post->to_ping );
4767      $to_ping = preg_split( '/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY );
4768  
4769      /**
4770       * Filters the list of URLs yet to ping for the given post.
4771       *
4772       * @since 2.0.0
4773       *
4774       * @param array $to_ping List of URLs yet to ping.
4775       */
4776      return apply_filters( 'get_to_ping', $to_ping );
4777  }
4778  
4779  /**
4780   * Do trackbacks for a list of URLs.
4781   *
4782   * @since 1.0.0
4783   *
4784   * @param string $tb_list Comma separated list of URLs.
4785   * @param int    $post_id Post ID.
4786   */
4787  function trackback_url_list( $tb_list, $post_id ) {
4788      if ( ! empty( $tb_list ) ) {
4789          // Get post data.
4790          $postdata = get_post( $post_id, ARRAY_A );
4791  
4792          // Form an excerpt.
4793          $excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4794  
4795          if ( strlen( $excerpt ) > 255 ) {
4796              $excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
4797          }
4798  
4799          $trackback_urls = explode( ',', $tb_list );
4800          foreach ( (array) $trackback_urls as $tb_url ) {
4801              $tb_url = trim( $tb_url );
4802              trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
4803          }
4804      }
4805  }
4806  
4807  //
4808  // Page functions
4809  //
4810  
4811  /**
4812   * Get a list of page IDs.
4813   *
4814   * @since 2.0.0
4815   *
4816   * @global wpdb $wpdb WordPress database abstraction object.
4817   *
4818   * @return array List of page IDs.
4819   */
4820  function get_all_page_ids() {
4821      global $wpdb;
4822  
4823      $page_ids = wp_cache_get( 'all_page_ids', 'posts' );
4824      if ( ! is_array( $page_ids ) ) {
4825          $page_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'page'" );
4826          wp_cache_add( 'all_page_ids', $page_ids, 'posts' );
4827      }
4828  
4829      return $page_ids;
4830  }
4831  
4832  /**
4833   * Retrieves page data given a page ID or page object.
4834   *
4835   * Use get_post() instead of get_page().
4836   *
4837   * @since 1.5.1
4838   * @deprecated 3.5.0 Use get_post()
4839   *
4840   * @param mixed  $page   Page object or page ID. Passed by reference.
4841   * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4842   *                       a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4843   * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4844   *                       'edit', 'db', 'display'. Default 'raw'.
4845   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4846   */
4847  function get_page( $page, $output = OBJECT, $filter = 'raw' ) {
4848      return get_post( $page, $output, $filter );
4849  }
4850  
4851  /**
4852   * Retrieves a page given its path.
4853   *
4854   * @since 2.1.0
4855   *
4856   * @global wpdb $wpdb WordPress database abstraction object.
4857   *
4858   * @param string       $page_path Page path.
4859   * @param string       $output    Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4860   *                                a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4861   * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4862   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4863   */
4864  function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4865      global $wpdb;
4866  
4867      $last_changed = wp_cache_get_last_changed( 'posts' );
4868  
4869      $hash      = md5( $page_path . serialize( $post_type ) );
4870      $cache_key = "get_page_by_path:$hash:$last_changed";
4871      $cached    = wp_cache_get( $cache_key, 'posts' );
4872      if ( false !== $cached ) {
4873          // Special case: '0' is a bad `$page_path`.
4874          if ( '0' === $cached || 0 === $cached ) {
4875              return;
4876          } else {
4877              return get_post( $cached, $output );
4878          }
4879      }
4880  
4881      $page_path     = rawurlencode( urldecode( $page_path ) );
4882      $page_path     = str_replace( '%2F', '/', $page_path );
4883      $page_path     = str_replace( '%20', ' ', $page_path );
4884      $parts         = explode( '/', trim( $page_path, '/' ) );
4885      $parts         = array_map( 'sanitize_title_for_query', $parts );
4886      $escaped_parts = esc_sql( $parts );
4887  
4888      $in_string = "'" . implode( "','", $escaped_parts ) . "'";
4889  
4890      if ( is_array( $post_type ) ) {
4891          $post_types = $post_type;
4892      } else {
4893          $post_types = array( $post_type, 'attachment' );
4894      }
4895  
4896      $post_types          = esc_sql( $post_types );
4897      $post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4898      $sql                 = "
4899          SELECT ID, post_name, post_parent, post_type
4900          FROM $wpdb->posts
4901          WHERE post_name IN ($in_string)
4902          AND post_type IN ($post_type_in_string)
4903      ";
4904  
4905      $pages = $wpdb->get_results( $sql, OBJECT_K );
4906  
4907      $revparts = array_reverse( $parts );
4908  
4909      $foundid = 0;
4910      foreach ( (array) $pages as $page ) {
4911          if ( $page->post_name == $revparts[0] ) {
4912              $count = 0;
4913              $p     = $page;
4914  
4915              /*
4916               * Loop through the given path parts from right to left,
4917               * ensuring each matches the post ancestry.
4918               */
4919              while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4920                  $count++;
4921                  $parent = $pages[ $p->post_parent ];
4922                  if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] ) {
4923                      break;
4924                  }
4925                  $p = $parent;
4926              }
4927  
4928              if ( $p->post_parent == 0 && $count + 1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4929                  $foundid = $page->ID;
4930                  if ( $page->post_type == $post_type ) {
4931                      break;
4932                  }
4933              }
4934          }
4935      }
4936  
4937      // We cache misses as well as hits.
4938      wp_cache_set( $cache_key, $foundid, 'posts' );
4939  
4940      if ( $foundid ) {
4941          return get_post( $foundid, $output );
4942      }
4943  }
4944  
4945  /**
4946   * Retrieve a page given its title.
4947   *
4948   * If more than one post uses the same title, the post with the smallest ID will be returned.
4949   * Be careful: in case of more than one post having the same title, it will check the oldest
4950   * publication date, not the smallest ID.
4951   *
4952   * Because this function uses the MySQL '=' comparison, $page_title will usually be matched
4953   * as case-insensitive with default collation.
4954   *
4955   * @since 2.1.0
4956   * @since 3.0.0 The `$post_type` parameter was added.
4957   *
4958   * @global wpdb $wpdb WordPress database abstraction object.
4959   *
4960   * @param string       $page_title Page title.
4961   * @param string       $output     Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4962   *                                 a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4963   * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
4964   * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4965   */
4966  function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4967      global $wpdb;
4968  
4969      if ( is_array( $post_type ) ) {
4970          $post_type           = esc_sql( $post_type );
4971          $post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4972          $sql                 = $wpdb->prepare(
4973              "
4974              SELECT ID
4975              FROM $wpdb->posts
4976              WHERE post_title = %s
4977              AND post_type IN ($post_type_in_string)
4978          ",
4979              $page_title
4980          );
4981      } else {
4982          $sql = $wpdb->prepare(
4983              "
4984              SELECT ID
4985              FROM $wpdb->posts
4986              WHERE post_title = %s
4987              AND post_type = %s
4988          ",
4989              $page_title,
4990              $post_type
4991          );
4992      }
4993  
4994      $page = $wpdb->get_var( $sql );
4995  
4996      if ( $page ) {
4997          return get_post( $page, $output );
4998      }
4999  }
5000  
5001  /**
5002   * Identify descendants of a given page ID in a list of page objects.
5003   *
5004   * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
5005   *
5006   * @since 1.5.1
5007   *
5008   * @param int   $page_id Page ID.
5009   * @param array $pages   List of page objects from which descendants should be identified.
5010   * @return array List of page children.
5011   */
5012  function get_page_children( $page_id, $pages ) {
5013      // Build a hash of ID -> children.
5014      $children = array();
5015      foreach ( (array) $pages as $page ) {
5016          $children[ intval( $page->post_parent ) ][] = $page;
5017      }
5018  
5019      $page_list = array();
5020  
5021      // Start the search by looking at immediate children.
5022      if ( isset( $children[ $page_id ] ) ) {
5023          // Always start at the end of the stack in order to preserve original `$pages` order.
5024          $to_look = array_reverse( $children[ $page_id ] );
5025  
5026          while ( $to_look ) {
5027              $p           = array_pop( $to_look );
5028              $page_list[] = $p;
5029              if ( isset( $children[ $p->ID ] ) ) {
5030                  foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
5031                      // Append to the `$to_look` stack to descend the tree.
5032                      $to_look[] = $child;
5033                  }
5034              }
5035          }
5036      }
5037  
5038      return $page_list;
5039  }
5040  
5041  /**
5042   * Order the pages with children under parents in a flat list.
5043   *
5044   * It uses auxiliary structure to hold parent-children relationships and
5045   * runs in O(N) complexity
5046   *
5047   * @since 2.0.0
5048   *
5049   * @param array $pages   Posts array (passed by reference).
5050   * @param int   $page_id Optional. Parent page ID. Default 0.
5051   * @return array A list arranged by hierarchy. Children immediately follow their parents.
5052   */
5053  function get_page_hierarchy( &$pages, $page_id = 0 ) {
5054      if ( empty( $pages ) ) {
5055          return array();
5056      }
5057  
5058      $children = array();
5059      foreach ( (array) $pages as $p ) {
5060          $parent_id                = intval( $p->post_parent );
5061          $children[ $parent_id ][] = $p;
5062      }
5063  
5064      $result = array();
5065      _page_traverse_name( $page_id, $children, $result );
5066  
5067      return $result;
5068  }
5069  
5070  /**
5071   * Traverse and return all the nested children post names of a root page.
5072   *
5073   * $children contains parent-children relations
5074   *
5075   * @since 2.9.0
5076   * @access private
5077   *
5078   * @see _page_traverse_name()
5079   *
5080   * @param int   $page_id   Page ID.
5081   * @param array $children  Parent-children relations (passed by reference).
5082   * @param array $result    Result (passed by reference).
5083   */
5084  function _page_traverse_name( $page_id, &$children, &$result ) {
5085      if ( isset( $children[ $page_id ] ) ) {
5086          foreach ( (array) $children[ $page_id ] as $child ) {
5087              $result[ $child->ID ] = $child->post_name;
5088              _page_traverse_name( $child->ID, $children, $result );
5089          }
5090      }
5091  }
5092  
5093  /**
5094   * Build the URI path for a page.
5095   *
5096   * Sub pages will be in the "directory" under the parent page post name.
5097   *
5098   * @since 1.5.0
5099   * @since 4.6.0 Converted the `$page` parameter to optional.
5100   *
5101   * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
5102   * @return string|false Page URI, false on error.
5103   */
5104  function get_page_uri( $page = 0 ) {
5105      if ( ! $page instanceof WP_Post ) {
5106          $page = get_post( $page );
5107      }
5108  
5109      if ( ! $page ) {
5110          return false;
5111      }
5112  
5113      $uri = $page->post_name;
5114  
5115      foreach ( $page->ancestors as $parent ) {
5116          $parent = get_post( $parent );
5117          if ( $parent && $parent->post_name ) {
5118              $uri = $parent->post_name . '/' . $uri;
5119          }
5120      }
5121  
5122      /**
5123       * Filters the URI for a page.
5124       *
5125       * @since 4.4.0
5126       *
5127       * @param string  $uri  Page URI.
5128       * @param WP_Post $page Page object.
5129       */
5130      return apply_filters( 'get_page_uri', $uri, $page );
5131  }
5132  
5133  /**
5134   * Retrieve a list of pages (or hierarchical post type items).
5135   *
5136   * @global wpdb $wpdb WordPress database abstraction object.
5137   *
5138   * @since 1.5.0
5139   *
5140   * @param array|string $args {
5141   *     Optional. Array or string of arguments to retrieve pages.
5142   *
5143   *     @type int          $child_of     Page ID to return child and grandchild pages of. Note: The value
5144   *                                      of `$hierarchical` has no bearing on whether `$child_of` returns
5145   *                                      hierarchical results. Default 0, or no restriction.
5146   *     @type string       $sort_order   How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
5147   *     @type string       $sort_column  What columns to sort pages by, comma-separated. Accepts 'post_author',
5148   *                                      'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
5149   *                                      'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
5150   *                                      'post_' can be omitted for any values that start with it.
5151   *                                      Default 'post_title'.
5152   *     @type bool         $hierarchical Whether to return pages hierarchically. If false in conjunction with
5153   *                                      `$child_of` also being false, both arguments will be disregarded.
5154   *                                      Default true.
5155   *     @type array        $exclude      Array of page IDs to exclude. Default empty array.
5156   *     @type array        $include      Array of page IDs to include. Cannot be used with `$child_of`,
5157   *                                      `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
5158   *                                      Default empty array.
5159   *     @type string       $meta_key     Only include pages with this meta key. Default empty.
5160   *     @type string       $meta_value   Only include pages with this meta value. Requires `$meta_key`.
5161   *                                      Default empty.
5162   *     @type string       $authors      A comma-separated list of author IDs. Default empty.
5163   *     @type int          $parent       Page ID to return direct children of. Default -1, or no restriction.
5164   *     @type string|array $exclude_tree Comma-separated string or array of page IDs to exclude.
5165   *                                      Default empty array.
5166   *     @type int          $number       The number of pages to return. Defau