[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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