[ 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 stdClass[] $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 stdClass[] $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 stdClass|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 stdClass[] $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 stdClass 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 stdClass $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 stdClass 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 stdClass        $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              _x( 'Audio', 'file type group' ),
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              _x( 'Video', 'file type group' ),
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             An array of taxonomy terms keyed by their taxonomy name.
3827   *                                         If the taxonomy is hierarchical, the term list needs to be
3828   *                                         either an array of term IDs or a comma-separated string of IDs.
3829   *                                         If the taxonomy is non-hierarchical, the term list can be an array
3830   *                                         that contains term names or slugs, or a comma-separated string
3831   *                                         of names or slugs. This is because, in hierarchical taxonomy,
3832   *                                         child terms can have the same names with different parent terms,
3833   *                                         so the only way to connect them is using ID. Default empty.
3834   *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
3835   * }
3836   * @param bool  $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
3837   * @param bool  $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
3838   * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
3839   */
3840  function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) {
3841      global $wpdb;
3842  
3843      // Capture original pre-sanitized array for passing into filters.
3844      $unsanitized_postarr = $postarr;
3845  
3846      $user_id = get_current_user_id();
3847  
3848      $defaults = array(
3849          'post_author'           => $user_id,
3850          'post_content'          => '',
3851          'post_content_filtered' => '',
3852          'post_title'            => '',
3853          'post_excerpt'          => '',
3854          'post_status'           => 'draft',
3855          'post_type'             => 'post',
3856          'comment_status'        => '',
3857          'ping_status'           => '',
3858          'post_password'         => '',
3859          'to_ping'               => '',
3860          'pinged'                => '',
3861          'post_parent'           => 0,
3862          'menu_order'            => 0,
3863          'guid'                  => '',
3864          'import_id'             => 0,
3865          'context'               => '',
3866          'post_date'             => '',
3867          'post_date_gmt'         => '',
3868      );
3869  
3870      $postarr = wp_parse_args( $postarr, $defaults );
3871  
3872      unset( $postarr['filter'] );
3873  
3874      $postarr = sanitize_post( $postarr, 'db' );
3875  
3876      // Are we updating or creating?
3877      $post_ID = 0;
3878      $update  = false;
3879      $guid    = $postarr['guid'];
3880  
3881      if ( ! empty( $postarr['ID'] ) ) {
3882          $update = true;
3883  
3884          // Get the post ID and GUID.
3885          $post_ID     = $postarr['ID'];
3886          $post_before = get_post( $post_ID );
3887  
3888          if ( is_null( $post_before ) ) {
3889              if ( $wp_error ) {
3890                  return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3891              }
3892              return 0;
3893          }
3894  
3895          $guid            = get_post_field( 'guid', $post_ID );
3896          $previous_status = get_post_field( 'post_status', $post_ID );
3897      } else {
3898          $previous_status = 'new';
3899          $post_before     = null;
3900      }
3901  
3902      $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3903  
3904      $post_title   = $postarr['post_title'];
3905      $post_content = $postarr['post_content'];
3906      $post_excerpt = $postarr['post_excerpt'];
3907  
3908      if ( isset( $postarr['post_name'] ) ) {
3909          $post_name = $postarr['post_name'];
3910      } elseif ( $update ) {
3911          // For an update, don't modify the post_name if it wasn't supplied as an argument.
3912          $post_name = $post_before->post_name;
3913      }
3914  
3915      $maybe_empty = 'attachment' !== $post_type
3916          && ! $post_content && ! $post_title && ! $post_excerpt
3917          && post_type_supports( $post_type, 'editor' )
3918          && post_type_supports( $post_type, 'title' )
3919          && post_type_supports( $post_type, 'excerpt' );
3920  
3921      /**
3922       * Filters whether the post should be considered "empty".
3923       *
3924       * The post is considered "empty" if both:
3925       * 1. The post type supports the title, editor, and excerpt fields
3926       * 2. The title, editor, and excerpt fields are all empty
3927       *
3928       * Returning a truthy value from the filter will effectively short-circuit
3929       * the new post being inserted and return 0. If $wp_error is true, a WP_Error
3930       * will be returned instead.
3931       *
3932       * @since 3.3.0
3933       *
3934       * @param bool  $maybe_empty Whether the post should be considered "empty".
3935       * @param array $postarr     Array of post data.
3936       */
3937      if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3938          if ( $wp_error ) {
3939              return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3940          } else {
3941              return 0;
3942          }
3943      }
3944  
3945      $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3946  
3947      if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3948          $post_status = 'inherit';
3949      }
3950  
3951      if ( ! empty( $postarr['post_category'] ) ) {
3952          // Filter out empty terms.
3953          $post_category = array_filter( $postarr['post_category'] );
3954      }
3955  
3956      // Make sure we set a valid category.
3957      if ( empty( $post_category ) || 0 === count( $post_category ) || ! is_array( $post_category ) ) {
3958          // 'post' requires at least one category.
3959          if ( 'post' === $post_type && 'auto-draft' !== $post_status ) {
3960              $post_category = array( get_option( 'default_category' ) );
3961          } else {
3962              $post_category = array();
3963          }
3964      }
3965  
3966      /*
3967       * Don't allow contributors to set the post slug for pending review posts.
3968       *
3969       * For new posts check the primitive capability, for updates check the meta capability.
3970       */
3971      $post_type_object = get_post_type_object( $post_type );
3972  
3973      if ( ! $update && 'pending' === $post_status && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
3974          $post_name = '';
3975      } elseif ( $update && 'pending' === $post_status && ! current_user_can( 'publish_post', $post_ID ) ) {
3976          $post_name = '';
3977      }
3978  
3979      /*
3980       * Create a valid post name. Drafts and pending posts are allowed to have
3981       * an empty post name.
3982       */
3983      if ( empty( $post_name ) ) {
3984          if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
3985              $post_name = sanitize_title( $post_title );
3986          } else {
3987              $post_name = '';
3988          }
3989      } else {
3990          // On updates, we need to check to see if it's using the old, fixed sanitization context.
3991          $check_name = sanitize_title( $post_name, '', 'old-save' );
3992  
3993          if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3994              $post_name = $check_name;
3995          } else { // new post, or slug has changed.
3996              $post_name = sanitize_title( $post_name );
3997          }
3998      }
3999  
4000      /*
4001       * Resolve the post date from any provided post date or post date GMT strings;
4002       * if none are provided, the date will be set to now.
4003       */
4004      $post_date = wp_resolve_post_date( $postarr['post_date'], $postarr['post_date_gmt'] );
4005      if ( ! $post_date ) {
4006          if ( $wp_error ) {
4007              return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
4008          } else {
4009              return 0;
4010          }
4011      }
4012  
4013      if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' === $postarr['post_date_gmt'] ) {
4014          if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
4015              $post_date_gmt = get_gmt_from_date( $post_date );
4016          } else {
4017              $post_date_gmt = '0000-00-00 00:00:00';
4018          }
4019      } else {
4020          $post_date_gmt = $postarr['post_date_gmt'];
4021      }
4022  
4023      if ( $update || '0000-00-00 00:00:00' === $post_date ) {
4024          $post_modified     = current_time( 'mysql' );
4025          $post_modified_gmt = current_time( 'mysql', 1 );
4026      } else {
4027          $post_modified     = $post_date;
4028          $post_modified_gmt = $post_date_gmt;
4029      }
4030  
4031      if ( 'attachment' !== $post_type ) {
4032          $now = gmdate( 'Y-m-d H:i:s' );
4033  
4034          if ( 'publish' === $post_status ) {
4035              if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
4036                  $post_status = 'future';
4037              }
4038          } elseif ( 'future' === $post_status ) {
4039              if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
4040                  $post_status = 'publish';
4041              }
4042          }
4043      }
4044  
4045      // Comment status.
4046      if ( empty( $postarr['comment_status'] ) ) {
4047          if ( $update ) {
4048              $comment_status = 'closed';
4049          } else {
4050              $comment_status = get_default_comment_status( $post_type );
4051          }
4052      } else {
4053          $comment_status = $postarr['comment_status'];
4054      }
4055  
4056      // These variables are needed by compact() later.
4057      $post_content_filtered = $postarr['post_content_filtered'];
4058      $post_author           = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
4059      $ping_status           = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
4060      $to_ping               = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
4061      $pinged                = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
4062      $import_id             = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
4063  
4064      /*
4065       * The 'wp_insert_post_parent' filter expects all variables to be present.
4066       * Previously, these variables would have already been extracted
4067       */
4068      if ( isset( $postarr['menu_order'] ) ) {
4069          $menu_order = (int) $postarr['menu_order'];
4070      } else {
4071          $menu_order = 0;
4072      }
4073  
4074      $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
4075      if ( 'private' === $post_status ) {
4076          $post_password = '';
4077      }
4078  
4079      if ( isset( $postarr['post_parent'] ) ) {
4080          $post_parent = (int) $postarr['post_parent'];
4081      } else {
4082          $post_parent = 0;
4083      }
4084  
4085      $new_postarr = array_merge(
4086          array(
4087              'ID' => $post_ID,
4088          ),
4089          compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
4090      );
4091  
4092      /**
4093       * Filters the post parent -- used to check for and prevent hierarchy loops.
4094       *
4095       * @since 3.1.0
4096       *
4097       * @param int   $post_parent Post parent ID.
4098       * @param int   $post_ID     Post ID.
4099       * @param array $new_postarr Array of parsed post data.
4100       * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
4101       */
4102      $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
4103  
4104      /*
4105       * If the post is being untrashed and it has a desired slug stored in post meta,
4106       * reassign it.
4107       */
4108      if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
4109          $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
4110  
4111          if ( $desired_post_slug ) {
4112              delete_post_meta( $post_ID, '_wp_desired_post_slug' );
4113              $post_name = $desired_post_slug;
4114          }
4115      }
4116  
4117      // If a trashed post has the desired slug, change it and let this post have it.
4118      if ( 'trash' !== $post_status && $post_name ) {
4119          /**
4120           * Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
4121           *
4122           * @since 5.4.0
4123           *
4124           * @param bool   $add_trashed_suffix Whether to attempt to add the suffix.
4125           * @param string $post_name          The name of the post being updated.
4126           * @param int    $post_ID            Post ID.
4127           */
4128          $add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_ID );
4129  
4130          if ( $add_trashed_suffix ) {
4131              wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
4132          }
4133      }
4134  
4135      // When trashing an existing post, change its slug to allow non-trashed posts to use it.
4136      if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
4137          $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
4138      }
4139  
4140      $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
4141  
4142      // Don't unslash.
4143      $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
4144  
4145      // Expected_slashed (everything!).
4146      $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' );
4147  
4148      $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
4149  
4150      foreach ( $emoji_fields as $emoji_field ) {
4151          if ( isset( $data[ $emoji_field ] ) ) {
4152              $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
4153  
4154              if ( 'utf8' === $charset ) {
4155                  $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
4156              }
4157          }
4158      }
4159  
4160      if ( 'attachment' === $post_type ) {
4161          /**
4162           * Filters attachment post data before it is updated in or added to the database.
4163           *
4164           * @since 3.9.0
4165           * @since 5.4.1 `$unsanitized_postarr` argument added.
4166           *
4167           * @param array $data                An array of slashed, sanitized, and processed attachment post data.
4168           * @param array $postarr             An array of slashed and sanitized attachment post data, but not processed.
4169           * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
4170           *                                   as originally passed to wp_insert_post().
4171           */
4172          $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr );
4173      } else {
4174          /**
4175           * Filters slashed post data just before it is inserted into the database.
4176           *
4177           * @since 2.7.0
4178           * @since 5.4.1 `$unsanitized_postarr` argument added.
4179           *
4180           * @param array $data                An array of slashed, sanitized, and processed post data.
4181           * @param array $postarr             An array of sanitized (and slashed) but otherwise unmodified post data.
4182           * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
4183           *                                   originally passed to wp_insert_post().
4184           */
4185          $data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr );
4186      }
4187  
4188      $data  = wp_unslash( $data );
4189      $where = array( 'ID' => $post_ID );
4190  
4191      if ( $update ) {
4192          /**
4193           * Fires immediately before an existing post is updated in the database.
4194           *
4195           * @since 2.5.0
4196           *
4197           * @param int   $post_ID Post ID.
4198           * @param array $data    Array of unslashed post data.
4199           */
4200          do_action( 'pre_post_update', $post_ID, $data );
4201  
4202          if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
4203              if ( $wp_error ) {
4204                  if ( 'attachment' === $post_type ) {
4205                      $message = __( 'Could not update attachment in the database.' );
4206                  } else {
4207                      $message = __( 'Could not update post in the database.' );
4208                  }
4209  
4210                  return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
4211              } else {
4212                  return 0;
4213              }
4214          }
4215      } else {
4216          // If there is a suggested ID, use it if not already present.
4217          if ( ! empty( $import_id ) ) {
4218              $import_id = (int) $import_id;
4219  
4220              if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
4221                  $data['ID'] = $import_id;
4222              }
4223          }
4224  
4225          if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
4226              if ( $wp_error ) {
4227                  if ( 'attachment' === $post_type ) {
4228                      $message = __( 'Could not insert attachment into the database.' );
4229                  } else {
4230                      $message = __( 'Could not insert post into the database.' );
4231                  }
4232  
4233                  return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
4234              } else {
4235                  return 0;
4236              }
4237          }
4238  
4239          $post_ID = (int) $wpdb->insert_id;
4240  
4241          // Use the newly generated $post_ID.
4242          $where = array( 'ID' => $post_ID );
4243      }
4244  
4245      if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
4246          $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
4247  
4248          $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
4249          clean_post_cache( $post_ID );
4250      }
4251  
4252      if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
4253          wp_set_post_categories( $post_ID, $post_category );
4254      }
4255  
4256      if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
4257          wp_set_post_tags( $post_ID, $postarr['tags_input'] );
4258      }
4259  
4260      // Add default term for all associated custom taxonomies.
4261      if ( 'auto-draft' !== $post_status ) {
4262          foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {
4263  
4264              if ( ! empty( $tax_object->default_term ) ) {
4265  
4266                  // Filter out empty terms.
4267                  if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
4268                      $postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
4269                  }
4270  
4271                  // Passed custom taxonomy list overwrites the existing list if not empty.
4272                  $terms = wp_get_object_terms( $post_ID, $taxonomy, array( 'fields' => 'ids' ) );
4273                  if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
4274                      $postarr['tax_input'][ $taxonomy ] = $terms;
4275                  }
4276  
4277                  if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
4278                      $default_term_id = get_option( 'default_term_' . $taxonomy );
4279                      if ( ! empty( $default_term_id ) ) {
4280                          $postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
4281                      }
4282                  }
4283              }
4284          }
4285      }
4286  
4287      // New-style support for all custom taxonomies.
4288      if ( ! empty( $postarr['tax_input'] ) ) {
4289          foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
4290              $taxonomy_obj = get_taxonomy( $taxonomy );
4291  
4292              if ( ! $taxonomy_obj ) {
4293                  /* translators: %s: Taxonomy name. */
4294                  _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
4295                  continue;
4296              }
4297  
4298              // array = hierarchical, string = non-hierarchical.
4299              if ( is_array( $tags ) ) {
4300                  $tags = array_filter( $tags );
4301              }
4302  
4303              if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
4304                  wp_set_post_terms( $post_ID, $tags, $taxonomy );
4305              }
4306          }
4307      }
4308  
4309      if ( ! empty( $postarr['meta_input'] ) ) {
4310          foreach ( $postarr['meta_input'] as $field => $value ) {
4311              update_post_meta( $post_ID, $field, $value );
4312          }
4313      }
4314  
4315      $current_guid = get_post_field( 'guid', $post_ID );
4316  
4317      // Set GUID.
4318      if ( ! $update && '' === $current_guid ) {
4319          $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
4320      }
4321  
4322      if ( 'attachment' === $postarr['post_type'] ) {
4323          if ( ! empty( $postarr['file'] ) ) {
4324              update_attached_file( $post_ID, $postarr['file'] );
4325          }
4326  
4327          if ( ! empty( $postarr['context'] ) ) {
4328              add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
4329          }
4330      }
4331  
4332      // Set or remove featured image.
4333      if ( isset( $postarr['_thumbnail_id'] ) ) {
4334          $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
4335  
4336          if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
4337              if ( wp_attachment_is( 'audio', $post_ID ) ) {
4338                  $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
4339              } elseif ( wp_attachment_is( 'video', $post_ID ) ) {
4340                  $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
4341              }
4342          }
4343  
4344          if ( $thumbnail_support ) {
4345              $thumbnail_id = (int) $postarr['_thumbnail_id'];
4346              if ( -1 === $thumbnail_id ) {
4347                  delete_post_thumbnail( $post_ID );
4348              } else {
4349                  set_post_thumbnail( $post_ID, $thumbnail_id );
4350              }
4351          }
4352      }
4353  
4354      clean_post_cache( $post_ID );
4355  
4356      $post = get_post( $post_ID );
4357  
4358      if ( ! empty( $postarr['page_template'] ) ) {
4359          $post->page_template = $postarr['page_template'];
4360          $page_templates      = wp_get_theme()->get_page_templates( $post );
4361  
4362          if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
4363              if ( $wp_error ) {
4364                  return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
4365              }
4366  
4367              update_post_meta( $post_ID, '_wp_page_template', 'default' );
4368          } else {
4369              update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
4370          }
4371      }
4372  
4373      if ( 'attachment' !== $postarr['post_type'] ) {
4374          wp_transition_post_status( $data['post_status'], $previous_status, $post );
4375      } else {
4376          if ( $update ) {
4377              /**
4378               * Fires once an existing attachment has been updated.
4379               *
4380               * @since 2.0.0
4381               *
4382               * @param int $post_ID Attachment ID.
4383               */
4384              do_action( 'edit_attachment', $post_ID );
4385  
4386              $post_after = get_post( $post_ID );
4387  
4388              /**
4389               * Fires once an existing attachment has been updated.
4390               *
4391               * @since 4.4.0
4392               *
4393               * @param int     $post_ID      Post ID.
4394               * @param WP_Post $post_after   Post object following the update.
4395               * @param WP_Post $post_before  Post object before the update.
4396               */
4397              do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
4398          } else {
4399  
4400              /**
4401               * Fires once an attachment has been added.
4402               *
4403               * @since 2.0.0
4404               *
4405               * @param int $post_ID Attachment ID.
4406               */
4407              do_action( 'add_attachment', $post_ID );
4408          }
4409  
4410          return $post_ID;
4411      }
4412  
4413      if ( $update ) {
4414          /**
4415           * Fires once an existing post has been updated.
4416           *
4417           * The dynamic portion of the hook name, `$post->post_type`, refers to
4418           * the post type slug.
4419           *
4420           * Possible hook names include:
4421           *
4422           *  - `edit_post_post`
4423           *  - `edit_post_page`
4424           *
4425           * @since 5.1.0
4426           *
4427           * @param int     $post_ID Post ID.
4428           * @param WP_Post $post    Post object.
4429           */
4430          do_action( "edit_post_{$post->post_type}", $post_ID, $post );
4431  
4432          /**
4433           * Fires once an existing post has been updated.
4434           *
4435           * @since 1.2.0
4436           *
4437           * @param int     $post_ID Post ID.
4438           * @param WP_Post $post    Post object.
4439           */
4440          do_action( 'edit_post', $post_ID, $post );
4441  
4442          $post_after = get_post( $post_ID );
4443  
4444          /**
4445           * Fires once an existing post has been updated.
4446           *
4447           * @since 3.0.0
4448           *
4449           * @param int     $post_ID      Post ID.
4450           * @param WP_Post $post_after   Post object following the update.
4451           * @param WP_Post $post_before  Post object before the update.
4452           */
4453          do_action( 'post_updated', $post_ID, $post_after, $post_before );
4454      }
4455  
4456      /**
4457       * Fires once a post has been saved.
4458       *
4459       * The dynamic portion of the hook name, `$post->post_type`, refers to
4460       * the post type slug.
4461       *
4462       * Possible hook names include:
4463       *
4464       *  - `save_post_post`
4465       *  - `save_post_page`
4466       *
4467       * @since 3.7.0
4468       *
4469       * @param int     $post_ID Post ID.
4470       * @param WP_Post $post    Post object.
4471       * @param bool    $update  Whether this is an existing post being updated.
4472       */
4473      do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
4474  
4475      /**
4476       * Fires once a post has been saved.
4477       *
4478       * @since 1.5.0
4479       *
4480       * @param int     $post_ID Post ID.
4481       * @param WP_Post $post    Post object.
4482       * @param bool    $update  Whether this is an existing post being updated.
4483       */
4484      do_action( 'save_post', $post_ID, $post, $update );
4485  
4486      /**
4487       * Fires once a post has been saved.
4488       *
4489       * @since 2.0.0
4490       *
4491       * @param int     $post_ID Post ID.
4492       * @param WP_Post $post    Post object.
4493       * @param bool    $update  Whether this is an existing post being updated.
4494       */
4495      do_action( 'wp_insert_post', $post_ID, $post, $update );
4496  
4497      if ( $fire_after_hooks ) {
4498          wp_after_insert_post( $post, $update, $post_before );
4499      }
4500  
4501      return $post_ID;
4502  }
4503  
4504  /**
4505   * Update a post with new post data.
4506   *
4507   * The date does not have to be set for drafts. You can set the date and it will
4508   * not be overridden.
4509   *
4510   * @since 1.0.0
4511   * @since 3.5.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
4512   * @since 5.6.0 Added the `$fire_after_hooks` parameter.
4513   *
4514   * @param array|object $postarr          Optional. Post data. Arrays are expected to be escaped,
4515   *                                       objects are not. See wp_insert_post() for accepted arguments.
4516   *                                       Default array.
4517   * @param bool         $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
4518   * @param bool         $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
4519   * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
4520   */
4521  function wp_update_post( $postarr = array(), $wp_error = false, $fire_after_hooks = true ) {
4522      if ( is_object( $postarr ) ) {
4523          // Non-escaped post was passed.
4524          $postarr = get_object_vars( $postarr );
4525          $postarr = wp_slash( $postarr );
4526      }
4527  
4528      // First, get all of the original fields.
4529      $post = get_post( $postarr['ID'], ARRAY_A );
4530  
4531      if ( is_null( $post ) ) {
4532          if ( $wp_error ) {
4533              return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
4534          }
4535          return 0;
4536      }
4537  
4538      // Escape data pulled from DB.
4539      $post = wp_slash( $post );
4540  
4541      // Passed post category list overwrites existing category list if not empty.
4542      if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
4543          && count( $postarr['post_category'] ) > 0
4544      ) {
4545          $post_cats = $postarr['post_category'];
4546      } else {
4547          $post_cats = $post['post_category'];
4548      }
4549  
4550      // Drafts shouldn't be assigned a date unless explicitly done so by the user.
4551      if ( isset( $post['post_status'] )
4552          && in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ), true )
4553          && empty( $postarr['edit_date'] ) && ( '0000-00-00 00:00:00' === $post['post_date_gmt'] )
4554      ) {
4555          $clear_date = true;
4556      } else {
4557          $clear_date = false;
4558      }
4559  
4560      // Merge old and new fields with new fields overwriting old ones.
4561      $postarr                  = array_merge( $post, $postarr );
4562      $postarr['post_category'] = $post_cats;
4563      if ( $clear_date ) {
4564          $postarr['post_date']     = current_time( 'mysql' );
4565          $postarr['post_date_gmt'] = '';
4566      }
4567  
4568      if ( 'attachment' === $postarr['post_type'] ) {
4569          return wp_insert_attachment( $postarr, false, 0, $wp_error );
4570      }
4571  
4572      // Discard 'tags_input' parameter if it's the same as existing post tags.
4573      if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $postarr['post_type'], 'post_tag' ) ) {
4574          $tags      = get_the_terms( $postarr['ID'], 'post_tag' );
4575          $tag_names = array();
4576  
4577          if ( $tags && ! is_wp_error( $tags ) ) {
4578              $tag_names = wp_list_pluck( $tags, 'name' );
4579          }
4580  
4581          if ( $postarr['tags_input'] === $tag_names ) {
4582              unset( $postarr['tags_input'] );
4583          }
4584      }
4585  
4586      return wp_insert_post( $postarr, $wp_error, $fire_after_hooks );
4587  }
4588  
4589  /**
4590   * Publish a post by transitioning the post status.
4591   *
4592   * @since 2.1.0
4593   *
4594   * @global wpdb $wpdb WordPress database abstraction object.
4595   *
4596   * @param int|WP_Post $post Post ID or post object.
4597   */
4598  function wp_publish_post( $post ) {
4599      global $wpdb;
4600  
4601      $post = get_post( $post );
4602  
4603      if ( ! $post ) {
4604          return;
4605      }
4606  
4607      if ( 'publish' === $post->post_status ) {
4608          return;
4609      }
4610  
4611      $post_before = get_post( $post->ID );
4612  
4613      // Ensure at least one term is applied for taxonomies with a default term.
4614      foreach ( get_object_taxonomies( $post->post_type, 'object' ) as $taxonomy => $tax_object ) {
4615          // Skip taxonomy if no default term is set.
4616          if (
4617              'category' !== $taxonomy &&
4618              empty( $tax_object->default_term )
4619          ) {
4620              continue;
4621          }
4622  
4623          // Do not modify previously set terms.
4624          if ( ! empty( get_the_terms( $post, $taxonomy ) ) ) {
4625              continue;
4626          }
4627  
4628          if ( 'category' === $taxonomy ) {
4629              $default_term_id = (int) get_option( 'default_category', 0 );
4630          } else {
4631              $default_term_id = (int) get_option( 'default_term_' . $taxonomy, 0 );
4632          }
4633  
4634          if ( ! $default_term_id ) {
4635              continue;
4636          }
4637          wp_set_post_terms( $post->ID, array( $default_term_id ), $taxonomy );
4638      }
4639  
4640      $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
4641  
4642      clean_post_cache( $post->ID );
4643  
4644      $old_status        = $post->post_status;
4645      $post->post_status = 'publish';
4646      wp_transition_post_status( 'publish', $old_status, $post );
4647  
4648      /** This action is documented in wp-includes/post.php */
4649      do_action( "edit_post_{$post->post_type}", $post->ID, $post );
4650  
4651      /** This action is documented in wp-includes/post.php */
4652      do_action( 'edit_post', $post->ID, $post );
4653  
4654      /** This action is documented in wp-includes/post.php */
4655      do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
4656  
4657      /** This action is documented in wp-includes/post.php */
4658      do_action( 'save_post', $post->ID, $post, true );
4659  
4660      /** This action is documented in wp-includes/post.php */
4661      do_action( 'wp_insert_post', $post->ID, $post, true );
4662  
4663      wp_after_insert_post( $post, true, $post_before );
4664  }
4665  
4666  /**
4667   * Publish future post and make sure post ID has future post status.
4668   *
4669   * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
4670   * from publishing drafts, etc.
4671   *
4672   * @since 2.5.0
4673   *
4674   * @param int|WP_Post $post_id Post ID or post object.
4675   */
4676  function check_and_publish_future_post( $post_id ) {
4677      $post = get_post( $post_id );
4678  
4679      if ( ! $post ) {
4680          return;
4681      }
4682  
4683      if ( 'future' !== $post->post_status ) {
4684          return;
4685      }
4686  
4687      $time = strtotime( $post->post_date_gmt . ' GMT' );
4688  
4689      // Uh oh, someone jumped the gun!
4690      if ( $time > time() ) {
4691          wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // Clear anything else in the system.
4692          wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
4693          return;
4694      }
4695  
4696      // wp_publish_post() returns no meaningful value.
4697      wp_publish_post( $post_id );
4698  }
4699  
4700  /**
4701   * Uses wp_checkdate to return a valid Gregorian-calendar value for post_date.
4702   * If post_date is not provided, this first checks post_date_gmt if provided,
4703   * then falls back to use the current time.
4704   *
4705   * For back-compat purposes in wp_insert_post, an empty post_date and an invalid
4706   * post_date_gmt will continue to return '1970-01-01 00:00:00' rather than false.
4707   *
4708   * @since 5.7.0
4709   *
4710   * @param string $post_date     The date in mysql format.
4711   * @param string $post_date_gmt The GMT date in mysql format.
4712   * @return string|false A valid Gregorian-calendar date string, or false on failure.
4713   */
4714  function wp_resolve_post_date( $post_date = '', $post_date_gmt = '' ) {
4715      // If the date is empty, set the date to now.
4716      if ( empty( $post_date ) || '0000-00-00 00:00:00' === $post_date ) {
4717          if ( empty( $post_date_gmt ) || '0000-00-00 00:00:00' === $post_date_gmt ) {
4718              $post_date = current_time( 'mysql' );
4719          } else {
4720              $post_date = get_date_from_gmt( $post_date_gmt );
4721          }
4722      }
4723  
4724      // Validate the date.
4725      $month = substr( $post_date, 5, 2 );
4726      $day   = substr( $post_date, 8, 2 );
4727      $year  = substr( $post_date, 0, 4 );
4728  
4729      $valid_date = wp_checkdate( $month, $day, $year, $post_date );
4730  
4731      if ( ! $valid_date ) {
4732          return false;
4733      }
4734      return $post_date;
4735  }
4736  
4737  /**
4738   * Computes a unique slug for the post, when given the desired slug and some post details.
4739   *
4740   * @since 2.8.0
4741   *
4742   * @global wpdb       $wpdb       WordPress database abstraction object.
4743   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
4744   *
4745   * @param string $slug        The desired slug (post_name).
4746   * @param int    $post_ID     Post ID.
4747   * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
4748   * @param string $post_type   Post type.
4749   * @param int    $post_parent Post parent ID.
4750   * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
4751   */
4752  function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
4753      if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
4754          || ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
4755      ) {
4756          return $slug;
4757      }
4758  
4759      /**
4760       * Filters the post slug before it is generated to be unique.
4761       *
4762       * Returning a non-null value will short-circuit the
4763       * unique slug generation, returning the passed value instead.
4764       *
4765       * @since 5.1.0
4766       *
4767       * @param string|null $override_slug Short-circuit return value.
4768       * @param string      $slug          The desired slug (post_name).
4769       * @param int         $post_ID       Post ID.
4770       * @param string      $post_status   The post status.
4771       * @param string      $post_type     Post type.
4772       * @param int         $post_parent   Post parent ID.
4773       */
4774      $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
4775      if ( null !== $override_slug ) {
4776          return $override_slug;
4777      }
4778  
4779      global $wpdb, $wp_rewrite;
4780  
4781      $original_slug = $slug;
4782  
4783      $feeds = $wp_rewrite->feeds;
4784      if ( ! is_array( $feeds ) ) {
4785          $feeds = array();
4786      }
4787  
4788      if ( 'attachment' === $post_type ) {
4789          // Attachment slugs must be unique across all types.
4790          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
4791          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
4792  
4793          /**
4794           * Filters whether the post slug would make a bad attachment slug.
4795           *
4796           * @since 3.1.0
4797           *
4798           * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
4799           * @param string $slug     The post slug.
4800           */
4801          $is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );
4802  
4803          if ( $post_name_check
4804              || in_array( $slug, $feeds, true ) || 'embed' === $slug
4805              || $is_bad_attachment_slug
4806          ) {
4807              $suffix = 2;
4808              do {
4809                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4810                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
4811                  $suffix++;
4812              } while ( $post_name_check );
4813              $slug = $alt_post_name;
4814          }
4815      } elseif ( is_post_type_hierarchical( $post_type ) ) {
4816          if ( 'nav_menu_item' === $post_type ) {
4817              return $slug;
4818          }
4819  
4820          /*
4821           * Page slugs must be unique within their own trees. Pages are in a separate
4822           * namespace than posts so page slugs are allowed to overlap post slugs.
4823           */
4824          $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";
4825          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
4826  
4827          /**
4828           * Filters whether the post slug would make a bad hierarchical post slug.
4829           *
4830           * @since 3.1.0
4831           *
4832           * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
4833           * @param string $slug        The post slug.
4834           * @param string $post_type   Post type.
4835           * @param int    $post_parent Post parent ID.
4836           */
4837          $is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );
4838  
4839          if ( $post_name_check
4840              || in_array( $slug, $feeds, true ) || 'embed' === $slug
4841              || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
4842              || $is_bad_hierarchical_slug
4843          ) {
4844              $suffix = 2;
4845              do {
4846                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4847                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
4848                  $suffix++;
4849              } while ( $post_name_check );
4850              $slug = $alt_post_name;
4851          }
4852      } else {
4853          // Post slugs must be unique across all posts.
4854          $check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
4855          $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
4856  
4857          $post = get_post( $post_ID );
4858  
4859          // Prevent new post slugs that could result in URLs that conflict with date archives.
4860          $conflicts_with_date_archive = false;
4861          if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
4862              $slug_num = (int) $slug;
4863  
4864              if ( $slug_num ) {
4865                  $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
4866                  $postname_index = array_search( '%postname%', $permastructs, true );
4867  
4868                  /*
4869                  * Potential date clashes are as follows:
4870                  *
4871                  * - Any integer in the first permastruct position could be a year.
4872                  * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
4873                  * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
4874                  */
4875                  if ( 0 === $postname_index ||
4876                      ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
4877                      ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
4878                  ) {
4879                      $conflicts_with_date_archive = true;
4880                  }
4881              }
4882          }
4883  
4884          /**
4885           * Filters whether the post slug would be bad as a flat slug.
4886           *
4887           * @since 3.1.0
4888           *
4889           * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
4890           * @param string $slug      The post slug.
4891           * @param string $post_type Post type.
4892           */
4893          $is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );
4894  
4895          if ( $post_name_check
4896              || in_array( $slug, $feeds, true ) || 'embed' === $slug
4897              || $conflicts_with_date_archive
4898              || $is_bad_flat_slug
4899          ) {
4900              $suffix = 2;
4901              do {
4902                  $alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4903                  $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
4904                  $suffix++;
4905              } while ( $post_name_check );
4906              $slug = $alt_post_name;
4907          }
4908      }
4909  
4910      /**
4911       * Filters the unique post slug.
4912       *
4913       * @since 3.3.0
4914       *
4915       * @param string $slug          The post slug.
4916       * @param int    $post_ID       Post ID.
4917       * @param string $post_status   The post status.
4918       * @param string $post_type     Post type.
4919       * @param int    $post_parent   Post parent ID
4920       * @param string $original_slug The original post slug.
4921       */
4922      return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
4923  }
4924  
4925  /**
4926   * Truncate a post slug.
4927   *
4928   * @since 3.6.0
4929   * @access private
4930   *
4931   * @see utf8_uri_encode()
4932   *
4933   * @param string $slug   The slug to truncate.
4934   * @param int    $length Optional. Max length of the slug. Default 200 (characters).
4935   * @return string The truncated slug.
4936   */
4937  function _truncate_post_slug( $slug, $length = 200 ) {
4938      if ( strlen( $slug ) > $length ) {
4939          $decoded_slug = urldecode( $slug );
4940          if ( $decoded_slug === $slug ) {
4941              $slug = substr( $slug, 0, $length );
4942          } else {
4943              $slug = utf8_uri_encode( $decoded_slug, $length );
4944          }
4945      }
4946  
4947      return rtrim( $slug, '-' );
4948  }
4949  
4950  /**
4951   * Add tags to a post.
4952   *
4953   * @see wp_set_post_tags()
4954   *
4955   * @since 2.3.0
4956   *
4957   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4958   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4959   *                              separated by commas. Default empty.
4960   * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
4961   */
4962  function wp_add_post_tags( $post_id = 0, $tags = '' ) {
4963      return wp_set_post_tags( $post_id, $tags, true );
4964  }
4965  
4966  /**
4967   * Set the tags for a post.
4968   *
4969   * @since 2.3.0
4970   *
4971   * @see wp_set_object_terms()
4972   *
4973   * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4974   * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
4975   *                              separated by commas. Default empty.
4976   * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
4977   *                              replace the tags with the new tags. Default false.
4978   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4979   */
4980  function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
4981      return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
4982  }
4983  
4984  /**
4985   * Set the terms for a post.
4986   *
4987   * @since 2.8.0
4988   *
4989   * @see wp_set_object_terms()
4990   *
4991   * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
4992   * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
4993   *                               separated by commas. Hierarchical taxonomies must always pass IDs rather
4994   *                               than names so that children with the same names but different parents
4995   *                               aren't confused. Default empty.
4996   * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
4997   * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
4998   *                               replace the terms with the new terms. Default false.
4999   * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
5000   */
5001  function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
5002      $post_id = (int) $post_id;
5003  
5004      if ( ! $post_id ) {
5005          return false;
5006      }
5007  
5008      if ( empty( $tags ) ) {
5009          $tags = array();
5010      }
5011  
5012      if ( ! is_array( $tags ) ) {
5013          $comma = _x( ',', 'tag delimiter' );
5014          if ( ',' !== $comma ) {
5015              $tags = str_replace( $comma, ',', $tags );
5016          }
5017          $tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
5018      }
5019  
5020      /*
5021       * Hierarchical taxonomies must always pass IDs rather than names so that
5022       * children with the same names but different parents aren't confused.
5023       */
5024      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
5025          $tags = array_unique( array_map( 'intval', $tags ) );
5026      }
5027  
5028      return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
5029  }
5030  
5031  /**
5032   * Set categories for a post.
5033   *
5034   * If no categories are provided, the default category is used.
5035   *
5036   * @since 2.1.0
5037   *
5038   * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
5039   *                                   of the global $post. Default 0.
5040   * @param int[]|int $post_categories Optional. List of category IDs, or the ID of a single category.
5041   *                                   Default empty array.
5042   * @param bool      $append          If true, don't delete existing categories, just add on.
5043   *                                   If false, replace the categories with the new categories.
5044   * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
5045   */
5046  function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
5047      $post_ID     = (int) $post_ID;
5048      $post_type   = get_post_type( $post_ID );
5049      $post_status = get_post_status( $post_ID );
5050  
5051      // If $post_categories isn't already an array, make it one.
5052      $post_categories = (array) $post_categories;
5053  
5054      if ( empty( $post_categories ) ) {
5055          /**
5056           * Filters post types (in addition to 'post') that require a default category.
5057           *
5058           * @since 5.5.0
5059           *
5060           * @param string[] $post_types An array of post type names. Default empty array.
5061           */
5062          $default_category_post_types = apply_filters( 'default_category_post_types', array() );
5063  
5064          // Regular posts always require a default category.
5065          $default_category_post_types = array_merge( $default_category_post_types, array( 'post' ) );
5066  
5067          if ( in_array( $post_type, $default_category_post_types, true )
5068              && is_object_in_taxonomy( $post_type, 'category' )
5069              && 'auto-draft' !== $post_status
5070          ) {
5071              $post_categories = array( get_option( 'default_category' ) );
5072              $append          = false;
5073          } else {
5074              $post_categories = array();
5075          }
5076      } elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
5077          return true;
5078      }
5079  
5080      return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
5081  }
5082  
5083  /**
5084   * Fires actions related to the transitioning of a post's status.
5085   *
5086   * When a post is saved, the post status is "transitioned" from one status to another,
5087   * though this does not always mean the status has actually changed before and after
5088   * the save. This function fires a number of action hooks related to that transition:
5089   * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
5090   * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
5091   * that the function does not transition the post object in the database.
5092   *
5093   * For instance: When publishing a post for the first time, the post status may transition
5094   * from 'draft' – or some other status – to 'publish'. However, if a post is already
5095   * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
5096   * before and after the transition.
5097   *
5098   * @since 2.3.0
5099   *
5100   * @param string  $new_status Transition to this post status.
5101   * @param string  $old_status Previous post status.
5102   * @param WP_Post $post Post data.
5103   */
5104  function wp_transition_post_status( $new_status, $old_status, $post ) {
5105      /**
5106       * Fires when a post is transitioned from one status to another.
5107       *
5108       * @since 2.3.0
5109       *
5110       * @param string  $new_status New post status.
5111       * @param string  $old_status Old post status.
5112       * @param WP_Post $post       Post object.
5113       */
5114      do_action( 'transition_post_status', $new_status, $old_status, $post );
5115  
5116      /**
5117       * Fires when a post is transitioned from one status to another.
5118       *
5119       * The dynamic portions of the hook name, `$new_status` and `$old_status`,
5120       * refer to the old and new post statuses, respectively.
5121       *
5122       * Possible hook names include:
5123       *
5124       *  - `draft_to_publish`
5125       *  - `publish_to_trash`
5126       *  - `pending_to_draft`
5127       *
5128       * @since 2.3.0
5129       *
5130       * @param WP_Post $post Post object.
5131       */
5132      do_action( "{$old_status}_to_{$new_status}", $post );
5133  
5134      /**
5135       * Fires when a post is transitioned from one status to another.
5136       *
5137       * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
5138       * refer to the new post status and post type, respectively.
5139       *
5140       * Possible hook names include:
5141       *
5142       *  - `draft_post`
5143       *  - `future_post`
5144       *  - `pending_post`
5145       *  - `private_post`
5146       *  - `publish_post`
5147       *  - `trash_post`
5148       *  - `draft_page`
5149       *  - `future_page`
5150       *  - `pending_page`
5151       *  - `private_page`
5152       *  - `publish_page`
5153       *  - `trash_page`
5154       *  - `publish_attachment`
5155       *  - `trash_attachment`
5156       *
5157       * Please note: When this action is hooked using a particular post status (like
5158       * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
5159       * first transitioned to that status from something else, as well as upon
5160       * subsequent post updates (old and new status are both the same).
5161       *
5162       * Therefore, if you are looking to only fire a callback when a post is first
5163       * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
5164       *
5165       * @since 2.3.0
5166       *
5167       * @param int     $post_id Post ID.
5168       * @param WP_Post $post    Post object.
5169       */
5170      do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
5171  }
5172  
5173  /**
5174   * Fires actions after a post, its terms and meta data has been saved.
5175   *
5176   * @since 5.6.0
5177   *
5178   * @param int|WP_Post  $post        The post ID or object that has been saved.
5179   * @param bool         $update      Whether this is an existing post being updated.
5180   * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
5181   *                                  to the update for updated posts.
5182   */
5183  function wp_after_insert_post( $post, $update, $post_before ) {
5184      $post = get_post( $post );
5185      if ( ! $post ) {
5186          return;
5187      }
5188  
5189      $post_id = $post->ID;
5190  
5191      /**
5192       * Fires once a post, its terms and meta data has been saved.
5193       *
5194       * @since 5.6.0
5195       *
5196       * @param int          $post_id     Post ID.
5197       * @param WP_Post      $post        Post object.
5198       * @param bool         $update      Whether this is an existing post being updated.
5199       * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
5200       *                                  to the update for updated posts.
5201       */
5202      do_action( 'wp_after_insert_post', $post_id, $post, $update, $post_before );
5203  }
5204  
5205  //
5206  // Comment, trackback, and pingback functions.
5207  //
5208  
5209  /**
5210   * Add a URL to those already pinged.
5211   *
5212   * @since 1.5.0
5213   * @since 4.7.0 `$post_id` can be a WP_Post object.
5214   * @since 4.7.0 `$uri` can be an array of URIs.
5215   *
5216   * @global wpdb $wpdb WordPress database abstraction object.
5217   *
5218   * @param int|WP_Post  $post_id Post object or ID.
5219   * @param string|array $uri     Ping URI or array of URIs.
5220   * @return int|false How many rows were updated.
5221   */
5222  function add_ping( $post_id, $uri ) {
5223      global $wpdb;
5224  
5225      $post = get_post( $post_id );
5226  
5227      if ( ! $post ) {
5228          return false;
5229      }
5230  
5231      $pung = trim( $post->pinged );
5232      $pung =