balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

post.php (264501B)


      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
    811  *                                 values return the current global post inside the loop. A numerically valid post
    812  *                                 ID that points to a non-existent post returns `null`. Defaults to global $post.
    813  * @param string           $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
    814  *                                 correspond to a WP_Post object, an associative array, or a numeric array,
    815  *                                 respectively. Default OBJECT.
    816  * @param string           $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
    817  *                                 or 'display'. Default 'raw'.
    818  * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
    819  *                            When $output is OBJECT, a `WP_Post` instance is returned.
    820  */
    821 function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
    822 	if ( empty( $post ) && isset( $GLOBALS['post'] ) ) {
    823 		$post = $GLOBALS['post'];
    824 	}
    825 
    826 	if ( $post instanceof WP_Post ) {
    827 		$_post = $post;
    828 	} elseif ( is_object( $post ) ) {
    829 		if ( empty( $post->filter ) ) {
    830 			$_post = sanitize_post( $post, 'raw' );
    831 			$_post = new WP_Post( $_post );
    832 		} elseif ( 'raw' === $post->filter ) {
    833 			$_post = new WP_Post( $post );
    834 		} else {
    835 			$_post = WP_Post::get_instance( $post->ID );
    836 		}
    837 	} else {
    838 		$_post = WP_Post::get_instance( $post );
    839 	}
    840 
    841 	if ( ! $_post ) {
    842 		return null;
    843 	}
    844 
    845 	$_post = $_post->filter( $filter );
    846 
    847 	if ( ARRAY_A === $output ) {
    848 		return $_post->to_array();
    849 	} elseif ( ARRAY_N === $output ) {
    850 		return array_values( $_post->to_array() );
    851 	}
    852 
    853 	return $_post;
    854 }
    855 
    856 /**
    857  * Retrieves the IDs of the ancestors of a post.
    858  *
    859  * @since 2.5.0
    860  *
    861  * @param int|WP_Post $post Post ID or post object.
    862  * @return int[] Array of ancestor IDs or empty array if there are none.
    863  */
    864 function get_post_ancestors( $post ) {
    865 	$post = get_post( $post );
    866 
    867 	if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID ) {
    868 		return array();
    869 	}
    870 
    871 	$ancestors = array();
    872 
    873 	$id          = $post->post_parent;
    874 	$ancestors[] = $id;
    875 
    876 	while ( $ancestor = get_post( $id ) ) {
    877 		// Loop detection: If the ancestor has been seen before, break.
    878 		if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors, true ) ) {
    879 			break;
    880 		}
    881 
    882 		$id          = $ancestor->post_parent;
    883 		$ancestors[] = $id;
    884 	}
    885 
    886 	return $ancestors;
    887 }
    888 
    889 /**
    890  * Retrieve data from a post field based on Post ID.
    891  *
    892  * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
    893  * etc and based off of the post object property or key names.
    894  *
    895  * The context values are based off of the taxonomy filter functions and
    896  * supported values are found within those functions.
    897  *
    898  * @since 2.3.0
    899  * @since 4.5.0 The `$post` parameter was made optional.
    900  *
    901  * @see sanitize_post_field()
    902  *
    903  * @param string      $field   Post field name.
    904  * @param int|WP_Post $post    Optional. Post ID or post object. Defaults to global $post.
    905  * @param string      $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
    906  *                             or 'display'. Default 'display'.
    907  * @return string The value of the post field on success, empty string on failure.
    908  */
    909 function get_post_field( $field, $post = null, $context = 'display' ) {
    910 	$post = get_post( $post );
    911 
    912 	if ( ! $post ) {
    913 		return '';
    914 	}
    915 
    916 	if ( ! isset( $post->$field ) ) {
    917 		return '';
    918 	}
    919 
    920 	return sanitize_post_field( $field, $post->$field, $post->ID, $context );
    921 }
    922 
    923 /**
    924  * Retrieve the mime type of an attachment based on the ID.
    925  *
    926  * This function can be used with any post type, but it makes more sense with
    927  * attachments.
    928  *
    929  * @since 2.0.0
    930  *
    931  * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
    932  * @return string|false The mime type on success, false on failure.
    933  */
    934 function get_post_mime_type( $post = null ) {
    935 	$post = get_post( $post );
    936 
    937 	if ( is_object( $post ) ) {
    938 		return $post->post_mime_type;
    939 	}
    940 
    941 	return false;
    942 }
    943 
    944 /**
    945  * Retrieve the post status based on the post ID.
    946  *
    947  * If the post ID is of an attachment, then the parent post status will be given
    948  * instead.
    949  *
    950  * @since 2.0.0
    951  *
    952  * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post..
    953  * @return string|false Post status on success, false on failure.
    954  */
    955 function get_post_status( $post = null ) {
    956 	$post = get_post( $post );
    957 
    958 	if ( ! is_object( $post ) ) {
    959 		return false;
    960 	}
    961 
    962 	$post_status = $post->post_status;
    963 
    964 	if (
    965 		'attachment' === $post->post_type &&
    966 		'inherit' === $post_status
    967 	) {
    968 		if (
    969 			0 === $post->post_parent ||
    970 			! get_post( $post->post_parent ) ||
    971 			$post->ID === $post->post_parent
    972 		) {
    973 			// Unattached attachments with inherit status are assumed to be published.
    974 			$post_status = 'publish';
    975 		} elseif ( 'trash' === get_post_status( $post->post_parent ) ) {
    976 			// Get parent status prior to trashing.
    977 			$post_status = get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
    978 			if ( ! $post_status ) {
    979 				// Assume publish as above.
    980 				$post_status = 'publish';
    981 			}
    982 		} else {
    983 			$post_status = get_post_status( $post->post_parent );
    984 		}
    985 	} elseif (
    986 		'attachment' === $post->post_type &&
    987 		! in_array( $post_status, array( 'private', 'trash', 'auto-draft' ), true )
    988 	) {
    989 		/*
    990 		 * Ensure uninherited attachments have a permitted status either 'private', 'trash', 'auto-draft'.
    991 		 * This is to match the logic in wp_insert_post().
    992 		 *
    993 		 * Note: 'inherit' is excluded from this check as it is resolved to the parent post's
    994 		 * status in the logic block above.
    995 		 */
    996 		$post_status = 'publish';
    997 	}
    998 
    999 	/**
   1000 	 * Filters the post status.
   1001 	 *
   1002 	 * @since 4.4.0
   1003 	 * @since 5.7.0 The attachment post type is now passed through this filter.
   1004 	 *
   1005 	 * @param string  $post_status The post status.
   1006 	 * @param WP_Post $post        The post object.
   1007 	 */
   1008 	return apply_filters( 'get_post_status', $post_status, $post );
   1009 }
   1010 
   1011 /**
   1012  * Retrieve all of the WordPress supported post statuses.
   1013  *
   1014  * Posts have a limited set of valid status values, this provides the
   1015  * post_status values and descriptions.
   1016  *
   1017  * @since 2.5.0
   1018  *
   1019  * @return string[] Array of post status labels keyed by their status.
   1020  */
   1021 function get_post_statuses() {
   1022 	$status = array(
   1023 		'draft'   => __( 'Draft' ),
   1024 		'pending' => __( 'Pending Review' ),
   1025 		'private' => __( 'Private' ),
   1026 		'publish' => __( 'Published' ),
   1027 	);
   1028 
   1029 	return $status;
   1030 }
   1031 
   1032 /**
   1033  * Retrieve all of the WordPress support page statuses.
   1034  *
   1035  * Pages have a limited set of valid status values, this provides the
   1036  * post_status values and descriptions.
   1037  *
   1038  * @since 2.5.0
   1039  *
   1040  * @return string[] Array of page status labels keyed by their status.
   1041  */
   1042 function get_page_statuses() {
   1043 	$status = array(
   1044 		'draft'   => __( 'Draft' ),
   1045 		'private' => __( 'Private' ),
   1046 		'publish' => __( 'Published' ),
   1047 	);
   1048 
   1049 	return $status;
   1050 }
   1051 
   1052 /**
   1053  * Return statuses for privacy requests.
   1054  *
   1055  * @since 4.9.6
   1056  * @access private
   1057  *
   1058  * @return array
   1059  */
   1060 function _wp_privacy_statuses() {
   1061 	return array(
   1062 		'request-pending'   => _x( 'Pending', 'request status' ),      // Pending confirmation from user.
   1063 		'request-confirmed' => _x( 'Confirmed', 'request status' ),    // User has confirmed the action.
   1064 		'request-failed'    => _x( 'Failed', 'request status' ),       // User failed to confirm the action.
   1065 		'request-completed' => _x( 'Completed', 'request status' ),    // Admin has handled the request.
   1066 	);
   1067 }
   1068 
   1069 /**
   1070  * Register a post status. Do not use before init.
   1071  *
   1072  * A simple function for creating or modifying a post status based on the
   1073  * parameters given. The function will accept an array (second optional
   1074  * parameter), along with a string for the post status name.
   1075  *
   1076  * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
   1077  *
   1078  * @since 3.0.0
   1079  *
   1080  * @global array $wp_post_statuses Inserts new post status object into the list
   1081  *
   1082  * @param string       $post_status Name of the post status.
   1083  * @param array|string $args {
   1084  *     Optional. Array or string of post status arguments.
   1085  *
   1086  *     @type bool|string $label                     A descriptive name for the post status marked
   1087  *                                                  for translation. Defaults to value of $post_status.
   1088  *     @type bool|array  $label_count               Descriptive text to use for nooped plurals.
   1089  *                                                  Default array of $label, twice.
   1090  *     @type bool        $exclude_from_search       Whether to exclude posts with this post status
   1091  *                                                  from search results. Default is value of $internal.
   1092  *     @type bool        $_builtin                  Whether the status is built-in. Core-use only.
   1093  *                                                  Default false.
   1094  *     @type bool        $public                    Whether posts of this status should be shown
   1095  *                                                  in the front end of the site. Default false.
   1096  *     @type bool        $internal                  Whether the status is for internal use only.
   1097  *                                                  Default false.
   1098  *     @type bool        $protected                 Whether posts with this status should be protected.
   1099  *                                                  Default false.
   1100  *     @type bool        $private                   Whether posts with this status should be private.
   1101  *                                                  Default false.
   1102  *     @type bool        $publicly_queryable        Whether posts with this status should be publicly-
   1103  *                                                  queryable. Default is value of $public.
   1104  *     @type bool        $show_in_admin_all_list    Whether to include posts in the edit listing for
   1105  *                                                  their post type. Default is the opposite value
   1106  *                                                  of $internal.
   1107  *     @type bool        $show_in_admin_status_list Show in the list of statuses with post counts at
   1108  *                                                  the top of the edit listings,
   1109  *                                                  e.g. All (12) | Published (9) | My Custom Status (2)
   1110  *                                                  Default is the opposite value of $internal.
   1111  *     @type bool        $date_floating             Whether the post has a floating creation date.
   1112  *                                                  Default to false.
   1113  * }
   1114  * @return object
   1115  */
   1116 function register_post_status( $post_status, $args = array() ) {
   1117 	global $wp_post_statuses;
   1118 
   1119 	if ( ! is_array( $wp_post_statuses ) ) {
   1120 		$wp_post_statuses = array();
   1121 	}
   1122 
   1123 	// Args prefixed with an underscore are reserved for internal use.
   1124 	$defaults = array(
   1125 		'label'                     => false,
   1126 		'label_count'               => false,
   1127 		'exclude_from_search'       => null,
   1128 		'_builtin'                  => false,
   1129 		'public'                    => null,
   1130 		'internal'                  => null,
   1131 		'protected'                 => null,
   1132 		'private'                   => null,
   1133 		'publicly_queryable'        => null,
   1134 		'show_in_admin_status_list' => null,
   1135 		'show_in_admin_all_list'    => null,
   1136 		'date_floating'             => null,
   1137 	);
   1138 	$args     = wp_parse_args( $args, $defaults );
   1139 	$args     = (object) $args;
   1140 
   1141 	$post_status = sanitize_key( $post_status );
   1142 	$args->name  = $post_status;
   1143 
   1144 	// Set various defaults.
   1145 	if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private ) {
   1146 		$args->internal = true;
   1147 	}
   1148 
   1149 	if ( null === $args->public ) {
   1150 		$args->public = false;
   1151 	}
   1152 
   1153 	if ( null === $args->private ) {
   1154 		$args->private = false;
   1155 	}
   1156 
   1157 	if ( null === $args->protected ) {
   1158 		$args->protected = false;
   1159 	}
   1160 
   1161 	if ( null === $args->internal ) {
   1162 		$args->internal = false;
   1163 	}
   1164 
   1165 	if ( null === $args->publicly_queryable ) {
   1166 		$args->publicly_queryable = $args->public;
   1167 	}
   1168 
   1169 	if ( null === $args->exclude_from_search ) {
   1170 		$args->exclude_from_search = $args->internal;
   1171 	}
   1172 
   1173 	if ( null === $args->show_in_admin_all_list ) {
   1174 		$args->show_in_admin_all_list = ! $args->internal;
   1175 	}
   1176 
   1177 	if ( null === $args->show_in_admin_status_list ) {
   1178 		$args->show_in_admin_status_list = ! $args->internal;
   1179 	}
   1180 
   1181 	if ( null === $args->date_floating ) {
   1182 		$args->date_floating = false;
   1183 	}
   1184 
   1185 	if ( false === $args->label ) {
   1186 		$args->label = $post_status;
   1187 	}
   1188 
   1189 	if ( false === $args->label_count ) {
   1190 		// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingle,WordPress.WP.I18n.NonSingularStringLiteralPlural
   1191 		$args->label_count = _n_noop( $args->label, $args->label );
   1192 	}
   1193 
   1194 	$wp_post_statuses[ $post_status ] = $args;
   1195 
   1196 	return $args;
   1197 }
   1198 
   1199 /**
   1200  * Retrieve a post status object by name.
   1201  *
   1202  * @since 3.0.0
   1203  *
   1204  * @global array $wp_post_statuses List of post statuses.
   1205  *
   1206  * @see register_post_status()
   1207  *
   1208  * @param string $post_status The name of a registered post status.
   1209  * @return object|null A post status object.
   1210  */
   1211 function get_post_status_object( $post_status ) {
   1212 	global $wp_post_statuses;
   1213 
   1214 	if ( empty( $wp_post_statuses[ $post_status ] ) ) {
   1215 		return null;
   1216 	}
   1217 
   1218 	return $wp_post_statuses[ $post_status ];
   1219 }
   1220 
   1221 /**
   1222  * Get a list of post statuses.
   1223  *
   1224  * @since 3.0.0
   1225  *
   1226  * @global array $wp_post_statuses List of post statuses.
   1227  *
   1228  * @see register_post_status()
   1229  *
   1230  * @param array|string $args     Optional. Array or string of post status arguments to compare against
   1231  *                               properties of the global `$wp_post_statuses objects`. Default empty array.
   1232  * @param string       $output   Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
   1233  * @param string       $operator Optional. The logical operation to perform. 'or' means only one element
   1234  *                               from the array needs to match; 'and' means all elements must match.
   1235  *                               Default 'and'.
   1236  * @return array 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       $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
   2179  *     available arguments.
   2180  *
   2181  *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of `$posts_per_page`
   2182  *                                        in WP_Query. Accepts -1 for all. Default 5.
   2183  *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
   2184  *                                        Is an alias of `$cat` in WP_Query. Default 0.
   2185  *     @type int[]      $include          An array of post IDs to retrieve, sticky posts will be included.
   2186  *                                        Is an alias of `$post__in` in WP_Query. Default empty array.
   2187  *     @type int[]      $exclude          An array of post IDs not to retrieve. Default empty array.
   2188  *     @type bool       $suppress_filters Whether to suppress filters. Default true.
   2189  * }
   2190  * @return WP_Post[]|int[] Array of post objects or post IDs.
   2191  */
   2192 function get_posts( $args = null ) {
   2193 	$defaults = array(
   2194 		'numberposts'      => 5,
   2195 		'category'         => 0,
   2196 		'orderby'          => 'date',
   2197 		'order'            => 'DESC',
   2198 		'include'          => array(),
   2199 		'exclude'          => array(),
   2200 		'meta_key'         => '',
   2201 		'meta_value'       => '',
   2202 		'post_type'        => 'post',
   2203 		'suppress_filters' => true,
   2204 	);
   2205 
   2206 	$parsed_args = wp_parse_args( $args, $defaults );
   2207 	if ( empty( $parsed_args['post_status'] ) ) {
   2208 		$parsed_args['post_status'] = ( 'attachment' === $parsed_args['post_type'] ) ? 'inherit' : 'publish';
   2209 	}
   2210 	if ( ! empty( $parsed_args['numberposts'] ) && empty( $parsed_args['posts_per_page'] ) ) {
   2211 		$parsed_args['posts_per_page'] = $parsed_args['numberposts'];
   2212 	}
   2213 	if ( ! empty( $parsed_args['category'] ) ) {
   2214 		$parsed_args['cat'] = $parsed_args['category'];
   2215 	}
   2216 	if ( ! empty( $parsed_args['include'] ) ) {
   2217 		$incposts                      = wp_parse_id_list( $parsed_args['include'] );
   2218 		$parsed_args['posts_per_page'] = count( $incposts );  // Only the number of posts included.
   2219 		$parsed_args['post__in']       = $incposts;
   2220 	} elseif ( ! empty( $parsed_args['exclude'] ) ) {
   2221 		$parsed_args['post__not_in'] = wp_parse_id_list( $parsed_args['exclude'] );
   2222 	}
   2223 
   2224 	$parsed_args['ignore_sticky_posts'] = true;
   2225 	$parsed_args['no_found_rows']       = true;
   2226 
   2227 	$get_posts = new WP_Query;
   2228 	return $get_posts->query( $parsed_args );
   2229 
   2230 }
   2231 
   2232 //
   2233 // Post meta functions.
   2234 //
   2235 
   2236 /**
   2237  * Adds a meta field to the given post.
   2238  *
   2239  * Post meta data is called "Custom Fields" on the Administration Screen.
   2240  *
   2241  * @since 1.5.0
   2242  *
   2243  * @param int    $post_id    Post ID.
   2244  * @param string $meta_key   Metadata name.
   2245  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
   2246  * @param bool   $unique     Optional. Whether the same key should not be added.
   2247  *                           Default false.
   2248  * @return int|false Meta ID on success, false on failure.
   2249  */
   2250 function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
   2251 	// Make sure meta is added to the post, not a revision.
   2252 	$the_post = wp_is_post_revision( $post_id );
   2253 	if ( $the_post ) {
   2254 		$post_id = $the_post;
   2255 	}
   2256 
   2257 	return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
   2258 }
   2259 
   2260 /**
   2261  * Deletes a post meta field for the given post ID.
   2262  *
   2263  * You can match based on the key, or key and value. Removing based on key and
   2264  * value, will keep from removing duplicate metadata with the same key. It also
   2265  * allows removing all metadata matching the key, if needed.
   2266  *
   2267  * @since 1.5.0
   2268  *
   2269  * @param int    $post_id    Post ID.
   2270  * @param string $meta_key   Metadata name.
   2271  * @param mixed  $meta_value Optional. Metadata value. If provided,
   2272  *                           rows will only be removed that match the value.
   2273  *                           Must be serializable if non-scalar. Default empty.
   2274  * @return bool True on success, false on failure.
   2275  */
   2276 function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
   2277 	// Make sure meta is added to the post, not a revision.
   2278 	$the_post = wp_is_post_revision( $post_id );
   2279 	if ( $the_post ) {
   2280 		$post_id = $the_post;
   2281 	}
   2282 
   2283 	return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
   2284 }
   2285 
   2286 /**
   2287  * Retrieves a post meta field for the given post ID.
   2288  *
   2289  * @since 1.5.0
   2290  *
   2291  * @param int    $post_id Post ID.
   2292  * @param string $key     Optional. The meta key to retrieve. By default,
   2293  *                        returns data for all keys. Default empty.
   2294  * @param bool   $single  Optional. Whether to return a single value.
   2295  *                        This parameter has no effect if `$key` is not specified.
   2296  *                        Default false.
   2297  * @return mixed An array of values if `$single` is false.
   2298  *               The value of the meta field if `$single` is true.
   2299  *               False for an invalid `$post_id` (non-numeric, zero, or negative value).
   2300  *               An empty string if a valid but non-existing post ID is passed.
   2301  */
   2302 function get_post_meta( $post_id, $key = '', $single = false ) {
   2303 	return get_metadata( 'post', $post_id, $key, $single );
   2304 }
   2305 
   2306 /**
   2307  * Updates a post meta field based on the given post ID.
   2308  *
   2309  * Use the `$prev_value` parameter to differentiate between meta fields with the
   2310  * same key and post ID.
   2311  *
   2312  * If the meta field for the post does not exist, it will be added and its ID returned.
   2313  *
   2314  * Can be used in place of add_post_meta().
   2315  *
   2316  * @since 1.5.0
   2317  *
   2318  * @param int    $post_id    Post ID.
   2319  * @param string $meta_key   Metadata key.
   2320  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
   2321  * @param mixed  $prev_value Optional. Previous value to check before updating.
   2322  *                           If specified, only update existing metadata entries with
   2323  *                           this value. Otherwise, update all entries. Default empty.
   2324  * @return int|bool Meta ID if the key didn't exist, true on successful update,
   2325  *                  false on failure or if the value passed to the function
   2326  *                  is the same as the one that is already in the database.
   2327  */
   2328 function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
   2329 	// Make sure meta is added to the post, not a revision.
   2330 	$the_post = wp_is_post_revision( $post_id );
   2331 	if ( $the_post ) {
   2332 		$post_id = $the_post;
   2333 	}
   2334 
   2335 	return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
   2336 }
   2337 
   2338 /**
   2339  * Deletes everything from post meta matching the given meta key.
   2340  *
   2341  * @since 2.3.0
   2342  *
   2343  * @param string $post_meta_key Key to search for when deleting.
   2344  * @return bool Whether the post meta key was deleted from the database.
   2345  */
   2346 function delete_post_meta_by_key( $post_meta_key ) {
   2347 	return delete_metadata( 'post', null, $post_meta_key, '', true );
   2348 }
   2349 
   2350 /**
   2351  * Registers a meta key for posts.
   2352  *
   2353  * @since 4.9.8
   2354  *
   2355  * @param string $post_type Post type to register a meta key for. Pass an empty string
   2356  *                          to register the meta key across all existing post types.
   2357  * @param string $meta_key  The meta key to register.
   2358  * @param array  $args      Data used to describe the meta key when registered. See
   2359  *                          {@see register_meta()} for a list of supported arguments.
   2360  * @return bool True if the meta key was successfully registered, false if not.
   2361  */
   2362 function register_post_meta( $post_type, $meta_key, array $args ) {
   2363 	$args['object_subtype'] = $post_type;
   2364 
   2365 	return register_meta( 'post', $meta_key, $args );
   2366 }
   2367 
   2368 /**
   2369  * Unregisters a meta key for posts.
   2370  *
   2371  * @since 4.9.8
   2372  *
   2373  * @param string $post_type Post type the meta key is currently registered for. Pass
   2374  *                          an empty string if the meta key is registered across all
   2375  *                          existing post types.
   2376  * @param string $meta_key  The meta key to unregister.
   2377  * @return bool True on success, false if the meta key was not previously registered.
   2378  */
   2379 function unregister_post_meta( $post_type, $meta_key ) {
   2380 	return unregister_meta_key( 'post', $meta_key, $post_type );
   2381 }
   2382 
   2383 /**
   2384  * Retrieve post meta fields, based on post ID.
   2385  *
   2386  * The post meta fields are retrieved from the cache where possible,
   2387  * so the function is optimized to be called more than once.
   2388  *
   2389  * @since 1.2.0
   2390  *
   2391  * @param int $post_id Optional. Post ID. Default is ID of the global $post.
   2392  * @return array Post meta for the given post.
   2393  */
   2394 function get_post_custom( $post_id = 0 ) {
   2395 	$post_id = absint( $post_id );
   2396 	if ( ! $post_id ) {
   2397 		$post_id = get_the_ID();
   2398 	}
   2399 
   2400 	return get_post_meta( $post_id );
   2401 }
   2402 
   2403 /**
   2404  * Retrieve meta field names for a post.
   2405  *
   2406  * If there are no meta fields, then nothing (null) will be returned.
   2407  *
   2408  * @since 1.2.0
   2409  *
   2410  * @param int $post_id Optional. Post ID. Default is ID of the global $post.
   2411  * @return array|void Array of the keys, if retrieved.
   2412  */
   2413 function get_post_custom_keys( $post_id = 0 ) {
   2414 	$custom = get_post_custom( $post_id );
   2415 
   2416 	if ( ! is_array( $custom ) ) {
   2417 		return;
   2418 	}
   2419 
   2420 	$keys = array_keys( $custom );
   2421 	if ( $keys ) {
   2422 		return $keys;
   2423 	}
   2424 }
   2425 
   2426 /**
   2427  * Retrieve values for a custom post field.
   2428  *
   2429  * The parameters must not be considered optional. All of the post meta fields
   2430  * will be retrieved and only the meta field key values returned.
   2431  *
   2432  * @since 1.2.0
   2433  *
   2434  * @param string $key     Optional. Meta field key. Default empty.
   2435  * @param int    $post_id Optional. Post ID. Default is ID of the global $post.
   2436  * @return array|null Meta field values.
   2437  */
   2438 function get_post_custom_values( $key = '', $post_id = 0 ) {
   2439 	if ( ! $key ) {
   2440 		return null;
   2441 	}
   2442 
   2443 	$custom = get_post_custom( $post_id );
   2444 
   2445 	return isset( $custom[ $key ] ) ? $custom[ $key ] : null;
   2446 }
   2447 
   2448 /**
   2449  * Determines whether a post is sticky.
   2450  *
   2451  * Sticky posts should remain at the top of The Loop. If the post ID is not
   2452  * given, then The Loop ID for the current post will be used.
   2453  *
   2454  * For more information on this and similar theme functions, check out
   2455  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
   2456  * Conditional Tags} article in the Theme Developer Handbook.
   2457  *
   2458  * @since 2.7.0
   2459  *
   2460  * @param int $post_id Optional. Post ID. Default is ID of the global $post.
   2461  * @return bool Whether post is sticky.
   2462  */
   2463 function is_sticky( $post_id = 0 ) {
   2464 	$post_id = absint( $post_id );
   2465 
   2466 	if ( ! $post_id ) {
   2467 		$post_id = get_the_ID();
   2468 	}
   2469 
   2470 	$stickies = get_option( 'sticky_posts' );
   2471 
   2472 	if ( is_array( $stickies ) ) {
   2473 		$stickies  = array_map( 'intval', $stickies );
   2474 		$is_sticky = in_array( $post_id, $stickies, true );
   2475 	} else {
   2476 		$is_sticky = false;
   2477 	}
   2478 
   2479 	/**
   2480 	 * Filters whether a post is sticky.
   2481 	 *
   2482 	 * @since 5.3.0
   2483 	 *
   2484 	 * @param bool $is_sticky Whether a post is sticky.
   2485 	 * @param int  $post_id   Post ID.
   2486 	 */
   2487 	return apply_filters( 'is_sticky', $is_sticky, $post_id );
   2488 }
   2489 
   2490 /**
   2491  * Sanitizes every post field.
   2492  *
   2493  * If the context is 'raw', then the post object or array will get minimal
   2494  * sanitization of the integer fields.
   2495  *
   2496  * @since 2.3.0
   2497  *
   2498  * @see sanitize_post_field()
   2499  *
   2500  * @param object|WP_Post|array $post    The post object or array
   2501  * @param string               $context Optional. How to sanitize post fields.
   2502  *                                      Accepts 'raw', 'edit', 'db', 'display',
   2503  *                                      'attribute', or 'js'. Default 'display'.
   2504  * @return object|WP_Post|array The now sanitized post object or array (will be the
   2505  *                              same type as `$post`).
   2506  */
   2507 function sanitize_post( $post, $context = 'display' ) {
   2508 	if ( is_object( $post ) ) {
   2509 		// Check if post already filtered for this context.
   2510 		if ( isset( $post->filter ) && $context == $post->filter ) {
   2511 			return $post;
   2512 		}
   2513 		if ( ! isset( $post->ID ) ) {
   2514 			$post->ID = 0;
   2515 		}
   2516 		foreach ( array_keys( get_object_vars( $post ) ) as $field ) {
   2517 			$post->$field = sanitize_post_field( $field, $post->$field, $post->ID, $context );
   2518 		}
   2519 		$post->filter = $context;
   2520 	} elseif ( is_array( $post ) ) {
   2521 		// Check if post already filtered for this context.
   2522 		if ( isset( $post['filter'] ) && $context == $post['filter'] ) {
   2523 			return $post;
   2524 		}
   2525 		if ( ! isset( $post['ID'] ) ) {
   2526 			$post['ID'] = 0;
   2527 		}
   2528 		foreach ( array_keys( $post ) as $field ) {
   2529 			$post[ $field ] = sanitize_post_field( $field, $post[ $field ], $post['ID'], $context );
   2530 		}
   2531 		$post['filter'] = $context;
   2532 	}
   2533 	return $post;
   2534 }
   2535 
   2536 /**
   2537  * Sanitizes a post field based on context.
   2538  *
   2539  * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and
   2540  * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
   2541  * are treated like 'display' when calling filters.
   2542  *
   2543  * @since 2.3.0
   2544  * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
   2545  *
   2546  * @param string $field   The Post Object field name.
   2547  * @param mixed  $value   The Post Object value.
   2548  * @param int    $post_id Post ID.
   2549  * @param string $context Optional. How to sanitize the field. Possible values are 'raw', 'edit',
   2550  *                        'db', 'display', 'attribute' and 'js'. Default 'display'.
   2551  * @return mixed Sanitized value.
   2552  */
   2553 function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
   2554 	$int_fields = array( 'ID', 'post_parent', 'menu_order' );
   2555 	if ( in_array( $field, $int_fields, true ) ) {
   2556 		$value = (int) $value;
   2557 	}
   2558 
   2559 	// Fields which contain arrays of integers.
   2560 	$array_int_fields = array( 'ancestors' );
   2561 	if ( in_array( $field, $array_int_fields, true ) ) {
   2562 		$value = array_map( 'absint', $value );
   2563 		return $value;
   2564 	}
   2565 
   2566 	if ( 'raw' === $context ) {
   2567 		return $value;
   2568 	}
   2569 
   2570 	$prefixed = false;
   2571 	if ( false !== strpos( $field, 'post_' ) ) {
   2572 		$prefixed        = true;
   2573 		$field_no_prefix = str_replace( 'post_', '', $field );
   2574 	}
   2575 
   2576 	if ( 'edit' === $context ) {
   2577 		$format_to_edit = array( 'post_content', 'post_excerpt', 'post_title', 'post_password' );
   2578 
   2579 		if ( $prefixed ) {
   2580 
   2581 			/**
   2582 			 * Filters the value of a specific post field to edit.
   2583 			 *
   2584 			 * The dynamic portion of the hook name, `$field`, refers to the post
   2585 			 * field name.
   2586 			 *
   2587 			 * @since 2.3.0
   2588 			 *
   2589 			 * @param mixed $value   Value of the post field.
   2590 			 * @param int   $post_id Post ID.
   2591 			 */
   2592 			$value = apply_filters( "edit_{$field}", $value, $post_id );
   2593 
   2594 			/**
   2595 			 * Filters the value of a specific post field to edit.
   2596 			 *
   2597 			 * The dynamic portion of the hook name, `$field_no_prefix`, refers to
   2598 			 * the post field name.
   2599 			 *
   2600 			 * @since 2.3.0
   2601 			 *
   2602 			 * @param mixed $value   Value of the post field.
   2603 			 * @param int   $post_id Post ID.
   2604 			 */
   2605 			$value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
   2606 		} else {
   2607 			$value = apply_filters( "edit_post_{$field}", $value, $post_id );
   2608 		}
   2609 
   2610 		if ( in_array( $field, $format_to_edit, true ) ) {
   2611 			if ( 'post_content' === $field ) {
   2612 				$value = format_to_edit( $value, user_can_richedit() );
   2613 			} else {
   2614 				$value = format_to_edit( $value );
   2615 			}
   2616 		} else {
   2617 			$value = esc_attr( $value );
   2618 		}
   2619 	} elseif ( 'db' === $context ) {
   2620 		if ( $prefixed ) {
   2621 
   2622 			/**
   2623 			 * Filters the value of a specific post field before saving.
   2624 			 *
   2625 			 * The dynamic portion of the hook name, `$field`, refers to the post
   2626 			 * field name.
   2627 			 *
   2628 			 * @since 2.3.0
   2629 			 *
   2630 			 * @param mixed $value Value of the post field.
   2631 			 */
   2632 			$value = apply_filters( "pre_{$field}", $value );
   2633 
   2634 			/**
   2635 			 * Filters the value of a specific field before saving.
   2636 			 *
   2637 			 * The dynamic portion of the hook name, `$field_no_prefix`, refers
   2638 			 * to the post field name.
   2639 			 *
   2640 			 * @since 2.3.0
   2641 			 *
   2642 			 * @param mixed $value Value of the post field.
   2643 			 */
   2644 			$value = apply_filters( "{$field_no_prefix}_save_pre", $value );
   2645 		} else {
   2646 			$value = apply_filters( "pre_post_{$field}", $value );
   2647 
   2648 			/**
   2649 			 * Filters the value of a specific post field before saving.
   2650 			 *
   2651 			 * The dynamic portion of the hook name, `$field`, refers to the post
   2652 			 * field name.
   2653 			 *
   2654 			 * @since 2.3.0
   2655 			 *
   2656 			 * @param mixed $value Value of the post field.
   2657 			 */
   2658 			$value = apply_filters( "{$field}_pre", $value );
   2659 		}
   2660 	} else {
   2661 
   2662 		// Use display filters by default.
   2663 		if ( $prefixed ) {
   2664 
   2665 			/**
   2666 			 * Filters the value of a specific post field for display.
   2667 			 *
   2668 			 * The dynamic portion of the hook name, `$field`, refers to the post
   2669 			 * field name.
   2670 			 *
   2671 			 * @since 2.3.0
   2672 			 *
   2673 			 * @param mixed  $value   Value of the prefixed post field.
   2674 			 * @param int    $post_id Post ID.
   2675 			 * @param string $context Context for how to sanitize the field.
   2676 			 *                        Accepts 'raw', 'edit', 'db', 'display',
   2677 			 *                        'attribute', or 'js'. Default 'display'.
   2678 			 */
   2679 			$value = apply_filters( "{$field}", $value, $post_id, $context );
   2680 		} else {
   2681 			$value = apply_filters( "post_{$field}", $value, $post_id, $context );
   2682 		}
   2683 
   2684 		if ( 'attribute' === $context ) {
   2685 			$value = esc_attr( $value );
   2686 		} elseif ( 'js' === $context ) {
   2687 			$value = esc_js( $value );
   2688 		}
   2689 	}
   2690 
   2691 	// Restore the type for integer fields after esc_attr().
   2692 	if ( in_array( $field, $int_fields, true ) ) {
   2693 		$value = (int) $value;
   2694 	}
   2695 
   2696 	return $value;
   2697 }
   2698 
   2699 /**
   2700  * Make a post sticky.
   2701  *
   2702  * Sticky posts should be displayed at the top of the front page.
   2703  *
   2704  * @since 2.7.0
   2705  *
   2706  * @param int $post_id Post ID.
   2707  */
   2708 function stick_post( $post_id ) {
   2709 	$post_id  = (int) $post_id;
   2710 	$stickies = get_option( 'sticky_posts' );
   2711 	$updated  = false;
   2712 
   2713 	if ( ! is_array( $stickies ) ) {
   2714 		$stickies = array( $post_id );
   2715 	} else {
   2716 		$stickies = array_unique( array_map( 'intval', $stickies ) );
   2717 	}
   2718 
   2719 	if ( ! in_array( $post_id, $stickies, true ) ) {
   2720 		$stickies[] = $post_id;
   2721 		$updated    = update_option( 'sticky_posts', array_values( $stickies ) );
   2722 	}
   2723 
   2724 	if ( $updated ) {
   2725 		/**
   2726 		 * Fires once a post has been added to the sticky list.
   2727 		 *
   2728 		 * @since 4.6.0
   2729 		 *
   2730 		 * @param int $post_id ID of the post that was stuck.
   2731 		 */
   2732 		do_action( 'post_stuck', $post_id );
   2733 	}
   2734 }
   2735 
   2736 /**
   2737  * Un-stick a post.
   2738  *
   2739  * Sticky posts should be displayed at the top of the front page.
   2740  *
   2741  * @since 2.7.0
   2742  *
   2743  * @param int $post_id Post ID.
   2744  */
   2745 function unstick_post( $post_id ) {
   2746 	$post_id  = (int) $post_id;
   2747 	$stickies = get_option( 'sticky_posts' );
   2748 
   2749 	if ( ! is_array( $stickies ) ) {
   2750 		return;
   2751 	}
   2752 
   2753 	$stickies = array_values( array_unique( array_map( 'intval', $stickies ) ) );
   2754 
   2755 	if ( ! in_array( $post_id, $stickies, true ) ) {
   2756 		return;
   2757 	}
   2758 
   2759 	$offset = array_search( $post_id, $stickies, true );
   2760 	if ( false === $offset ) {
   2761 		return;
   2762 	}
   2763 
   2764 	array_splice( $stickies, $offset, 1 );
   2765 
   2766 	$updated = update_option( 'sticky_posts', $stickies );
   2767 
   2768 	if ( $updated ) {
   2769 		/**
   2770 		 * Fires once a post has been removed from the sticky list.
   2771 		 *
   2772 		 * @since 4.6.0
   2773 		 *
   2774 		 * @param int $post_id ID of the post that was unstuck.
   2775 		 */
   2776 		do_action( 'post_unstuck', $post_id );
   2777 	}
   2778 }
   2779 
   2780 /**
   2781  * Return the cache key for wp_count_posts() based on the passed arguments.
   2782  *
   2783  * @since 3.9.0
   2784  * @access private
   2785  *
   2786  * @param string $type Optional. Post type to retrieve count Default 'post'.
   2787  * @param string $perm Optional. 'readable' or empty. Default empty.
   2788  * @return string The cache key.
   2789  */
   2790 function _count_posts_cache_key( $type = 'post', $perm = '' ) {
   2791 	$cache_key = 'posts-' . $type;
   2792 
   2793 	if ( 'readable' === $perm && is_user_logged_in() ) {
   2794 		$post_type_object = get_post_type_object( $type );
   2795 
   2796 		if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
   2797 			$cache_key .= '_' . $perm . '_' . get_current_user_id();
   2798 		}
   2799 	}
   2800 
   2801 	return $cache_key;
   2802 }
   2803 
   2804 /**
   2805  * Count number of posts of a post type and if user has permissions to view.
   2806  *
   2807  * This function provides an efficient method of finding the amount of post's
   2808  * type a blog has. Another method is to count the amount of items in
   2809  * get_posts(), but that method has a lot of overhead with doing so. Therefore,
   2810  * when developing for 2.5+, use this function instead.
   2811  *
   2812  * The $perm parameter checks for 'readable' value and if the user can read
   2813  * private posts, it will display that for the user that is signed in.
   2814  *
   2815  * @since 2.5.0
   2816  *
   2817  * @global wpdb $wpdb WordPress database abstraction object.
   2818  *
   2819  * @param string $type Optional. Post type to retrieve count. Default 'post'.
   2820  * @param string $perm Optional. 'readable' or empty. Default empty.
   2821  * @return object Number of posts for each status.
   2822  */
   2823 function wp_count_posts( $type = 'post', $perm = '' ) {
   2824 	global $wpdb;
   2825 
   2826 	if ( ! post_type_exists( $type ) ) {
   2827 		return new stdClass;
   2828 	}
   2829 
   2830 	$cache_key = _count_posts_cache_key( $type, $perm );
   2831 
   2832 	$counts = wp_cache_get( $cache_key, 'counts' );
   2833 	if ( false !== $counts ) {
   2834 		// We may have cached this before every status was registered.
   2835 		foreach ( get_post_stati() as $status ) {
   2836 			if ( ! isset( $counts->{$status} ) ) {
   2837 				$counts->{$status} = 0;
   2838 			}
   2839 		}
   2840 
   2841 		/** This filter is documented in wp-includes/post.php */
   2842 		return apply_filters( 'wp_count_posts', $counts, $type, $perm );
   2843 	}
   2844 
   2845 	$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
   2846 
   2847 	if ( 'readable' === $perm && is_user_logged_in() ) {
   2848 		$post_type_object = get_post_type_object( $type );
   2849 		if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
   2850 			$query .= $wpdb->prepare(
   2851 				" AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
   2852 				get_current_user_id()
   2853 			);
   2854 		}
   2855 	}
   2856 
   2857 	$query .= ' GROUP BY post_status';
   2858 
   2859 	$results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
   2860 	$counts  = array_fill_keys( get_post_stati(), 0 );
   2861 
   2862 	foreach ( $results as $row ) {
   2863 		$counts[ $row['post_status'] ] = $row['num_posts'];
   2864 	}
   2865 
   2866 	$counts = (object) $counts;
   2867 	wp_cache_set( $cache_key, $counts, 'counts' );
   2868 
   2869 	/**
   2870 	 * Modify returned post counts by status for the current post type.
   2871 	 *
   2872 	 * @since 3.7.0
   2873 	 *
   2874 	 * @param object $counts An object containing the current post_type's post
   2875 	 *                       counts by status.
   2876 	 * @param string $type   Post type.
   2877 	 * @param string $perm   The permission to determine if the posts are 'readable'
   2878 	 *                       by the current user.
   2879 	 */
   2880 	return apply_filters( 'wp_count_posts', $counts, $type, $perm );
   2881 }
   2882 
   2883 /**
   2884  * Count number of attachments for the mime type(s).
   2885  *
   2886  * If you set the optional mime_type parameter, then an array will still be
   2887  * returned, but will only have the item you are looking for. It does not give
   2888  * you the number of attachments that are children of a post. You can get that
   2889  * by counting the number of children that post has.
   2890  *
   2891  * @since 2.5.0
   2892  *
   2893  * @global wpdb $wpdb WordPress database abstraction object.
   2894  *
   2895  * @param string|string[] $mime_type Optional. Array or comma-separated list of
   2896  *                                   MIME patterns. Default empty.
   2897  * @return object An object containing the attachment counts by mime type.
   2898  */
   2899 function wp_count_attachments( $mime_type = '' ) {
   2900 	global $wpdb;
   2901 
   2902 	$and   = wp_post_mime_type_where( $mime_type );
   2903 	$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 );
   2904 
   2905 	$counts = array();
   2906 	foreach ( (array) $count as $row ) {
   2907 		$counts[ $row['post_mime_type'] ] = $row['num_posts'];
   2908 	}
   2909 	$counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and" );
   2910 
   2911 	/**
   2912 	 * Modify returned attachment counts by mime type.
   2913 	 *
   2914 	 * @since 3.7.0
   2915 	 *
   2916 	 * @param object          $counts    An object containing the attachment counts by
   2917 	 *                                   mime type.
   2918 	 * @param string|string[] $mime_type Array or comma-separated list of MIME patterns.
   2919 	 */
   2920 	return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
   2921 }
   2922 
   2923 /**
   2924  * Get default post mime types.
   2925  *
   2926  * @since 2.9.0
   2927  * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
   2928  *
   2929  * @return array List of post mime types.
   2930  */
   2931 function get_post_mime_types() {
   2932 	$post_mime_types = array(   // array( adj, noun )
   2933 		'image'       => array(
   2934 			__( 'Images' ),
   2935 			__( 'Manage Images' ),
   2936 			/* translators: %s: Number of images. */
   2937 			_n_noop(
   2938 				'Image <span class="count">(%s)</span>',
   2939 				'Images <span class="count">(%s)</span>'
   2940 			),
   2941 		),
   2942 		'audio'       => array(
   2943 			__( 'Audio' ),
   2944 			__( 'Manage Audio' ),
   2945 			/* translators: %s: Number of audio files. */
   2946 			_n_noop(
   2947 				'Audio <span class="count">(%s)</span>',
   2948 				'Audio <span class="count">(%s)</span>'
   2949 			),
   2950 		),
   2951 		'video'       => array(
   2952 			__( 'Video' ),
   2953 			__( 'Manage Video' ),
   2954 			/* translators: %s: Number of video files. */
   2955 			_n_noop(
   2956 				'Video <span class="count">(%s)</span>',
   2957 				'Video <span class="count">(%s)</span>'
   2958 			),
   2959 		),
   2960 		'document'    => array(
   2961 			__( 'Documents' ),
   2962 			__( 'Manage Documents' ),
   2963 			/* translators: %s: Number of documents. */
   2964 			_n_noop(
   2965 				'Document <span class="count">(%s)</span>',
   2966 				'Documents <span class="count">(%s)</span>'
   2967 			),
   2968 		),
   2969 		'spreadsheet' => array(
   2970 			__( 'Spreadsheets' ),
   2971 			__( 'Manage Spreadsheets' ),
   2972 			/* translators: %s: Number of spreadsheets. */
   2973 			_n_noop(
   2974 				'Spreadsheet <span class="count">(%s)</span>',
   2975 				'Spreadsheets <span class="count">(%s)</span>'
   2976 			),
   2977 		),
   2978 		'archive'     => array(
   2979 			_x( 'Archives', 'file type group' ),
   2980 			__( 'Manage Archives' ),
   2981 			/* translators: %s: Number of archives. */
   2982 			_n_noop(
   2983 				'Archive <span class="count">(%s)</span>',
   2984 				'Archives <span class="count">(%s)</span>'
   2985 			),
   2986 		),
   2987 	);
   2988 
   2989 	$ext_types  = wp_get_ext_types();
   2990 	$mime_types = wp_get_mime_types();
   2991 
   2992 	foreach ( $post_mime_types as $group => $labels ) {
   2993 		if ( in_array( $group, array( 'image', 'audio', 'video' ), true ) ) {
   2994 			continue;
   2995 		}
   2996 
   2997 		if ( ! isset( $ext_types[ $group ] ) ) {
   2998 			unset( $post_mime_types[ $group ] );
   2999 			continue;
   3000 		}
   3001 
   3002 		$group_mime_types = array();
   3003 		foreach ( $ext_types[ $group ] as $extension ) {
   3004 			foreach ( $mime_types as $exts => $mime ) {
   3005 				if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
   3006 					$group_mime_types[] = $mime;
   3007 					break;
   3008 				}
   3009 			}
   3010 		}
   3011 		$group_mime_types = implode( ',', array_unique( $group_mime_types ) );
   3012 
   3013 		$post_mime_types[ $group_mime_types ] = $labels;
   3014 		unset( $post_mime_types[ $group ] );
   3015 	}
   3016 
   3017 	/**
   3018 	 * Filters the default list of post mime types.
   3019 	 *
   3020 	 * @since 2.5.0
   3021 	 *
   3022 	 * @param array $post_mime_types Default list of post mime types.
   3023 	 */
   3024 	return apply_filters( 'post_mime_types', $post_mime_types );
   3025 }
   3026 
   3027 /**
   3028  * Check a MIME-Type against a list.
   3029  *
   3030  * If the wildcard_mime_types parameter is a string, it must be comma separated
   3031  * list. If the real_mime_types is a string, it is also comma separated to
   3032  * create the list.
   3033  *
   3034  * @since 2.5.0
   3035  *
   3036  * @param string|string[] $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
   3037  *                                             or flash (same as *flash*).
   3038  * @param string|string[] $real_mime_types     Real post mime type values.
   3039  * @return array array(wildcard=>array(real types)).
   3040  */
   3041 function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
   3042 	$matches = array();
   3043 	if ( is_string( $wildcard_mime_types ) ) {
   3044 		$wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
   3045 	}
   3046 	if ( is_string( $real_mime_types ) ) {
   3047 		$real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
   3048 	}
   3049 
   3050 	$patternses = array();
   3051 	$wild       = '[-._a-z0-9]*';
   3052 
   3053 	foreach ( (array) $wildcard_mime_types as $type ) {
   3054 		$mimes = array_map( 'trim', explode( ',', $type ) );
   3055 		foreach ( $mimes as $mime ) {
   3056 			$regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
   3057 
   3058 			$patternses[][ $type ] = "^$regex$";
   3059 
   3060 			if ( false === strpos( $mime, '/' ) ) {
   3061 				$patternses[][ $type ] = "^$regex/";
   3062 				$patternses[][ $type ] = $regex;
   3063 			}
   3064 		}
   3065 	}
   3066 	asort( $patternses );
   3067 
   3068 	foreach ( $patternses as $patterns ) {
   3069 		foreach ( $patterns as $type => $pattern ) {
   3070 			foreach ( (array) $real_mime_types as $real ) {
   3071 				if ( preg_match( "#$pattern#", $real )
   3072 					&& ( empty( $matches[ $type ] ) || false === array_search( $real, $matches[ $type ], true ) )
   3073 				) {
   3074 					$matches[ $type ][] = $real;
   3075 				}
   3076 			}
   3077 		}
   3078 	}
   3079 
   3080 	return $matches;
   3081 }
   3082 
   3083 /**
   3084  * Convert MIME types into SQL.
   3085  *
   3086  * @since 2.5.0
   3087  *
   3088  * @param string|string[] $post_mime_types List of mime types or comma separated string
   3089  *                                         of mime types.
   3090  * @param string          $table_alias     Optional. Specify a table alias, if needed.
   3091  *                                         Default empty.
   3092  * @return string The SQL AND clause for mime searching.
   3093  */
   3094 function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
   3095 	$where     = '';
   3096 	$wildcards = array( '', '%', '%/%' );
   3097 	if ( is_string( $post_mime_types ) ) {
   3098 		$post_mime_types = array_map( 'trim', explode( ',', $post_mime_types ) );
   3099 	}
   3100 
   3101 	$wheres = array();
   3102 
   3103 	foreach ( (array) $post_mime_types as $mime_type ) {
   3104 		$mime_type = preg_replace( '/\s/', '', $mime_type );
   3105 		$slashpos  = strpos( $mime_type, '/' );
   3106 		if ( false !== $slashpos ) {
   3107 			$mime_group    = preg_replace( '/[^-*.a-zA-Z0-9]/', '', substr( $mime_type, 0, $slashpos ) );
   3108 			$mime_subgroup = preg_replace( '/[^-*.+a-zA-Z0-9]/', '', substr( $mime_type, $slashpos + 1 ) );
   3109 			if ( empty( $mime_subgroup ) ) {
   3110 				$mime_subgroup = '*';
   3111 			} else {
   3112 				$mime_subgroup = str_replace( '/', '', $mime_subgroup );
   3113 			}
   3114 			$mime_pattern = "$mime_group/$mime_subgroup";
   3115 		} else {
   3116 			$mime_pattern = preg_replace( '/[^-*.a-zA-Z0-9]/', '', $mime_type );
   3117 			if ( false === strpos( $mime_pattern, '*' ) ) {
   3118 				$mime_pattern .= '/*';
   3119 			}
   3120 		}
   3121 
   3122 		$mime_pattern = preg_replace( '/\*+/', '%', $mime_pattern );
   3123 
   3124 		if ( in_array( $mime_type, $wildcards, true ) ) {
   3125 			return '';
   3126 		}
   3127 
   3128 		if ( false !== strpos( $mime_pattern, '%' ) ) {
   3129 			$wheres[] = empty( $table_alias ) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
   3130 		} else {
   3131 			$wheres[] = empty( $table_alias ) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
   3132 		}
   3133 	}
   3134 
   3135 	if ( ! empty( $wheres ) ) {
   3136 		$where = ' AND (' . implode( ' OR ', $wheres ) . ') ';
   3137 	}
   3138 
   3139 	return $where;
   3140 }
   3141 
   3142 /**
   3143  * Trash or delete a post or page.
   3144  *
   3145  * When the post and page is permanently deleted, everything that is tied to
   3146  * it is deleted also. This includes comments, post meta fields, and terms
   3147  * associated with the post.
   3148  *
   3149  * The post or page is moved to Trash instead of permanently deleted unless
   3150  * Trash is disabled, item is already in the Trash, or $force_delete is true.
   3151  *
   3152  * @since 1.0.0
   3153  *
   3154  * @global wpdb $wpdb WordPress database abstraction object.
   3155  * @see wp_delete_attachment()
   3156  * @see wp_trash_post()
   3157  *
   3158  * @param int  $postid       Optional. Post ID. Default 0.
   3159  * @param bool $force_delete Optional. Whether to bypass Trash and force deletion.
   3160  *                           Default false.
   3161  * @return WP_Post|false|null Post data on success, false or null on failure.
   3162  */
   3163 function wp_delete_post( $postid = 0, $force_delete = false ) {
   3164 	global $wpdb;
   3165 
   3166 	$post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $postid ) );
   3167 
   3168 	if ( ! $post ) {
   3169 		return $post;
   3170 	}
   3171 
   3172 	$post = get_post( $post );
   3173 
   3174 	if ( ! $force_delete && ( 'post' === $post->post_type || 'page' === $post->post_type ) && 'trash' !== get_post_status( $postid ) && EMPTY_TRASH_DAYS ) {
   3175 		return wp_trash_post( $postid );
   3176 	}
   3177 
   3178 	if ( 'attachment' === $post->post_type ) {
   3179 		return wp_delete_attachment( $postid, $force_delete );
   3180 	}
   3181 
   3182 	/**
   3183 	 * Filters whether a post deletion should take place.
   3184 	 *
   3185 	 * @since 4.4.0
   3186 	 *
   3187 	 * @param bool|null $delete       Whether to go forward with deletion.
   3188 	 * @param WP_Post   $post         Post object.
   3189 	 * @param bool      $force_delete Whether to bypass the Trash.
   3190 	 */
   3191 	$check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
   3192 	if ( null !== $check ) {
   3193 		return $check;
   3194 	}
   3195 
   3196 	/**
   3197 	 * Fires before a post is deleted, at the start of wp_delete_post().
   3198 	 *
   3199 	 * @since 3.2.0
   3200 	 * @since 5.5.0 Added the `$post` parameter.
   3201 	 *
   3202 	 * @see wp_delete_post()
   3203 	 *
   3204 	 * @param int     $postid Post ID.
   3205 	 * @param WP_Post $post   Post object.
   3206 	 */
   3207 	do_action( 'before_delete_post', $postid, $post );
   3208 
   3209 	delete_post_meta( $postid, '_wp_trash_meta_status' );
   3210 	delete_post_meta( $postid, '_wp_trash_meta_time' );
   3211 
   3212 	wp_delete_object_term_relationships( $postid, get_object_taxonomies( $post->post_type ) );
   3213 
   3214 	$parent_data  = array( 'post_parent' => $post->post_parent );
   3215 	$parent_where = array( 'post_parent' => $postid );
   3216 
   3217 	if ( is_post_type_hierarchical( $post->post_type ) ) {
   3218 		// Point children of this page to its parent, also clean the cache of affected children.
   3219 		$children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
   3220 		$children       = $wpdb->get_results( $children_query );
   3221 		if ( $children ) {
   3222 			$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
   3223 		}
   3224 	}
   3225 
   3226 	// Do raw query. wp_get_post_revisions() is filtered.
   3227 	$revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
   3228 	// Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
   3229 	foreach ( $revision_ids as $revision_id ) {
   3230 		wp_delete_post_revision( $revision_id );
   3231 	}
   3232 
   3233 	// Point all attachments to this post up one level.
   3234 	$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
   3235 
   3236 	wp_defer_comment_counting( true );
   3237 
   3238 	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ) );
   3239 	foreach ( $comment_ids as $comment_id ) {
   3240 		wp_delete_comment( $comment_id, true );
   3241 	}
   3242 
   3243 	wp_defer_comment_counting( false );
   3244 
   3245 	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ) );
   3246 	foreach ( $post_meta_ids as $mid ) {
   3247 		delete_metadata_by_mid( 'post', $mid );
   3248 	}
   3249 
   3250 	/**
   3251 	 * Fires immediately before a post is deleted from the database.
   3252 	 *
   3253 	 * @since 1.2.0
   3254 	 * @since 5.5.0 Added the `$post` parameter.
   3255 	 *
   3256 	 * @param int     $postid Post ID.
   3257 	 * @param WP_Post $post   Post object.
   3258 	 */
   3259 	do_action( 'delete_post', $postid, $post );
   3260 
   3261 	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
   3262 	if ( ! $result ) {
   3263 		return false;
   3264 	}
   3265 
   3266 	/**
   3267 	 * Fires immediately after a post is deleted from the database.
   3268 	 *
   3269 	 * @since 2.2.0
   3270 	 * @since 5.5.0 Added the `$post` parameter.
   3271 	 *
   3272 	 * @param int     $postid Post ID.
   3273 	 * @param WP_Post $post   Post object.
   3274 	 */
   3275 	do_action( 'deleted_post', $postid, $post );
   3276 
   3277 	clean_post_cache( $post );
   3278 
   3279 	if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
   3280 		foreach ( $children as $child ) {
   3281 			clean_post_cache( $child );
   3282 		}
   3283 	}
   3284 
   3285 	wp_clear_scheduled_hook( 'publish_future_post', array( $postid ) );
   3286 
   3287 	/**
   3288 	 * Fires after a post is deleted, at the conclusion of wp_delete_post().
   3289 	 *
   3290 	 * @since 3.2.0
   3291 	 * @since 5.5.0 Added the `$post` parameter.
   3292 	 *
   3293 	 * @see wp_delete_post()
   3294 	 *
   3295 	 * @param int     $postid Post ID.
   3296 	 * @param WP_Post $post   Post object.
   3297 	 */
   3298 	do_action( 'after_delete_post', $postid, $post );
   3299 
   3300 	return $post;
   3301 }
   3302 
   3303 /**
   3304  * Reset the page_on_front, show_on_front, and page_for_post settings when
   3305  * a linked page is deleted or trashed.
   3306  *
   3307  * Also ensures the post is no longer sticky.
   3308  *
   3309  * @since 3.7.0
   3310  * @access private
   3311  *
   3312  * @param int $post_id Post ID.
   3313  */
   3314 function _reset_front_page_settings_for_post( $post_id ) {
   3315 	$post = get_post( $post_id );
   3316 
   3317 	if ( 'page' === $post->post_type ) {
   3318 		/*
   3319 		 * If the page is defined in option page_on_front or post_for_posts,
   3320 		 * adjust the corresponding options.
   3321 		 */
   3322 		if ( get_option( 'page_on_front' ) == $post->ID ) {
   3323 			update_option( 'show_on_front', 'posts' );
   3324 			update_option( 'page_on_front', 0 );
   3325 		}
   3326 		if ( get_option( 'page_for_posts' ) == $post->ID ) {
   3327 			update_option( 'page_for_posts', 0 );
   3328 		}
   3329 	}
   3330 
   3331 	unstick_post( $post->ID );
   3332 }
   3333 
   3334 /**
   3335  * Move a post or page to the Trash
   3336  *
   3337  * If Trash is disabled, the post or page is permanently deleted.
   3338  *
   3339  * @since 2.9.0
   3340  *
   3341  * @see wp_delete_post()
   3342  *
   3343  * @param int $post_id Optional. Post ID. Default is ID of the global $post
   3344  *                     if EMPTY_TRASH_DAYS equals true.
   3345  * @return WP_Post|false|null Post data on success, false or null on failure.
   3346  */
   3347 function wp_trash_post( $post_id = 0 ) {
   3348 	if ( ! EMPTY_TRASH_DAYS ) {
   3349 		return wp_delete_post( $post_id, true );
   3350 	}
   3351 
   3352 	$post = get_post( $post_id );
   3353 
   3354 	if ( ! $post ) {
   3355 		return $post;
   3356 	}
   3357 
   3358 	if ( 'trash' === $post->post_status ) {
   3359 		return false;
   3360 	}
   3361 
   3362 	/**
   3363 	 * Filters whether a post trashing should take place.
   3364 	 *
   3365 	 * @since 4.9.0
   3366 	 *
   3367 	 * @param bool|null $trash Whether to go forward with trashing.
   3368 	 * @param WP_Post   $post  Post object.
   3369 	 */
   3370 	$check = apply_filters( 'pre_trash_post', null, $post );
   3371 
   3372 	if ( null !== $check ) {
   3373 		return $check;
   3374 	}
   3375 
   3376 	/**
   3377 	 * Fires before a post is sent to the Trash.
   3378 	 *
   3379 	 * @since 3.3.0
   3380 	 *
   3381 	 * @param int $post_id Post ID.
   3382 	 */
   3383 	do_action( 'wp_trash_post', $post_id );
   3384 
   3385 	add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
   3386 	add_post_meta( $post_id, '_wp_trash_meta_time', time() );
   3387 
   3388 	$post_updated = wp_update_post(
   3389 		array(
   3390 			'ID'          => $post_id,
   3391 			'post_status' => 'trash',
   3392 		)
   3393 	);
   3394 
   3395 	if ( ! $post_updated ) {
   3396 		return false;
   3397 	}
   3398 
   3399 	wp_trash_post_comments( $post_id );
   3400 
   3401 	/**
   3402 	 * Fires after a post is sent to the Trash.
   3403 	 *
   3404 	 * @since 2.9.0
   3405 	 *
   3406 	 * @param int $post_id Post ID.
   3407 	 */
   3408 	do_action( 'trashed_post', $post_id );
   3409 
   3410 	return $post;
   3411 }
   3412 
   3413 /**
   3414  * Restores a post from the Trash.
   3415  *
   3416  * @since 2.9.0
   3417  * @since 5.6.0 An untrashed post is now returned to 'draft' status by default, except for
   3418  *              attachments which are returned to their original 'inherit' status.
   3419  *
   3420  * @param int $post_id Optional. Post ID. Default is ID of the global `$post`.
   3421  * @return WP_Post|false|null Post data on success, false or null on failure.
   3422  */
   3423 function wp_untrash_post( $post_id = 0 ) {
   3424 	$post = get_post( $post_id );
   3425 
   3426 	if ( ! $post ) {
   3427 		return $post;
   3428 	}
   3429 
   3430 	$post_id = $post->ID;
   3431 
   3432 	if ( 'trash' !== $post->post_status ) {
   3433 		return false;
   3434 	}
   3435 
   3436 	$previous_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
   3437 
   3438 	/**
   3439 	 * Filters whether a post untrashing should take place.
   3440 	 *
   3441 	 * @since 4.9.0
   3442 	 * @since 5.6.0 The `$previous_status` parameter was added.
   3443 	 *
   3444 	 * @param bool|null $untrash         Whether to go forward with untrashing.
   3445 	 * @param WP_Post   $post            Post object.
   3446 	 * @param string    $previous_status The status of the post at the point where it was trashed.
   3447 	 */
   3448 	$check = apply_filters( 'pre_untrash_post', null, $post, $previous_status );
   3449 	if ( null !== $check ) {
   3450 		return $check;
   3451 	}
   3452 
   3453 	/**
   3454 	 * Fires before a post is restored from the Trash.
   3455 	 *
   3456 	 * @since 2.9.0
   3457 	 * @since 5.6.0 The `$previous_status` parameter was added.
   3458 	 *
   3459 	 * @param int    $post_id         Post ID.
   3460 	 * @param string $previous_status The status of the post at the point where it was trashed.
   3461 	 */
   3462 	do_action( 'untrash_post', $post_id, $previous_status );
   3463 
   3464 	$new_status = ( 'attachment' === $post->post_type ) ? 'inherit' : 'draft';
   3465 
   3466 	/**
   3467 	 * Filters the status that a post gets assigned when it is restored from the trash (untrashed).
   3468 	 *
   3469 	 * By default posts that are restored will be assigned a status of 'draft'. Return the value of `$previous_status`
   3470 	 * in order to assign the status that the post had before it was trashed. The `wp_untrash_post_set_previous_status()`
   3471 	 * function is available for this.
   3472 	 *
   3473 	 * Prior to WordPress 5.6.0, restored posts were always assigned their original status.
   3474 	 *
   3475 	 * @since 5.6.0
   3476 	 *
   3477 	 * @param string $new_status      The new status of the post being restored.
   3478 	 * @param int    $post_id         The ID of the post being restored.
   3479 	 * @param string $previous_status The status of the post at the point where it was trashed.
   3480 	 */
   3481 	$post_status = apply_filters( 'wp_untrash_post_status', $new_status, $post_id, $previous_status );
   3482 
   3483 	delete_post_meta( $post_id, '_wp_trash_meta_status' );
   3484 	delete_post_meta( $post_id, '_wp_trash_meta_time' );
   3485 
   3486 	$post_updated = wp_update_post(
   3487 		array(
   3488 			'ID'          => $post_id,
   3489 			'post_status' => $post_status,
   3490 		)
   3491 	);
   3492 
   3493 	if ( ! $post_updated ) {
   3494 		return false;
   3495 	}
   3496 
   3497 	wp_untrash_post_comments( $post_id );
   3498 
   3499 	/**
   3500 	 * Fires after a post is restored from the Trash.
   3501 	 *
   3502 	 * @since 2.9.0
   3503 	 * @since 5.6.0 The `$previous_status` parameter was added.
   3504 	 *
   3505 	 * @param int    $post_id         Post ID.
   3506 	 * @param string $previous_status The status of the post at the point where it was trashed.
   3507 	 */
   3508 	do_action( 'untrashed_post', $post_id, $previous_status );
   3509 
   3510 	return $post;
   3511 }
   3512 
   3513 /**
   3514  * Moves comments for a post to the Trash.
   3515  *
   3516  * @since 2.9.0
   3517  *
   3518  * @global wpdb $wpdb WordPress database abstraction object.
   3519  *
   3520  * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
   3521  * @return mixed|void False on failure.
   3522  */
   3523 function wp_trash_post_comments( $post = null ) {
   3524 	global $wpdb;
   3525 
   3526 	$post = get_post( $post );
   3527 
   3528 	if ( ! $post ) {
   3529 		return;
   3530 	}
   3531 
   3532 	$post_id = $post->ID;
   3533 
   3534 	/**
   3535 	 * Fires before comments are sent to the Trash.
   3536 	 *
   3537 	 * @since 2.9.0
   3538 	 *
   3539 	 * @param int $post_id Post ID.
   3540 	 */
   3541 	do_action( 'trash_post_comments', $post_id );
   3542 
   3543 	$comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
   3544 
   3545 	if ( ! $comments ) {
   3546 		return;
   3547 	}
   3548 
   3549 	// Cache current status for each comment.
   3550 	$statuses = array();
   3551 	foreach ( $comments as $comment ) {
   3552 		$statuses[ $comment->comment_ID ] = $comment->comment_approved;
   3553 	}
   3554 	add_post_meta( $post_id, '_wp_trash_meta_comments_status', $statuses );
   3555 
   3556 	// Set status for all comments to post-trashed.
   3557 	$result = $wpdb->update( $wpdb->comments, array( 'comment_approved' => 'post-trashed' ), array( 'comment_post_ID' => $post_id ) );
   3558 
   3559 	clean_comment_cache( array_keys( $statuses ) );
   3560 
   3561 	/**
   3562 	 * Fires after comments are sent to the Trash.
   3563 	 *
   3564 	 * @since 2.9.0
   3565 	 *
   3566 	 * @param int   $post_id  Post ID.
   3567 	 * @param array $statuses Array of comment statuses.
   3568 	 */
   3569 	do_action( 'trashed_post_comments', $post_id, $statuses );
   3570 
   3571 	return $result;
   3572 }
   3573 
   3574 /**
   3575  * Restore comments for a post from the Trash.
   3576  *
   3577  * @since 2.9.0
   3578  *
   3579  * @global wpdb $wpdb WordPress database abstraction object.
   3580  *
   3581  * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
   3582  * @return true|void
   3583  */
   3584 function wp_untrash_post_comments( $post = null ) {
   3585 	global $wpdb;
   3586 
   3587 	$post = get_post( $post );
   3588 
   3589 	if ( ! $post ) {
   3590 		return;
   3591 	}
   3592 
   3593 	$post_id = $post->ID;
   3594 
   3595 	$statuses = get_post_meta( $post_id, '_wp_trash_meta_comments_status', true );
   3596 
   3597 	if ( ! $statuses ) {
   3598 		return true;
   3599 	}
   3600 
   3601 	/**
   3602 	 * Fires before comments are restored for a post from the Trash.
   3603 	 *
   3604 	 * @since 2.9.0
   3605 	 *
   3606 	 * @param int $post_id Post ID.
   3607 	 */
   3608 	do_action( 'untrash_post_comments', $post_id );
   3609 
   3610 	// Restore each comment to its original status.
   3611 	$group_by_status = array();
   3612 	foreach ( $statuses as $comment_id => $comment_status ) {
   3613 		$group_by_status[ $comment_status ][] = $comment_id;
   3614 	}
   3615 
   3616 	foreach ( $group_by_status as $status => $comments ) {
   3617 		// Sanity check. This shouldn't happen.
   3618 		if ( 'post-trashed' === $status ) {
   3619 			$status = '0';
   3620 		}
   3621 		$comments_in = implode( ', ', array_map( 'intval', $comments ) );
   3622 		$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
   3623 	}
   3624 
   3625 	clean_comment_cache( array_keys( $statuses ) );
   3626 
   3627 	delete_post_meta( $post_id, '_wp_trash_meta_comments_status' );
   3628 
   3629 	/**
   3630 	 * Fires after comments are restored for a post from the Trash.
   3631 	 *
   3632 	 * @since 2.9.0
   3633 	 *
   3634 	 * @param int $post_id Post ID.
   3635 	 */
   3636 	do_action( 'untrashed_post_comments', $post_id );
   3637 }
   3638 
   3639 /**
   3640  * Retrieve the list of categories for a post.
   3641  *
   3642  * Compatibility layer for themes and plugins. Also an easy layer of abstraction
   3643  * away from the complexity of the taxonomy layer.
   3644  *
   3645  * @since 2.1.0
   3646  *
   3647  * @see wp_get_object_terms()
   3648  *
   3649  * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
   3650  *                       global $post. Default 0.
   3651  * @param array $args    Optional. Category query parameters. Default empty array.
   3652  *                       See WP_Term_Query::__construct() for supported arguments.
   3653  * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
   3654  *                        'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
   3655  *                        is 'ids', an array of category IDs. If `$fields` is 'names', an array of category names.
   3656  *                        WP_Error object if 'category' taxonomy doesn't exist.
   3657  */
   3658 function wp_get_post_categories( $post_id = 0, $args = array() ) {
   3659 	$post_id = (int) $post_id;
   3660 
   3661 	$defaults = array( 'fields' => 'ids' );
   3662 	$args     = wp_parse_args( $args, $defaults );
   3663 
   3664 	$cats = wp_get_object_terms( $post_id, 'category', $args );
   3665 	return $cats;
   3666 }
   3667 
   3668 /**
   3669  * Retrieve the tags for a post.
   3670  *
   3671  * There is only one default for this function, called 'fields' and by default
   3672  * is set to 'all'. There are other defaults that can be overridden in
   3673  * wp_get_object_terms().
   3674  *
   3675  * @since 2.3.0
   3676  *
   3677  * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
   3678  *                       global $post. Default 0.
   3679  * @param array $args    Optional. Tag query parameters. Default empty array.
   3680  *                       See WP_Term_Query::__construct() for supported arguments.
   3681  * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
   3682  *                        WP_Error object if 'post_tag' taxonomy doesn't exist.
   3683  */
   3684 function wp_get_post_tags( $post_id = 0, $args = array() ) {
   3685 	return wp_get_post_terms( $post_id, 'post_tag', $args );
   3686 }
   3687 
   3688 /**
   3689  * Retrieves the terms for a post.
   3690  *
   3691  * @since 2.8.0
   3692  *
   3693  * @param int             $post_id  Optional. The Post ID. Does not default to the ID of the
   3694  *                                  global $post. Default 0.
   3695  * @param string|string[] $taxonomy Optional. The taxonomy slug or array of slugs for which
   3696  *                                  to retrieve terms. Default 'post_tag'.
   3697  * @param array           $args     {
   3698  *     Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
   3699  *
   3700  *     @type string $fields Term fields to retrieve. Default 'all'.
   3701  * }
   3702  * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
   3703  *                        WP_Error object if `$taxonomy` doesn't exist.
   3704  */
   3705 function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
   3706 	$post_id = (int) $post_id;
   3707 
   3708 	$defaults = array( 'fields' => 'all' );
   3709 	$args     = wp_parse_args( $args, $defaults );
   3710 
   3711 	$tags = wp_get_object_terms( $post_id, $taxonomy, $args );
   3712 
   3713 	return $tags;
   3714 }
   3715 
   3716 /**
   3717  * Retrieve a number of recent posts.
   3718  *
   3719  * @since 1.0.0
   3720  *
   3721  * @see get_posts()
   3722  *
   3723  * @param array  $args   Optional. Arguments to retrieve posts. Default empty array.
   3724  * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which
   3725  *                       correspond to a WP_Post object or an associative array, respectively.
   3726  *                       Default ARRAY_A.
   3727  * @return array|false Array of recent posts, where the type of each element is determined
   3728  *                     by the `$output` parameter. Empty array on failure.
   3729  */
   3730 function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
   3731 
   3732 	if ( is_numeric( $args ) ) {
   3733 		_deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
   3734 		$args = array( 'numberposts' => absint( $args ) );
   3735 	}
   3736 
   3737 	// Set default arguments.
   3738 	$defaults = array(
   3739 		'numberposts'      => 10,
   3740 		'offset'           => 0,
   3741 		'category'         => 0,
   3742 		'orderby'          => 'post_date',
   3743 		'order'            => 'DESC',
   3744 		'include'          => '',
   3745 		'exclude'          => '',
   3746 		'meta_key'         => '',
   3747 		'meta_value'       => '',
   3748 		'post_type'        => 'post',
   3749 		'post_status'      => 'draft, publish, future, pending, private',
   3750 		'suppress_filters' => true,
   3751 	);
   3752 
   3753 	$parsed_args = wp_parse_args( $args, $defaults );
   3754 
   3755 	$results = get_posts( $parsed_args );
   3756 
   3757 	// Backward compatibility. Prior to 3.1 expected posts to be returned in array.
   3758 	if ( ARRAY_A === $output ) {
   3759 		foreach ( $results as $key => $result ) {
   3760 			$results[ $key ] = get_object_vars( $result );
   3761 		}
   3762 		return $results ? $results : array();
   3763 	}
   3764 
   3765 	return $results ? $results : false;
   3766 
   3767 }
   3768 
   3769 /**
   3770  * Insert or update a post.
   3771  *
   3772  * If the $postarr parameter has 'ID' set to a value, then post will be updated.
   3773  *
   3774  * You can set the post date manually, by setting the values for 'post_date'
   3775  * and 'post_date_gmt' keys. You can close the comments or open the comments by
   3776  * setting the value for 'comment_status' key.
   3777  *
   3778  * @since 1.0.0
   3779  * @since 2.6.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
   3780  * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
   3781  * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
   3782  * @since 5.6.0 Added the `$fire_after_hooks` parameter.
   3783  *
   3784  * @see sanitize_post()
   3785  * @global wpdb $wpdb WordPress database abstraction object.
   3786  *
   3787  * @param array $postarr {
   3788  *     An array of elements that make up a post to update or insert.
   3789  *
   3790  *     @type int    $ID                    The post ID. If equal to something other than 0,
   3791  *                                         the post with that ID will be updated. Default 0.
   3792  *     @type int    $post_author           The ID of the user who added the post. Default is
   3793  *                                         the current user ID.
   3794  *     @type string $post_date             The date of the post. Default is the current time.
   3795  *     @type string $post_date_gmt         The date of the post in the GMT timezone. Default is
   3796  *                                         the value of `$post_date`.
   3797  *     @type mixed  $post_content          The post content. Default empty.
   3798  *     @type string $post_content_filtered The filtered post content. Default empty.
   3799  *     @type string $post_title            The post title. Default empty.
   3800  *     @type string $post_excerpt          The post excerpt. Default empty.
   3801  *     @type string $post_status           The post status. Default 'draft'.
   3802  *     @type string $post_type             The post type. Default 'post'.
   3803  *     @type string $comment_status        Whether the post can accept comments. Accepts 'open' or 'closed'.
   3804  *                                         Default is the value of 'default_comment_status' option.
   3805  *     @type string $ping_status           Whether the post can accept pings. Accepts 'open' or 'closed'.
   3806  *                                         Default is the value of 'default_ping_status' option.
   3807  *     @type string $post_password         The password to access the post. Default empty.
   3808  *     @type string $post_name             The post name. Default is the sanitized post title
   3809  *                                         when creating a new post.
   3810  *     @type string $to_ping               Space or carriage return-separated list of URLs to ping.
   3811  *                                         Default empty.
   3812  *     @type string $pinged                Space or carriage return-separated list of URLs that have
   3813  *                                         been pinged. Default empty.
   3814  *     @type string $post_modified         The date when the post was last modified. Default is
   3815  *                                         the current time.
   3816  *     @type string $post_modified_gmt     The date when the post was last modified in the GMT
   3817  *                                         timezone. Default is the current time.
   3818  *     @type int    $post_parent           Set this for the post it belongs to, if any. Default 0.
   3819  *     @type int    $menu_order            The order the post should be displayed in. Default 0.
   3820  *     @type string $post_mime_type        The mime type of the post. Default empty.
   3821  *     @type string $guid                  Global Unique ID for referencing the post. Default empty.
   3822  *     @type int    $import_id             The post ID to be used when inserting a new post.
   3823  *                                         If specified, must not match any existing post ID. Default 0.
   3824  *     @type int[]  $post_category         Array of category IDs.
   3825  *                                         Defaults to value of the 'default_category' option.
   3826  *     @type array  $tags_input            Array of tag names, slugs, or IDs. Default empty.
   3827  *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
   3828  *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
   3829  * }
   3830  * @param bool  $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
   3831  * @param bool  $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
   3832  * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
   3833  */
   3834 function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) {
   3835 	global $wpdb;
   3836 
   3837 	// Capture original pre-sanitized array for passing into filters.
   3838 	$unsanitized_postarr = $postarr;
   3839 
   3840 	$user_id = get_current_user_id();
   3841 
   3842 	$defaults = array(
   3843 		'post_author'           => $user_id,
   3844 		'post_content'          => '',
   3845 		'post_content_filtered' => '',
   3846 		'post_title'            => '',
   3847 		'post_excerpt'          => '',
   3848 		'post_status'           => 'draft',
   3849 		'post_type'             => 'post',
   3850 		'comment_status'        => '',
   3851 		'ping_status'           => '',
   3852 		'post_password'         => '',
   3853 		'to_ping'               => '',
   3854 		'pinged'                => '',
   3855 		'post_parent'           => 0,
   3856 		'menu_order'            => 0,
   3857 		'guid'                  => '',
   3858 		'import_id'             => 0,
   3859 		'context'               => '',
   3860 		'post_date'             => '',
   3861 		'post_date_gmt'         => '',
   3862 	);
   3863 
   3864 	$postarr = wp_parse_args( $postarr, $defaults );
   3865 
   3866 	unset( $postarr['filter'] );
   3867 
   3868 	$postarr = sanitize_post( $postarr, 'db' );
   3869 
   3870 	// Are we updating or creating?
   3871 	$post_ID = 0;
   3872 	$update  = false;
   3873 	$guid    = $postarr['guid'];
   3874 
   3875 	if ( ! empty( $postarr['ID'] ) ) {
   3876 		$update = true;
   3877 
   3878 		// Get the post ID and GUID.
   3879 		$post_ID     = $postarr['ID'];
   3880 		$post_before = get_post( $post_ID );
   3881 
   3882 		if ( is_null( $post_before ) ) {
   3883 			if ( $wp_error ) {
   3884 				return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
   3885 			}
   3886 			return 0;
   3887 		}
   3888 
   3889 		$guid            = get_post_field( 'guid', $post_ID );
   3890 		$previous_status = get_post_field( 'post_status', $post_ID );
   3891 	} else {
   3892 		$previous_status = 'new';
   3893 		$post_before     = null;
   3894 	}
   3895 
   3896 	$post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
   3897 
   3898 	$post_title   = $postarr['post_title'];
   3899 	$post_content = $postarr['post_content'];
   3900 	$post_excerpt = $postarr['post_excerpt'];
   3901 
   3902 	if ( isset( $postarr['post_name'] ) ) {
   3903 		$post_name = $postarr['post_name'];
   3904 	} elseif ( $update ) {
   3905 		// For an update, don't modify the post_name if it wasn't supplied as an argument.
   3906 		$post_name = $post_before->post_name;
   3907 	}
   3908 
   3909 	$maybe_empty = 'attachment' !== $post_type
   3910 		&& ! $post_content && ! $post_title && ! $post_excerpt
   3911 		&& post_type_supports( $post_type, 'editor' )
   3912 		&& post_type_supports( $post_type, 'title' )
   3913 		&& post_type_supports( $post_type, 'excerpt' );
   3914 
   3915 	/**
   3916 	 * Filters whether the post should be considered "empty".
   3917 	 *
   3918 	 * The post is considered "empty" if both:
   3919 	 * 1. The post type supports the title, editor, and excerpt fields
   3920 	 * 2. The title, editor, and excerpt fields are all empty
   3921 	 *
   3922 	 * Returning a truthy value from the filter will effectively short-circuit
   3923 	 * the new post being inserted and return 0. If $wp_error is true, a WP_Error
   3924 	 * will be returned instead.
   3925 	 *
   3926 	 * @since 3.3.0
   3927 	 *
   3928 	 * @param bool  $maybe_empty Whether the post should be considered "empty".
   3929 	 * @param array $postarr     Array of post data.
   3930 	 */
   3931 	if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
   3932 		if ( $wp_error ) {
   3933 			return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
   3934 		} else {
   3935 			return 0;
   3936 		}
   3937 	}
   3938 
   3939 	$post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
   3940 
   3941 	if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
   3942 		$post_status = 'inherit';
   3943 	}
   3944 
   3945 	if ( ! empty( $postarr['post_category'] ) ) {
   3946 		// Filter out empty terms.
   3947 		$post_category = array_filter( $postarr['post_category'] );
   3948 	}
   3949 
   3950 	// Make sure we set a valid category.
   3951 	if ( empty( $post_category ) || 0 === count( $post_category ) || ! is_array( $post_category ) ) {
   3952 		// 'post' requires at least one category.
   3953 		if ( 'post' === $post_type && 'auto-draft' !== $post_status ) {
   3954 			$post_category = array( get_option( 'default_category' ) );
   3955 		} else {
   3956 			$post_category = array();
   3957 		}
   3958 	}
   3959 
   3960 	/*
   3961 	 * Don't allow contributors to set the post slug for pending review posts.
   3962 	 *
   3963 	 * For new posts check the primitive capability, for updates check the meta capability.
   3964 	 */
   3965 	$post_type_object = get_post_type_object( $post_type );
   3966 
   3967 	if ( ! $update && 'pending' === $post_status && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
   3968 		$post_name = '';
   3969 	} elseif ( $update && 'pending' === $post_status && ! current_user_can( 'publish_post', $post_ID ) ) {
   3970 		$post_name = '';
   3971 	}
   3972 
   3973 	/*
   3974 	 * Create a valid post name. Drafts and pending posts are allowed to have
   3975 	 * an empty post name.
   3976 	 */
   3977 	if ( empty( $post_name ) ) {
   3978 		if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
   3979 			$post_name = sanitize_title( $post_title );
   3980 		} else {
   3981 			$post_name = '';
   3982 		}
   3983 	} else {
   3984 		// On updates, we need to check to see if it's using the old, fixed sanitization context.
   3985 		$check_name = sanitize_title( $post_name, '', 'old-save' );
   3986 
   3987 		if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
   3988 			$post_name = $check_name;
   3989 		} else { // new post, or slug has changed.
   3990 			$post_name = sanitize_title( $post_name );
   3991 		}
   3992 	}
   3993 
   3994 	/*
   3995 	 * Resolve the post date from any provided post date or post date GMT strings;
   3996 	 * if none are provided, the date will be set to now.
   3997 	 */
   3998 	$post_date = wp_resolve_post_date( $postarr['post_date'], $postarr['post_date_gmt'] );
   3999 	if ( ! $post_date ) {
   4000 		if ( $wp_error ) {
   4001 			return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
   4002 		} else {
   4003 			return 0;
   4004 		}
   4005 	}
   4006 
   4007 	if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' === $postarr['post_date_gmt'] ) {
   4008 		if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
   4009 			$post_date_gmt = get_gmt_from_date( $post_date );
   4010 		} else {
   4011 			$post_date_gmt = '0000-00-00 00:00:00';
   4012 		}
   4013 	} else {
   4014 		$post_date_gmt = $postarr['post_date_gmt'];
   4015 	}
   4016 
   4017 	if ( $update || '0000-00-00 00:00:00' === $post_date ) {
   4018 		$post_modified     = current_time( 'mysql' );
   4019 		$post_modified_gmt = current_time( 'mysql', 1 );
   4020 	} else {
   4021 		$post_modified     = $post_date;
   4022 		$post_modified_gmt = $post_date_gmt;
   4023 	}
   4024 
   4025 	if ( 'attachment' !== $post_type ) {
   4026 		$now = gmdate( 'Y-m-d H:i:s' );
   4027 
   4028 		if ( 'publish' === $post_status ) {
   4029 			if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
   4030 				$post_status = 'future';
   4031 			}
   4032 		} elseif ( 'future' === $post_status ) {
   4033 			if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
   4034 				$post_status = 'publish';
   4035 			}
   4036 		}
   4037 	}
   4038 
   4039 	// Comment status.
   4040 	if ( empty( $postarr['comment_status'] ) ) {
   4041 		if ( $update ) {
   4042 			$comment_status = 'closed';
   4043 		} else {
   4044 			$comment_status = get_default_comment_status( $post_type );
   4045 		}
   4046 	} else {
   4047 		$comment_status = $postarr['comment_status'];
   4048 	}
   4049 
   4050 	// These variables are needed by compact() later.
   4051 	$post_content_filtered = $postarr['post_content_filtered'];
   4052 	$post_author           = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
   4053 	$ping_status           = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
   4054 	$to_ping               = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
   4055 	$pinged                = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
   4056 	$import_id             = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
   4057 
   4058 	/*
   4059 	 * The 'wp_insert_post_parent' filter expects all variables to be present.
   4060 	 * Previously, these variables would have already been extracted
   4061 	 */
   4062 	if ( isset( $postarr['menu_order'] ) ) {
   4063 		$menu_order = (int) $postarr['menu_order'];
   4064 	} else {
   4065 		$menu_order = 0;
   4066 	}
   4067 
   4068 	$post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
   4069 	if ( 'private' === $post_status ) {
   4070 		$post_password = '';
   4071 	}
   4072 
   4073 	if ( isset( $postarr['post_parent'] ) ) {
   4074 		$post_parent = (int) $postarr['post_parent'];
   4075 	} else {
   4076 		$post_parent = 0;
   4077 	}
   4078 
   4079 	$new_postarr = array_merge(
   4080 		array(
   4081 			'ID' => $post_ID,
   4082 		),
   4083 		compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
   4084 	);
   4085 
   4086 	/**
   4087 	 * Filters the post parent -- used to check for and prevent hierarchy loops.
   4088 	 *
   4089 	 * @since 3.1.0
   4090 	 *
   4091 	 * @param int   $post_parent Post parent ID.
   4092 	 * @param int   $post_ID     Post ID.
   4093 	 * @param array $new_postarr Array of parsed post data.
   4094 	 * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
   4095 	 */
   4096 	$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
   4097 
   4098 	/*
   4099 	 * If the post is being untrashed and it has a desired slug stored in post meta,
   4100 	 * reassign it.
   4101 	 */
   4102 	if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
   4103 		$desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
   4104 
   4105 		if ( $desired_post_slug ) {
   4106 			delete_post_meta( $post_ID, '_wp_desired_post_slug' );
   4107 			$post_name = $desired_post_slug;
   4108 		}
   4109 	}
   4110 
   4111 	// If a trashed post has the desired slug, change it and let this post have it.
   4112 	if ( 'trash' !== $post_status && $post_name ) {
   4113 		/**
   4114 		 * Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
   4115 		 *
   4116 		 * @since 5.4.0
   4117 		 *
   4118 		 * @param bool   $add_trashed_suffix Whether to attempt to add the suffix.
   4119 		 * @param string $post_name          The name of the post being updated.
   4120 		 * @param int    $post_ID            Post ID.
   4121 		 */
   4122 		$add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_ID );
   4123 
   4124 		if ( $add_trashed_suffix ) {
   4125 			wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
   4126 		}
   4127 	}
   4128 
   4129 	// When trashing an existing post, change its slug to allow non-trashed posts to use it.
   4130 	if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
   4131 		$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
   4132 	}
   4133 
   4134 	$post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
   4135 
   4136 	// Don't unslash.
   4137 	$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
   4138 
   4139 	// Expected_slashed (everything!).
   4140 	$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' );
   4141 
   4142 	$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
   4143 
   4144 	foreach ( $emoji_fields as $emoji_field ) {
   4145 		if ( isset( $data[ $emoji_field ] ) ) {
   4146 			$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
   4147 
   4148 			if ( 'utf8' === $charset ) {
   4149 				$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
   4150 			}
   4151 		}
   4152 	}
   4153 
   4154 	if ( 'attachment' === $post_type ) {
   4155 		/**
   4156 		 * Filters attachment post data before it is updated in or added to the database.
   4157 		 *
   4158 		 * @since 3.9.0
   4159 		 * @since 5.4.1 `$unsanitized_postarr` argument added.
   4160 		 *
   4161 		 * @param array $data                An array of slashed, sanitized, and processed attachment post data.
   4162 		 * @param array $postarr             An array of slashed and sanitized attachment post data, but not processed.
   4163 		 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
   4164 		 *                                   as originally passed to wp_insert_post().
   4165 		 */
   4166 		$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr );
   4167 	} else {
   4168 		/**
   4169 		 * Filters slashed post data just before it is inserted into the database.
   4170 		 *
   4171 		 * @since 2.7.0
   4172 		 * @since 5.4.1 `$unsanitized_postarr` argument added.
   4173 		 *
   4174 		 * @param array $data                An array of slashed, sanitized, and processed post data.
   4175 		 * @param array $postarr             An array of sanitized (and slashed) but otherwise unmodified post data.
   4176 		 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
   4177 		 *                                   originally passed to wp_insert_post().
   4178 		 */
   4179 		$data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr );
   4180 	}
   4181 
   4182 	$data  = wp_unslash( $data );
   4183 	$where = array( 'ID' => $post_ID );
   4184 
   4185 	if ( $update ) {
   4186 		/**
   4187 		 * Fires immediately before an existing post is updated in the database.
   4188 		 *
   4189 		 * @since 2.5.0
   4190 		 *
   4191 		 * @param int   $post_ID Post ID.
   4192 		 * @param array $data    Array of unslashed post data.
   4193 		 */
   4194 		do_action( 'pre_post_update', $post_ID, $data );
   4195 
   4196 		if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
   4197 			if ( $wp_error ) {
   4198 				if ( 'attachment' === $post_type ) {
   4199 					$message = __( 'Could not update attachment in the database.' );
   4200 				} else {
   4201 					$message = __( 'Could not update post in the database.' );
   4202 				}
   4203 
   4204 				return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
   4205 			} else {
   4206 				return 0;
   4207 			}
   4208 		}
   4209 	} else {
   4210 		// If there is a suggested ID, use it if not already present.
   4211 		if ( ! empty( $import_id ) ) {
   4212 			$import_id = (int) $import_id;
   4213 
   4214 			if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
   4215 				$data['ID'] = $import_id;
   4216 			}
   4217 		}
   4218 
   4219 		if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
   4220 			if ( $wp_error ) {
   4221 				if ( 'attachment' === $post_type ) {
   4222 					$message = __( 'Could not insert attachment into the database.' );
   4223 				} else {
   4224 					$message = __( 'Could not insert post into the database.' );
   4225 				}
   4226 
   4227 				return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
   4228 			} else {
   4229 				return 0;
   4230 			}
   4231 		}
   4232 
   4233 		$post_ID = (int) $wpdb->insert_id;
   4234 
   4235 		// Use the newly generated $post_ID.
   4236 		$where = array( 'ID' => $post_ID );
   4237 	}
   4238 
   4239 	if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
   4240 		$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
   4241 
   4242 		$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
   4243 		clean_post_cache( $post_ID );
   4244 	}
   4245 
   4246 	if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
   4247 		wp_set_post_categories( $post_ID, $post_category );
   4248 	}
   4249 
   4250 	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
   4251 		wp_set_post_tags( $post_ID, $postarr['tags_input'] );
   4252 	}
   4253 
   4254 	// Add default term for all associated custom taxonomies.
   4255 	if ( 'auto-draft' !== $post_status ) {
   4256 		foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {
   4257 
   4258 			if ( ! empty( $tax_object->default_term ) ) {
   4259 
   4260 				// Filter out empty terms.
   4261 				if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
   4262 					$postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
   4263 				}
   4264 
   4265 				// Passed custom taxonomy list overwrites the existing list if not empty.
   4266 				$terms = wp_get_object_terms( $post_ID, $taxonomy, array( 'fields' => 'ids' ) );
   4267 				if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
   4268 					$postarr['tax_input'][ $taxonomy ] = $terms;
   4269 				}
   4270 
   4271 				if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
   4272 					$default_term_id = get_option( 'default_term_' . $taxonomy );
   4273 					if ( ! empty( $default_term_id ) ) {
   4274 						$postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
   4275 					}
   4276 				}
   4277 			}
   4278 		}
   4279 	}
   4280 
   4281 	// New-style support for all custom taxonomies.
   4282 	if ( ! empty( $postarr['tax_input'] ) ) {
   4283 		foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
   4284 			$taxonomy_obj = get_taxonomy( $taxonomy );
   4285 
   4286 			if ( ! $taxonomy_obj ) {
   4287 				/* translators: %s: Taxonomy name. */
   4288 				_doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
   4289 				continue;
   4290 			}
   4291 
   4292 			// array = hierarchical, string = non-hierarchical.
   4293 			if ( is_array( $tags ) ) {
   4294 				$tags = array_filter( $tags );
   4295 			}
   4296 
   4297 			if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
   4298 				wp_set_post_terms( $post_ID, $tags, $taxonomy );
   4299 			}
   4300 		}
   4301 	}
   4302 
   4303 	if ( ! empty( $postarr['meta_input'] ) ) {
   4304 		foreach ( $postarr['meta_input'] as $field => $value ) {
   4305 			update_post_meta( $post_ID, $field, $value );
   4306 		}
   4307 	}
   4308 
   4309 	$current_guid = get_post_field( 'guid', $post_ID );
   4310 
   4311 	// Set GUID.
   4312 	if ( ! $update && '' === $current_guid ) {
   4313 		$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
   4314 	}
   4315 
   4316 	if ( 'attachment' === $postarr['post_type'] ) {
   4317 		if ( ! empty( $postarr['file'] ) ) {
   4318 			update_attached_file( $post_ID, $postarr['file'] );
   4319 		}
   4320 
   4321 		if ( ! empty( $postarr['context'] ) ) {
   4322 			add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
   4323 		}
   4324 	}
   4325 
   4326 	// Set or remove featured image.
   4327 	if ( isset( $postarr['_thumbnail_id'] ) ) {
   4328 		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
   4329 
   4330 		if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
   4331 			if ( wp_attachment_is( 'audio', $post_ID ) ) {
   4332 				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
   4333 			} elseif ( wp_attachment_is( 'video', $post_ID ) ) {
   4334 				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
   4335 			}
   4336 		}
   4337 
   4338 		if ( $thumbnail_support ) {
   4339 			$thumbnail_id = (int) $postarr['_thumbnail_id'];
   4340 			if ( -1 === $thumbnail_id ) {
   4341 				delete_post_thumbnail( $post_ID );
   4342 			} else {
   4343 				set_post_thumbnail( $post_ID, $thumbnail_id );
   4344 			}
   4345 		}
   4346 	}
   4347 
   4348 	clean_post_cache( $post_ID );
   4349 
   4350 	$post = get_post( $post_ID );
   4351 
   4352 	if ( ! empty( $postarr['page_template'] ) ) {
   4353 		$post->page_template = $postarr['page_template'];
   4354 		$page_templates      = wp_get_theme()->get_page_templates( $post );
   4355 
   4356 		if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
   4357 			if ( $wp_error ) {
   4358 				return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
   4359 			}
   4360 
   4361 			update_post_meta( $post_ID, '_wp_page_template', 'default' );
   4362 		} else {
   4363 			update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
   4364 		}
   4365 	}
   4366 
   4367 	if ( 'attachment' !== $postarr['post_type'] ) {
   4368 		wp_transition_post_status( $data['post_status'], $previous_status, $post );
   4369 	} else {
   4370 		if ( $update ) {
   4371 			/**
   4372 			 * Fires once an existing attachment has been updated.
   4373 			 *
   4374 			 * @since 2.0.0
   4375 			 *
   4376 			 * @param int $post_ID Attachment ID.
   4377 			 */
   4378 			do_action( 'edit_attachment', $post_ID );
   4379 
   4380 			$post_after = get_post( $post_ID );
   4381 
   4382 			/**
   4383 			 * Fires once an existing attachment has been updated.
   4384 			 *
   4385 			 * @since 4.4.0
   4386 			 *
   4387 			 * @param int     $post_ID      Post ID.
   4388 			 * @param WP_Post $post_after   Post object following the update.
   4389 			 * @param WP_Post $post_before  Post object before the update.
   4390 			 */
   4391 			do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
   4392 		} else {
   4393 
   4394 			/**
   4395 			 * Fires once an attachment has been added.
   4396 			 *
   4397 			 * @since 2.0.0
   4398 			 *
   4399 			 * @param int $post_ID Attachment ID.
   4400 			 */
   4401 			do_action( 'add_attachment', $post_ID );
   4402 		}
   4403 
   4404 		return $post_ID;
   4405 	}
   4406 
   4407 	if ( $update ) {
   4408 		/**
   4409 		 * Fires once an existing post has been updated.
   4410 		 *
   4411 		 * The dynamic portion of the hook name, `$post->post_type`, refers to
   4412 		 * the post type slug.
   4413 		 *
   4414 		 * @since 5.1.0
   4415 		 *
   4416 		 * @param int     $post_ID Post ID.
   4417 		 * @param WP_Post $post    Post object.
   4418 		 */
   4419 		do_action( "edit_post_{$post->post_type}", $post_ID, $post );
   4420 
   4421 		/**
   4422 		 * Fires once an existing post has been updated.
   4423 		 *
   4424 		 * @since 1.2.0
   4425 		 *
   4426 		 * @param int     $post_ID Post ID.
   4427 		 * @param WP_Post $post    Post object.
   4428 		 */
   4429 		do_action( 'edit_post', $post_ID, $post );
   4430 
   4431 		$post_after = get_post( $post_ID );
   4432 
   4433 		/**
   4434 		 * Fires once an existing post has been updated.
   4435 		 *
   4436 		 * @since 3.0.0
   4437 		 *
   4438 		 * @param int     $post_ID      Post ID.
   4439 		 * @param WP_Post $post_after   Post object following the update.
   4440 		 * @param WP_Post $post_before  Post object before the update.
   4441 		 */
   4442 		do_action( 'post_updated', $post_ID, $post_after, $post_before );
   4443 	}
   4444 
   4445 	/**
   4446 	 * Fires once a post has been saved.
   4447 	 *
   4448 	 * The dynamic portion of the hook name, `$post->post_type`, refers to
   4449 	 * the post type slug.
   4450 	 *
   4451 	 * @since 3.7.0
   4452 	 *
   4453 	 * @param int     $post_ID Post ID.
   4454 	 * @param WP_Post $post    Post object.
   4455 	 * @param bool    $update  Whether this is an existing post being updated.
   4456 	 */
   4457 	do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
   4458 
   4459 	/**
   4460 	 * Fires once a post has been saved.
   4461 	 *
   4462 	 * @since 1.5.0
   4463 	 *
   4464 	 * @param int     $post_ID Post ID.
   4465 	 * @param WP_Post $post    Post object.
   4466 	 * @param bool    $update  Whether this is an existing post being updated.
   4467 	 */
   4468 	do_action( 'save_post', $post_ID, $post, $update );
   4469 
   4470 	/**
   4471 	 * Fires once a post has been saved.
   4472 	 *
   4473 	 * @since 2.0.0
   4474 	 *
   4475 	 * @param int     $post_ID Post ID.
   4476 	 * @param WP_Post $post    Post object.
   4477 	 * @param bool    $update  Whether this is an existing post being updated.
   4478 	 */
   4479 	do_action( 'wp_insert_post', $post_ID, $post, $update );
   4480 
   4481 	if ( $fire_after_hooks ) {
   4482 		wp_after_insert_post( $post, $update, $post_before );
   4483 	}
   4484 
   4485 	return $post_ID;
   4486 }
   4487 
   4488 /**
   4489  * Update a post with new post data.
   4490  *
   4491  * The date does not have to be set for drafts. You can set the date and it will
   4492  * not be overridden.
   4493  *
   4494  * @since 1.0.0
   4495  * @since 3.5.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
   4496  * @since 5.6.0 Added the `$fire_after_hooks` parameter.
   4497  *
   4498  * @param array|object $postarr          Optional. Post data. Arrays are expected to be escaped,
   4499  *                                       objects are not. See wp_insert_post() for accepted arguments.
   4500  *                                       Default array.
   4501  * @param bool         $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
   4502  * @param bool         $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
   4503  * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
   4504  */
   4505 function wp_update_post( $postarr = array(), $wp_error = false, $fire_after_hooks = true ) {
   4506 	if ( is_object( $postarr ) ) {
   4507 		// Non-escaped post was passed.
   4508 		$postarr = get_object_vars( $postarr );
   4509 		$postarr = wp_slash( $postarr );
   4510 	}
   4511 
   4512 	// First, get all of the original fields.
   4513 	$post = get_post( $postarr['ID'], ARRAY_A );
   4514 
   4515 	if ( is_null( $post ) ) {
   4516 		if ( $wp_error ) {
   4517 			return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
   4518 		}
   4519 		return 0;
   4520 	}
   4521 
   4522 	// Escape data pulled from DB.
   4523 	$post = wp_slash( $post );
   4524 
   4525 	// Passed post category list overwrites existing category list if not empty.
   4526 	if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
   4527 		&& count( $postarr['post_category'] ) > 0
   4528 	) {
   4529 		$post_cats = $postarr['post_category'];
   4530 	} else {
   4531 		$post_cats = $post['post_category'];
   4532 	}
   4533 
   4534 	// Drafts shouldn't be assigned a date unless explicitly done so by the user.
   4535 	if ( isset( $post['post_status'] )
   4536 		&& in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ), true )
   4537 		&& empty( $postarr['edit_date'] ) && ( '0000-00-00 00:00:00' === $post['post_date_gmt'] )
   4538 	) {
   4539 		$clear_date = true;
   4540 	} else {
   4541 		$clear_date = false;
   4542 	}
   4543 
   4544 	// Merge old and new fields with new fields overwriting old ones.
   4545 	$postarr                  = array_merge( $post, $postarr );
   4546 	$postarr['post_category'] = $post_cats;
   4547 	if ( $clear_date ) {
   4548 		$postarr['post_date']     = current_time( 'mysql' );
   4549 		$postarr['post_date_gmt'] = '';
   4550 	}
   4551 
   4552 	if ( 'attachment' === $postarr['post_type'] ) {
   4553 		return wp_insert_attachment( $postarr, false, 0, $wp_error );
   4554 	}
   4555 
   4556 	// Discard 'tags_input' parameter if it's the same as existing post tags.
   4557 	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $postarr['post_type'], 'post_tag' ) ) {
   4558 		$tags      = get_the_terms( $postarr['ID'], 'post_tag' );
   4559 		$tag_names = array();
   4560 
   4561 		if ( $tags && ! is_wp_error( $tags ) ) {
   4562 			$tag_names = wp_list_pluck( $tags, 'name' );
   4563 		}
   4564 
   4565 		if ( $postarr['tags_input'] === $tag_names ) {
   4566 			unset( $postarr['tags_input'] );
   4567 		}
   4568 	}
   4569 
   4570 	return wp_insert_post( $postarr, $wp_error, $fire_after_hooks );
   4571 }
   4572 
   4573 /**
   4574  * Publish a post by transitioning the post status.
   4575  *
   4576  * @since 2.1.0
   4577  *
   4578  * @global wpdb $wpdb WordPress database abstraction object.
   4579  *
   4580  * @param int|WP_Post $post Post ID or post object.
   4581  */
   4582 function wp_publish_post( $post ) {
   4583 	global $wpdb;
   4584 
   4585 	$post = get_post( $post );
   4586 
   4587 	if ( ! $post ) {
   4588 		return;
   4589 	}
   4590 
   4591 	if ( 'publish' === $post->post_status ) {
   4592 		return;
   4593 	}
   4594 
   4595 	$post_before = get_post( $post->ID );
   4596 
   4597 	// Ensure at least one term is applied for taxonomies with a default term.
   4598 	foreach ( get_object_taxonomies( $post->post_type, 'object' ) as $taxonomy => $tax_object ) {
   4599 		// Skip taxonomy if no default term is set.
   4600 		if (
   4601 			'category' !== $taxonomy &&
   4602 			empty( $tax_object->default_term )
   4603 		) {
   4604 			continue;
   4605 		}
   4606 
   4607 		// Do not modify previously set terms.
   4608 		if ( ! empty( get_the_terms( $post, $taxonomy ) ) ) {
   4609 			continue;
   4610 		}
   4611 
   4612 		if ( 'category' === $taxonomy ) {
   4613 			$default_term_id = (int) get_option( 'default_category', 0 );
   4614 		} else {
   4615 			$default_term_id = (int) get_option( 'default_term_' . $taxonomy, 0 );
   4616 		}
   4617 
   4618 		if ( ! $default_term_id ) {
   4619 			continue;
   4620 		}
   4621 		wp_set_post_terms( $post->ID, array( $default_term_id ), $taxonomy );
   4622 	}
   4623 
   4624 	$wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
   4625 
   4626 	clean_post_cache( $post->ID );
   4627 
   4628 	$old_status        = $post->post_status;
   4629 	$post->post_status = 'publish';
   4630 	wp_transition_post_status( 'publish', $old_status, $post );
   4631 
   4632 	/** This action is documented in wp-includes/post.php */
   4633 	do_action( "edit_post_{$post->post_type}", $post->ID, $post );
   4634 
   4635 	/** This action is documented in wp-includes/post.php */
   4636 	do_action( 'edit_post', $post->ID, $post );
   4637 
   4638 	/** This action is documented in wp-includes/post.php */
   4639 	do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
   4640 
   4641 	/** This action is documented in wp-includes/post.php */
   4642 	do_action( 'save_post', $post->ID, $post, true );
   4643 
   4644 	/** This action is documented in wp-includes/post.php */
   4645 	do_action( 'wp_insert_post', $post->ID, $post, true );
   4646 
   4647 	wp_after_insert_post( $post, true, $post_before );
   4648 }
   4649 
   4650 /**
   4651  * Publish future post and make sure post ID has future post status.
   4652  *
   4653  * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
   4654  * from publishing drafts, etc.
   4655  *
   4656  * @since 2.5.0
   4657  *
   4658  * @param int|WP_Post $post_id Post ID or post object.
   4659  */
   4660 function check_and_publish_future_post( $post_id ) {
   4661 	$post = get_post( $post_id );
   4662 
   4663 	if ( ! $post ) {
   4664 		return;
   4665 	}
   4666 
   4667 	if ( 'future' !== $post->post_status ) {
   4668 		return;
   4669 	}
   4670 
   4671 	$time = strtotime( $post->post_date_gmt . ' GMT' );
   4672 
   4673 	// Uh oh, someone jumped the gun!
   4674 	if ( $time > time() ) {
   4675 		wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // Clear anything else in the system.
   4676 		wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
   4677 		return;
   4678 	}
   4679 
   4680 	// wp_publish_post() returns no meaningful value.
   4681 	wp_publish_post( $post_id );
   4682 }
   4683 
   4684 /**
   4685  * Uses wp_checkdate to return a valid Gregorian-calendar value for post_date.
   4686  * If post_date is not provided, this first checks post_date_gmt if provided,
   4687  * then falls back to use the current time.
   4688  *
   4689  * For back-compat purposes in wp_insert_post, an empty post_date and an invalid
   4690  * post_date_gmt will continue to return '1970-01-01 00:00:00' rather than false.
   4691  *
   4692  * @since 5.7.0
   4693  *
   4694  * @param string $post_date     The date in mysql format.
   4695  * @param string $post_date_gmt The GMT date in mysql format.
   4696  * @return string|false A valid Gregorian-calendar date string, or false on failure.
   4697  */
   4698 function wp_resolve_post_date( $post_date = '', $post_date_gmt = '' ) {
   4699 	// If the date is empty, set the date to now.
   4700 	if ( empty( $post_date ) || '0000-00-00 00:00:00' === $post_date ) {
   4701 		if ( empty( $post_date_gmt ) || '0000-00-00 00:00:00' === $post_date_gmt ) {
   4702 			$post_date = current_time( 'mysql' );
   4703 		} else {
   4704 			$post_date = get_date_from_gmt( $post_date_gmt );
   4705 		}
   4706 	}
   4707 
   4708 	// Validate the date.
   4709 	$month = substr( $post_date, 5, 2 );
   4710 	$day   = substr( $post_date, 8, 2 );
   4711 	$year  = substr( $post_date, 0, 4 );
   4712 
   4713 	$valid_date = wp_checkdate( $month, $day, $year, $post_date );
   4714 
   4715 	if ( ! $valid_date ) {
   4716 		return false;
   4717 	}
   4718 	return $post_date;
   4719 }
   4720 
   4721 /**
   4722  * Computes a unique slug for the post, when given the desired slug and some post details.
   4723  *
   4724  * @since 2.8.0
   4725  *
   4726  * @global wpdb       $wpdb       WordPress database abstraction object.
   4727  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   4728  *
   4729  * @param string $slug        The desired slug (post_name).
   4730  * @param int    $post_ID     Post ID.
   4731  * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
   4732  * @param string $post_type   Post type.
   4733  * @param int    $post_parent Post parent ID.
   4734  * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
   4735  */
   4736 function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
   4737 	if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
   4738 		|| ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
   4739 	) {
   4740 		return $slug;
   4741 	}
   4742 
   4743 	/**
   4744 	 * Filters the post slug before it is generated to be unique.
   4745 	 *
   4746 	 * Returning a non-null value will short-circuit the
   4747 	 * unique slug generation, returning the passed value instead.
   4748 	 *
   4749 	 * @since 5.1.0
   4750 	 *
   4751 	 * @param string|null $override_slug Short-circuit return value.
   4752 	 * @param string      $slug          The desired slug (post_name).
   4753 	 * @param int         $post_ID       Post ID.
   4754 	 * @param string      $post_status   The post status.
   4755 	 * @param string      $post_type     Post type.
   4756 	 * @param int         $post_parent   Post parent ID.
   4757 	 */
   4758 	$override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
   4759 	if ( null !== $override_slug ) {
   4760 		return $override_slug;
   4761 	}
   4762 
   4763 	global $wpdb, $wp_rewrite;
   4764 
   4765 	$original_slug = $slug;
   4766 
   4767 	$feeds = $wp_rewrite->feeds;
   4768 	if ( ! is_array( $feeds ) ) {
   4769 		$feeds = array();
   4770 	}
   4771 
   4772 	if ( 'attachment' === $post_type ) {
   4773 		// Attachment slugs must be unique across all types.
   4774 		$check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
   4775 		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
   4776 
   4777 		/**
   4778 		 * Filters whether the post slug would make a bad attachment slug.
   4779 		 *
   4780 		 * @since 3.1.0
   4781 		 *
   4782 		 * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
   4783 		 * @param string $slug     The post slug.
   4784 		 */
   4785 		$is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );
   4786 
   4787 		if ( $post_name_check
   4788 			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
   4789 			|| $is_bad_attachment_slug
   4790 		) {
   4791 			$suffix = 2;
   4792 			do {
   4793 				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
   4794 				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
   4795 				$suffix++;
   4796 			} while ( $post_name_check );
   4797 			$slug = $alt_post_name;
   4798 		}
   4799 	} elseif ( is_post_type_hierarchical( $post_type ) ) {
   4800 		if ( 'nav_menu_item' === $post_type ) {
   4801 			return $slug;
   4802 		}
   4803 
   4804 		/*
   4805 		 * Page slugs must be unique within their own trees. Pages are in a separate
   4806 		 * namespace than posts so page slugs are allowed to overlap post slugs.
   4807 		 */
   4808 		$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";
   4809 		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
   4810 
   4811 		/**
   4812 		 * Filters whether the post slug would make a bad hierarchical post slug.
   4813 		 *
   4814 		 * @since 3.1.0
   4815 		 *
   4816 		 * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
   4817 		 * @param string $slug        The post slug.
   4818 		 * @param string $post_type   Post type.
   4819 		 * @param int    $post_parent Post parent ID.
   4820 		 */
   4821 		$is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );
   4822 
   4823 		if ( $post_name_check
   4824 			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
   4825 			|| preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
   4826 			|| $is_bad_hierarchical_slug
   4827 		) {
   4828 			$suffix = 2;
   4829 			do {
   4830 				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
   4831 				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
   4832 				$suffix++;
   4833 			} while ( $post_name_check );
   4834 			$slug = $alt_post_name;
   4835 		}
   4836 	} else {
   4837 		// Post slugs must be unique across all posts.
   4838 		$check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
   4839 		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
   4840 
   4841 		$post = get_post( $post_ID );
   4842 
   4843 		// Prevent new post slugs that could result in URLs that conflict with date archives.
   4844 		$conflicts_with_date_archive = false;
   4845 		if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
   4846 			$slug_num = (int) $slug;
   4847 
   4848 			if ( $slug_num ) {
   4849 				$permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
   4850 				$postname_index = array_search( '%postname%', $permastructs, true );
   4851 
   4852 				/*
   4853 				* Potential date clashes are as follows:
   4854 				*
   4855 				* - Any integer in the first permastruct position could be a year.
   4856 				* - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
   4857 				* - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
   4858 				*/
   4859 				if ( 0 === $postname_index ||
   4860 					( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
   4861 					( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
   4862 				) {
   4863 					$conflicts_with_date_archive = true;
   4864 				}
   4865 			}
   4866 		}
   4867 
   4868 		/**
   4869 		 * Filters whether the post slug would be bad as a flat slug.
   4870 		 *
   4871 		 * @since 3.1.0
   4872 		 *
   4873 		 * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
   4874 		 * @param string $slug      The post slug.
   4875 		 * @param string $post_type Post type.
   4876 		 */
   4877 		$is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );
   4878 
   4879 		if ( $post_name_check
   4880 			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
   4881 			|| $conflicts_with_date_archive
   4882 			|| $is_bad_flat_slug
   4883 		) {
   4884 			$suffix = 2;
   4885 			do {
   4886 				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
   4887 				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
   4888 				$suffix++;
   4889 			} while ( $post_name_check );
   4890 			$slug = $alt_post_name;
   4891 		}
   4892 	}
   4893 
   4894 	/**
   4895 	 * Filters the unique post slug.
   4896 	 *
   4897 	 * @since 3.3.0
   4898 	 *
   4899 	 * @param string $slug          The post slug.
   4900 	 * @param int    $post_ID       Post ID.
   4901 	 * @param string $post_status   The post status.
   4902 	 * @param string $post_type     Post type.
   4903 	 * @param int    $post_parent   Post parent ID
   4904 	 * @param string $original_slug The original post slug.
   4905 	 */
   4906 	return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
   4907 }
   4908 
   4909 /**
   4910  * Truncate a post slug.
   4911  *
   4912  * @since 3.6.0
   4913  * @access private
   4914  *
   4915  * @see utf8_uri_encode()
   4916  *
   4917  * @param string $slug   The slug to truncate.
   4918  * @param int    $length Optional. Max length of the slug. Default 200 (characters).
   4919  * @return string The truncated slug.
   4920  */
   4921 function _truncate_post_slug( $slug, $length = 200 ) {
   4922 	if ( strlen( $slug ) > $length ) {
   4923 		$decoded_slug = urldecode( $slug );
   4924 		if ( $decoded_slug === $slug ) {
   4925 			$slug = substr( $slug, 0, $length );
   4926 		} else {
   4927 			$slug = utf8_uri_encode( $decoded_slug, $length );
   4928 		}
   4929 	}
   4930 
   4931 	return rtrim( $slug, '-' );
   4932 }
   4933 
   4934 /**
   4935  * Add tags to a post.
   4936  *
   4937  * @see wp_set_post_tags()
   4938  *
   4939  * @since 2.3.0
   4940  *
   4941  * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
   4942  * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
   4943  *                              separated by commas. Default empty.
   4944  * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
   4945  */
   4946 function wp_add_post_tags( $post_id = 0, $tags = '' ) {
   4947 	return wp_set_post_tags( $post_id, $tags, true );
   4948 }
   4949 
   4950 /**
   4951  * Set the tags for a post.
   4952  *
   4953  * @since 2.3.0
   4954  *
   4955  * @see wp_set_object_terms()
   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  * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
   4961  *                              replace the tags with the new tags. Default false.
   4962  * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
   4963  */
   4964 function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
   4965 	return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
   4966 }
   4967 
   4968 /**
   4969  * Set the terms for a post.
   4970  *
   4971  * @since 2.8.0
   4972  *
   4973  * @see wp_set_object_terms()
   4974  *
   4975  * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
   4976  * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
   4977  *                               separated by commas. Hierarchical taxonomies must always pass IDs rather
   4978  *                               than names so that children with the same names but different parents
   4979  *                               aren't confused. Default empty.
   4980  * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
   4981  * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
   4982  *                               replace the terms with the new terms. Default false.
   4983  * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
   4984  */
   4985 function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
   4986 	$post_id = (int) $post_id;
   4987 
   4988 	if ( ! $post_id ) {
   4989 		return false;
   4990 	}
   4991 
   4992 	if ( empty( $tags ) ) {
   4993 		$tags = array();
   4994 	}
   4995 
   4996 	if ( ! is_array( $tags ) ) {
   4997 		$comma = _x( ',', 'tag delimiter' );
   4998 		if ( ',' !== $comma ) {
   4999 			$tags = str_replace( $comma, ',', $tags );
   5000 		}
   5001 		$tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
   5002 	}
   5003 
   5004 	/*
   5005 	 * Hierarchical taxonomies must always pass IDs rather than names so that
   5006 	 * children with the same names but different parents aren't confused.
   5007 	 */
   5008 	if ( is_taxonomy_hierarchical( $taxonomy ) ) {
   5009 		$tags = array_unique( array_map( 'intval', $tags ) );
   5010 	}
   5011 
   5012 	return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
   5013 }
   5014 
   5015 /**
   5016  * Set categories for a post.
   5017  *
   5018  * If no categories are provided, the default category is used.
   5019  *
   5020  * @since 2.1.0
   5021  *
   5022  * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
   5023  *                                   of the global $post. Default 0.
   5024  * @param int[]|int $post_categories Optional. List of category IDs, or the ID of a single category.
   5025  *                                   Default empty array.
   5026  * @param bool      $append          If true, don't delete existing categories, just add on.
   5027  *                                   If false, replace the categories with the new categories.
   5028  * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
   5029  */
   5030 function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
   5031 	$post_ID     = (int) $post_ID;
   5032 	$post_type   = get_post_type( $post_ID );
   5033 	$post_status = get_post_status( $post_ID );
   5034 
   5035 	// If $post_categories isn't already an array, make it one.
   5036 	$post_categories = (array) $post_categories;
   5037 
   5038 	if ( empty( $post_categories ) ) {
   5039 		/**
   5040 		 * Filters post types (in addition to 'post') that require a default category.
   5041 		 *
   5042 		 * @since 5.5.0
   5043 		 *
   5044 		 * @param string[] $post_types An array of post type names. Default empty array.
   5045 		 */
   5046 		$default_category_post_types = apply_filters( 'default_category_post_types', array() );
   5047 
   5048 		// Regular posts always require a default category.
   5049 		$default_category_post_types = array_merge( $default_category_post_types, array( 'post' ) );
   5050 
   5051 		if ( in_array( $post_type, $default_category_post_types, true )
   5052 			&& is_object_in_taxonomy( $post_type, 'category' )
   5053 			&& 'auto-draft' !== $post_status
   5054 		) {
   5055 			$post_categories = array( get_option( 'default_category' ) );
   5056 			$append          = false;
   5057 		} else {
   5058 			$post_categories = array();
   5059 		}
   5060 	} elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
   5061 		return true;
   5062 	}
   5063 
   5064 	return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
   5065 }
   5066 
   5067 /**
   5068  * Fires actions related to the transitioning of a post's status.
   5069  *
   5070  * When a post is saved, the post status is "transitioned" from one status to another,
   5071  * though this does not always mean the status has actually changed before and after
   5072  * the save. This function fires a number of action hooks related to that transition:
   5073  * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
   5074  * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
   5075  * that the function does not transition the post object in the database.
   5076  *
   5077  * For instance: When publishing a post for the first time, the post status may transition
   5078  * from 'draft' – or some other status – to 'publish'. However, if a post is already
   5079  * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
   5080  * before and after the transition.
   5081  *
   5082  * @since 2.3.0
   5083  *
   5084  * @param string  $new_status Transition to this post status.
   5085  * @param string  $old_status Previous post status.
   5086  * @param WP_Post $post Post data.
   5087  */
   5088 function wp_transition_post_status( $new_status, $old_status, $post ) {
   5089 	/**
   5090 	 * Fires when a post is transitioned from one status to another.
   5091 	 *
   5092 	 * @since 2.3.0
   5093 	 *
   5094 	 * @param string  $new_status New post status.
   5095 	 * @param string  $old_status Old post status.
   5096 	 * @param WP_Post $post       Post object.
   5097 	 */
   5098 	do_action( 'transition_post_status', $new_status, $old_status, $post );
   5099 
   5100 	/**
   5101 	 * Fires when a post is transitioned from one status to another.
   5102 	 *
   5103 	 * The dynamic portions of the hook name, `$new_status` and `$old_status`,
   5104 	 * refer to the old and new post statuses, respectively.
   5105 	 *
   5106 	 * @since 2.3.0
   5107 	 *
   5108 	 * @param WP_Post $post Post object.
   5109 	 */
   5110 	do_action( "{$old_status}_to_{$new_status}", $post );
   5111 
   5112 	/**
   5113 	 * Fires when a post is transitioned from one status to another.
   5114 	 *
   5115 	 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
   5116 	 * refer to the new post status and post type, respectively.
   5117 	 *
   5118 	 * Possible hook names include:
   5119 	 *
   5120 	 *  - `draft_post`
   5121 	 *  - `future_post`
   5122 	 *  - `pending_post`
   5123 	 *  - `private_post`
   5124 	 *  - `publish_post`
   5125 	 *  - `trash_post`
   5126 	 *  - `draft_page`
   5127 	 *  - `future_page`
   5128 	 *  - `pending_page`
   5129 	 *  - `private_page`
   5130 	 *  - `publish_page`
   5131 	 *  - `trash_page`
   5132 	 *  - `publish_attachment`
   5133 	 *  - `trash_attachment`
   5134 	 *
   5135 	 * Please note: When this action is hooked using a particular post status (like
   5136 	 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
   5137 	 * first transitioned to that status from something else, as well as upon
   5138 	 * subsequent post updates (old and new status are both the same).
   5139 	 *
   5140 	 * Therefore, if you are looking to only fire a callback when a post is first
   5141 	 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
   5142 	 *
   5143 	 * @since 2.3.0
   5144 	 *
   5145 	 * @param int     $post_id Post ID.
   5146 	 * @param WP_Post $post    Post object.
   5147 	 */
   5148 	do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
   5149 }
   5150 
   5151 /**
   5152  * Fires actions after a post, its terms and meta data has been saved.
   5153  *
   5154  * @since 5.6.0
   5155  *
   5156  * @param int|WP_Post  $post        The post ID or object that has been saved.
   5157  * @param bool         $update      Whether this is an existing post being updated.
   5158  * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
   5159  *                                  to the update for updated posts.
   5160  */
   5161 function wp_after_insert_post( $post, $update, $post_before ) {
   5162 	$post = get_post( $post );
   5163 	if ( ! $post ) {
   5164 		return;
   5165 	}
   5166 
   5167 	$post_id = $post->ID;
   5168 
   5169 	/**
   5170 	 * Fires once a post, its terms and meta data has been saved.
   5171 	 *
   5172 	 * @since 5.6.0
   5173 	 *
   5174 	 * @param int          $post_id     Post ID.
   5175 	 * @param WP_Post      $post        Post object.
   5176 	 * @param bool         $update      Whether this is an existing post being updated.
   5177 	 * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
   5178 	 *                                  to the update for updated posts.
   5179 	 */
   5180 	do_action( 'wp_after_insert_post', $post_id, $post, $update, $post_before );
   5181 }
   5182 
   5183 //
   5184 // Comment, trackback, and pingback functions.
   5185 //
   5186 
   5187 /**
   5188  * Add a URL to those already pinged.
   5189  *
   5190  * @since 1.5.0
   5191  * @since 4.7.0 `$post_id` can be a WP_Post object.
   5192  * @since 4.7.0 `$uri` can be an array of URIs.
   5193  *
   5194  * @global wpdb $wpdb WordPress database abstraction object.
   5195  *
   5196  * @param int|WP_Post  $post_id Post object or ID.
   5197  * @param string|array $uri     Ping URI or array of URIs.
   5198  * @return int|false How many rows were updated.
   5199  */
   5200 function add_ping( $post_id, $uri ) {
   5201 	global $wpdb;
   5202 
   5203 	$post = get_post( $post_id );
   5204 
   5205 	if ( ! $post ) {
   5206 		return false;
   5207 	}
   5208 
   5209 	$pung = trim( $post->pinged );
   5210 	$pung = preg_split( '/\s/', $pung );
   5211 
   5212 	if ( is_array( $uri ) ) {
   5213 		$pung = array_merge( $pung, $uri );
   5214 	} else {
   5215 		$pung[] = $uri;
   5216 	}
   5217 	$new = implode( "\n", $pung );
   5218 
   5219 	/**
   5220 	 * Filters the new ping URL to add for the given post.
   5221 	 *
   5222 	 * @since 2.0.0
   5223 	 *
   5224 	 * @param string $new New ping URL to add.
   5225 	 */
   5226 	$new = apply_filters( 'add_ping', $new );
   5227 
   5228 	$return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
   5229 	clean_post_cache( $post->ID );
   5230 	return $return;
   5231 }
   5232 
   5233 /**
   5234  * Retrieve enclosures already enclosed for a post.
   5235  *
   5236  * @since 1.5.0
   5237  *
   5238  * @param int $post_id Post ID.
   5239  * @return string[] Array of enclosures for the given post.
   5240  */
   5241 function get_enclosed( $post_id ) {
   5242 	$custom_fields = get_post_custom( $post_id );
   5243 	$pung          = array();
   5244 	if ( ! is_array( $custom_fields ) ) {
   5245 		return $pung;
   5246 	}
   5247 
   5248 	foreach ( $custom_fields as $key => $val ) {
   5249 		if ( 'enclosure' !== $key || ! is_array( $val ) ) {
   5250 			continue;
   5251 		}
   5252 		foreach ( $val as $enc ) {
   5253 			$enclosure = explode( "\n", $enc );
   5254 			$pung[]    = trim( $enclosure[0] );
   5255 		}
   5256 	}
   5257 
   5258 	/**
   5259 	 * Filters the list of enclosures already enclosed for the given post.
   5260 	 *
   5261 	 * @since 2.0.0
   5262 	 *
   5263 	 * @param string[] $pung    Array of enclosures for the given post.
   5264 	 * @param int      $post_id Post ID.
   5265 	 */
   5266 	return apply_filters( 'get_enclosed', $pung, $post_id );
   5267 }
   5268 
   5269 /**
   5270  * Retrieve URLs already pinged for a post.
   5271  *
   5272  * @since 1.5.0
   5273  *
   5274  * @since 4.7.0 `$post_id` can be a WP_Post object.
   5275  *
   5276  * @param int|WP_Post $post_id Post ID or object.
   5277  * @return string[]|false Array of URLs already pinged for the given post, false if the post is not found.
   5278  */
   5279 function get_pung( $post_id ) {
   5280 	$post = get_post( $post_id );
   5281 
   5282 	if ( ! $post ) {
   5283 		return false;
   5284 	}
   5285 
   5286 	$pung = trim( $post->pinged );
   5287 	$pung = preg_split( '/\s/', $pung );
   5288 
   5289 	/**
   5290 	 * Filters the list of already-pinged URLs for the given post.
   5291 	 *
   5292 	 * @since 2.0.0
   5293 	 *
   5294 	 * @param string[] $pung Array of URLs already pinged for the given post.
   5295 	 */
   5296 	return apply_filters( 'get_pung', $pung );
   5297 }
   5298 
   5299 /**
   5300  * Retrieve URLs that need to be pinged.
   5301  *
   5302  * @since 1.5.0
   5303  * @since 4.7.0 `$post_id` can be a WP_Post object.
   5304  *
   5305  * @param int|WP_Post $post_id Post Object or ID
   5306  * @return string[]|false List of URLs yet to ping.
   5307  */
   5308 function get_to_ping( $post_id ) {
   5309 	$post = get_post( $post_id );
   5310 
   5311 	if ( ! $post ) {
   5312 		return false;
   5313 	}
   5314 
   5315 	$to_ping = sanitize_trackback_urls( $post->to_ping );
   5316 	$to_ping = preg_split( '/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY );
   5317 
   5318 	/**
   5319 	 * Filters the list of URLs yet to ping for the given post.
   5320 	 *
   5321 	 * @since 2.0.0
   5322 	 *
   5323 	 * @param string[] $to_ping List of URLs yet to ping.
   5324 	 */
   5325 	return apply_filters( 'get_to_ping', $to_ping );
   5326 }
   5327 
   5328 /**
   5329  * Do trackbacks for a list of URLs.
   5330  *
   5331  * @since 1.0.0
   5332  *
   5333  * @param string $tb_list Comma separated list of URLs.
   5334  * @param int    $post_id Post ID.
   5335  */
   5336 function trackback_url_list( $tb_list, $post_id ) {
   5337 	if ( ! empty( $tb_list ) ) {
   5338 		// Get post data.
   5339 		$postdata = get_post( $post_id, ARRAY_A );
   5340 
   5341 		// Form an excerpt.
   5342 		$excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
   5343 
   5344 		if ( strlen( $excerpt ) > 255 ) {
   5345 			$excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
   5346 		}
   5347 
   5348 		$trackback_urls = explode( ',', $tb_list );
   5349 		foreach ( (array) $trackback_urls as $tb_url ) {
   5350 			$tb_url = trim( $tb_url );
   5351 			trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
   5352 		}
   5353 	}
   5354 }
   5355 
   5356 //
   5357 // Page functions.
   5358 //
   5359 
   5360 /**
   5361  * Get a list of page IDs.
   5362  *
   5363  * @since 2.0.0
   5364  *
   5365  * @global wpdb $wpdb WordPress database abstraction object.
   5366  *
   5367  * @return string[] List of page IDs as strings.
   5368  */
   5369 function get_all_page_ids() {
   5370 	global $wpdb;
   5371 
   5372 	$page_ids = wp_cache_get( 'all_page_ids', 'posts' );
   5373 	if ( ! is_array( $page_ids ) ) {
   5374 		$page_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'page'" );
   5375 		wp_cache_add( 'all_page_ids', $page_ids, 'posts' );
   5376 	}
   5377 
   5378 	return $page_ids;
   5379 }
   5380 
   5381 /**
   5382  * Retrieves page data given a page ID or page object.
   5383  *
   5384  * Use get_post() instead of get_page().
   5385  *
   5386  * @since 1.5.1
   5387  * @deprecated 3.5.0 Use get_post()
   5388  *
   5389  * @param int|WP_Post $page   Page object or page ID. Passed by reference.
   5390  * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   5391  *                            correspond to a WP_Post object, an associative array, or a numeric array,
   5392  *                            respectively. Default OBJECT.
   5393  * @param string      $filter Optional. How the return value should be filtered. Accepts 'raw',
   5394  *                            'edit', 'db', 'display'. Default 'raw'.
   5395  * @return WP_Post|array|null WP_Post or array on success, null on failure.
   5396  */
   5397 function get_page( $page, $output = OBJECT, $filter = 'raw' ) {
   5398 	return get_post( $page, $output, $filter );
   5399 }
   5400 
   5401 /**
   5402  * Retrieves a page given its path.
   5403  *
   5404  * @since 2.1.0
   5405  *
   5406  * @global wpdb $wpdb WordPress database abstraction object.
   5407  *
   5408  * @param string       $page_path Page path.
   5409  * @param string       $output    Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   5410  *                                correspond to a WP_Post object, an associative array, or a numeric array,
   5411  *                                respectively. Default OBJECT.
   5412  * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
   5413  * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
   5414  */
   5415 function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
   5416 	global $wpdb;
   5417 
   5418 	$last_changed = wp_cache_get_last_changed( 'posts' );
   5419 
   5420 	$hash      = md5( $page_path . serialize( $post_type ) );
   5421 	$cache_key = "get_page_by_path:$hash:$last_changed";
   5422 	$cached    = wp_cache_get( $cache_key, 'posts' );
   5423 	if ( false !== $cached ) {
   5424 		// Special case: '0' is a bad `$page_path`.
   5425 		if ( '0' === $cached || 0 === $cached ) {
   5426 			return;
   5427 		} else {
   5428 			return get_post( $cached, $output );
   5429 		}
   5430 	}
   5431 
   5432 	$page_path     = rawurlencode( urldecode( $page_path ) );
   5433 	$page_path     = str_replace( '%2F', '/', $page_path );
   5434 	$page_path     = str_replace( '%20', ' ', $page_path );
   5435 	$parts         = explode( '/', trim( $page_path, '/' ) );
   5436 	$parts         = array_map( 'sanitize_title_for_query', $parts );
   5437 	$escaped_parts = esc_sql( $parts );
   5438 
   5439 	$in_string = "'" . implode( "','", $escaped_parts ) . "'";
   5440 
   5441 	if ( is_array( $post_type ) ) {
   5442 		$post_types = $post_type;
   5443 	} else {
   5444 		$post_types = array( $post_type, 'attachment' );
   5445 	}
   5446 
   5447 	$post_types          = esc_sql( $post_types );
   5448 	$post_type_in_string = "'" . implode( "','", $post_types ) . "'";
   5449 	$sql                 = "
   5450 		SELECT ID, post_name, post_parent, post_type
   5451 		FROM $wpdb->posts
   5452 		WHERE post_name IN ($in_string)
   5453 		AND post_type IN ($post_type_in_string)
   5454 	";
   5455 
   5456 	$pages = $wpdb->get_results( $sql, OBJECT_K );
   5457 
   5458 	$revparts = array_reverse( $parts );
   5459 
   5460 	$foundid = 0;
   5461 	foreach ( (array) $pages as $page ) {
   5462 		if ( $page->post_name == $revparts[0] ) {
   5463 			$count = 0;
   5464 			$p     = $page;
   5465 
   5466 			/*
   5467 			 * Loop through the given path parts from right to left,
   5468 			 * ensuring each matches the post ancestry.
   5469 			 */
   5470 			while ( 0 != $p->post_parent && isset( $pages[ $p->post_parent ] ) ) {
   5471 				$count++;
   5472 				$parent = $pages[ $p->post_parent ];
   5473 				if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] ) {
   5474 					break;
   5475 				}
   5476 				$p = $parent;
   5477 			}
   5478 
   5479 			if ( 0 == $p->post_parent && count( $revparts ) == $count + 1 && $p->post_name == $revparts[ $count ] ) {
   5480 				$foundid = $page->ID;
   5481 				if ( $page->post_type == $post_type ) {
   5482 					break;
   5483 				}
   5484 			}
   5485 		}
   5486 	}
   5487 
   5488 	// We cache misses as well as hits.
   5489 	wp_cache_set( $cache_key, $foundid, 'posts' );
   5490 
   5491 	if ( $foundid ) {
   5492 		return get_post( $foundid, $output );
   5493 	}
   5494 }
   5495 
   5496 /**
   5497  * Retrieve a page given its title.
   5498  *
   5499  * If more than one post uses the same title, the post with the smallest ID will be returned.
   5500  * Be careful: in case of more than one post having the same title, it will check the oldest
   5501  * publication date, not the smallest ID.
   5502  *
   5503  * Because this function uses the MySQL '=' comparison, $page_title will usually be matched
   5504  * as case-insensitive with default collation.
   5505  *
   5506  * @since 2.1.0
   5507  * @since 3.0.0 The `$post_type` parameter was added.
   5508  *
   5509  * @global wpdb $wpdb WordPress database abstraction object.
   5510  *
   5511  * @param string       $page_title Page title.
   5512  * @param string       $output     Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   5513  *                                 correspond to a WP_Post object, an associative array, or a numeric array,
   5514  *                                 respectively. Default OBJECT.
   5515  * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
   5516  * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
   5517  */
   5518 function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
   5519 	global $wpdb;
   5520 
   5521 	if ( is_array( $post_type ) ) {
   5522 		$post_type           = esc_sql( $post_type );
   5523 		$post_type_in_string = "'" . implode( "','", $post_type ) . "'";
   5524 		$sql                 = $wpdb->prepare(
   5525 			"
   5526 			SELECT ID
   5527 			FROM $wpdb->posts
   5528 			WHERE post_title = %s
   5529 			AND post_type IN ($post_type_in_string)
   5530 		",
   5531 			$page_title
   5532 		);
   5533 	} else {
   5534 		$sql = $wpdb->prepare(
   5535 			"
   5536 			SELECT ID
   5537 			FROM $wpdb->posts
   5538 			WHERE post_title = %s
   5539 			AND post_type = %s
   5540 		",
   5541 			$page_title,
   5542 			$post_type
   5543 		);
   5544 	}
   5545 
   5546 	$page = $wpdb->get_var( $sql );
   5547 
   5548 	if ( $page ) {
   5549 		return get_post( $page, $output );
   5550 	}
   5551 }
   5552 
   5553 /**
   5554  * Identify descendants of a given page ID in a list of page objects.
   5555  *
   5556  * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
   5557  *
   5558  * @since 1.5.1
   5559  *
   5560  * @param int   $page_id Page ID.
   5561  * @param array $pages   List of page objects from which descendants should be identified.
   5562  * @return array List of page children.
   5563  */
   5564 function get_page_children( $page_id, $pages ) {
   5565 	// Build a hash of ID -> children.
   5566 	$children = array();
   5567 	foreach ( (array) $pages as $page ) {
   5568 		$children[ (int) $page->post_parent ][] = $page;
   5569 	}
   5570 
   5571 	$page_list = array();
   5572 
   5573 	// Start the search by looking at immediate children.
   5574 	if ( isset( $children[ $page_id ] ) ) {
   5575 		// Always start at the end of the stack in order to preserve original `$pages` order.
   5576 		$to_look = array_reverse( $children[ $page_id ] );
   5577 
   5578 		while ( $to_look ) {
   5579 			$p           = array_pop( $to_look );
   5580 			$page_list[] = $p;
   5581 			if ( isset( $children[ $p->ID ] ) ) {
   5582 				foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
   5583 					// Append to the `$to_look` stack to descend the tree.
   5584 					$to_look[] = $child;
   5585 				}
   5586 			}
   5587 		}
   5588 	}
   5589 
   5590 	return $page_list;
   5591 }
   5592 
   5593 /**
   5594  * Order the pages with children under parents in a flat list.
   5595  *
   5596  * It uses auxiliary structure to hold parent-children relationships and
   5597  * runs in O(N) complexity
   5598  *
   5599  * @since 2.0.0
   5600  *
   5601  * @param WP_Post[] $pages   Posts array (passed by reference).
   5602  * @param int       $page_id Optional. Parent page ID. Default 0.
   5603  * @return string[] Array of post names keyed by ID and arranged by hierarchy. Children immediately follow their parents.
   5604  */
   5605 function get_page_hierarchy( &$pages, $page_id = 0 ) {
   5606 	if ( empty( $pages ) ) {
   5607 		return array();
   5608 	}
   5609 
   5610 	$children = array();
   5611 	foreach ( (array) $pages as $p ) {
   5612 		$parent_id                = (int) $p->post_parent;
   5613 		$children[ $parent_id ][] = $p;
   5614 	}
   5615 
   5616 	$result = array();
   5617 	_page_traverse_name( $page_id, $children, $result );
   5618 
   5619 	return $result;
   5620 }
   5621 
   5622 /**
   5623  * Traverse and return all the nested children post names of a root page.
   5624  *
   5625  * $children contains parent-children relations
   5626  *
   5627  * @since 2.9.0
   5628  * @access private
   5629  *
   5630  * @see _page_traverse_name()
   5631  *
   5632  * @param int      $page_id  Page ID.
   5633  * @param array    $children Parent-children relations (passed by reference).
   5634  * @param string[] $result   Array of page names keyed by ID (passed by reference).
   5635  */
   5636 function _page_traverse_name( $page_id, &$children, &$result ) {
   5637 	if ( isset( $children[ $page_id ] ) ) {
   5638 		foreach ( (array) $children[ $page_id ] as $child ) {
   5639 			$result[ $child->ID ] = $child->post_name;
   5640 			_page_traverse_name( $child->ID, $children, $result );
   5641 		}
   5642 	}
   5643 }
   5644 
   5645 /**
   5646  * Build the URI path for a page.
   5647  *
   5648  * Sub pages will be in the "directory" under the parent page post name.
   5649  *
   5650  * @since 1.5.0
   5651  * @since 4.6.0 The `$page` parameter was made optional.
   5652  *
   5653  * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
   5654  * @return string|false Page URI, false on error.
   5655  */
   5656 function get_page_uri( $page = 0 ) {
   5657 	if ( ! $page instanceof WP_Post ) {
   5658 		$page = get_post( $page );
   5659 	}
   5660 
   5661 	if ( ! $page ) {
   5662 		return false;
   5663 	}
   5664 
   5665 	$uri = $page->post_name;
   5666 
   5667 	foreach ( $page->ancestors as $parent ) {
   5668 		$parent = get_post( $parent );
   5669 		if ( $parent && $parent->post_name ) {
   5670 			$uri = $parent->post_name . '/' . $uri;
   5671 		}
   5672 	}
   5673 
   5674 	/**
   5675 	 * Filters the URI for a page.
   5676 	 *
   5677 	 * @since 4.4.0
   5678 	 *
   5679 	 * @param string  $uri  Page URI.
   5680 	 * @param WP_Post $page Page object.
   5681 	 */
   5682 	return apply_filters( 'get_page_uri', $uri, $page );
   5683 }
   5684 
   5685 /**
   5686  * Retrieve an array of pages (or hierarchical post type items).
   5687  *
   5688  * @global wpdb $wpdb WordPress database abstraction object.
   5689  *
   5690  * @since 1.5.0
   5691  *
   5692  * @param array|string $args {
   5693  *     Optional. Array or string of arguments to retrieve pages.
   5694  *
   5695  *     @type int          $child_of     Page ID to return child and grandchild pages of. Note: The value
   5696  *                                      of `$hierarchical` has no bearing on whether `$child_of` returns
   5697  *                                      hierarchical results. Default 0, or no restriction.
   5698  *     @type string       $sort_order   How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
   5699  *     @type string       $sort_column  What columns to sort pages by, comma-separated. Accepts 'post_author',
   5700  *                                      'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
   5701  *                                      'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
   5702  *                                      'post_' can be omitted for any values that start with it.
   5703  *                                      Default 'post_title'.
   5704  *     @type bool         $hierarchical Whether to return pages hierarchically. If false in conjunction with
   5705  *                                      `$child_of` also being false, both arguments will be disregarded.
   5706  *                                      Default true.
   5707  *     @type int[]        $exclude      Array of page IDs to exclude. Default empty array.
   5708  *     @type int[]        $include      Array of page IDs to include. Cannot be used with `$child_of`,
   5709  *                                      `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
   5710  *                                      Default empty array.
   5711  *     @type string       $meta_key     Only include pages with this meta key. Default empty.
   5712  *     @type string       $meta_value   Only include pages with this meta value. Requires `$meta_key`.
   5713  *                                      Default empty.
   5714  *     @type string       $authors      A comma-separated list of author IDs. Default empty.
   5715  *     @type int          $parent       Page ID to return direct children of. Default -1, or no restriction.
   5716  *     @type string|int[] $exclude_tree Comma-separated string or array of page IDs to exclude.
   5717  *                                      Default empty array.
   5718  *     @type int          $number       The number of pages to return. Default 0, or all pages.
   5719  *     @type int          $offset       The number of pages to skip before returning. Requires `$number`.
   5720  *                                      Default 0.
   5721  *     @type string       $post_type    The post type to query. Default 'page'.
   5722  *     @type string|array $post_status  A comma-separated list or array of post statuses to include.
   5723  *                                      Default 'publish'.
   5724  * }
   5725  * @return WP_Post[]|int[]|false Array of pages (or hierarchical post type items). Boolean false if the
   5726  *                               specified post type is not hierarchical or the specified status is not
   5727  *                               supported by the post type.
   5728  */
   5729 function get_pages( $args = array() ) {
   5730 	global $wpdb;
   5731 
   5732 	$defaults = array(
   5733 		'child_of'     => 0,
   5734 		'sort_order'   => 'ASC',
   5735 		'sort_column'  => 'post_title',
   5736 		'hierarchical' => 1,
   5737 		'exclude'      => array(),
   5738 		'include'      => array(),
   5739 		'meta_key'     => '',
   5740 		'meta_value'   => '',
   5741 		'authors'      => '',
   5742 		'parent'       => -1,
   5743 		'exclude_tree' => array(),
   5744 		'number'       => '',
   5745 		'offset'       => 0,
   5746 		'post_type'    => 'page',
   5747 		'post_status'  => 'publish',
   5748 	);
   5749 
   5750 	$parsed_args = wp_parse_args( $args, $defaults );
   5751 
   5752 	$number       = (int) $parsed_args['number'];
   5753 	$offset       = (int) $parsed_args['offset'];
   5754 	$child_of     = (int) $parsed_args['child_of'];
   5755 	$hierarchical = $parsed_args['hierarchical'];
   5756 	$exclude      = $parsed_args['exclude'];
   5757 	$meta_key     = $parsed_args['meta_key'];
   5758 	$meta_value   = $parsed_args['meta_value'];
   5759 	$parent       = $parsed_args['parent'];
   5760 	$post_status  = $parsed_args['post_status'];
   5761 
   5762 	// Make sure the post type is hierarchical.
   5763 	$hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
   5764 	if ( ! in_array( $parsed_args['post_type'], $hierarchical_post_types, true ) ) {
   5765 		return false;
   5766 	}
   5767 
   5768 	if ( $parent > 0 && ! $child_of ) {
   5769 		$hierarchical = false;
   5770 	}
   5771 
   5772 	// Make sure we have a valid post status.
   5773 	if ( ! is_array( $post_status ) ) {
   5774 		$post_status = explode( ',', $post_status );
   5775 	}
   5776 	if ( array_diff( $post_status, get_post_stati() ) ) {
   5777 		return false;
   5778 	}
   5779 
   5780 	// $args can be whatever, only use the args defined in defaults to compute the key.
   5781 	$key          = md5( serialize( wp_array_slice_assoc( $parsed_args, array_keys( $defaults ) ) ) );
   5782 	$last_changed = wp_cache_get_last_changed( 'posts' );
   5783 
   5784 	$cache_key = "get_pages:$key:$last_changed";
   5785 	$cache     = wp_cache_get( $cache_key, 'posts' );
   5786 	if ( false !== $cache ) {
   5787 		_prime_post_caches( $cache, false, false );
   5788 
   5789 		// Convert to WP_Post instances.
   5790 		$pages = array_map( 'get_post', $cache );
   5791 		/** This filter is documented in wp-includes/post.php */
   5792 		$pages = apply_filters( 'get_pages', $pages, $parsed_args );
   5793 
   5794 		return $pages;
   5795 	}
   5796 
   5797 	$inclusions = '';
   5798 	if ( ! empty( $parsed_args['include'] ) ) {
   5799 		$child_of     = 0; // Ignore child_of, parent, exclude, meta_key, and meta_value params if using include.
   5800 		$parent       = -1;
   5801 		$exclude      = '';
   5802 		$meta_key     = '';
   5803 		$meta_value   = '';
   5804 		$hierarchical = false;
   5805 		$incpages     = wp_parse_id_list( $parsed_args['include'] );
   5806 		if ( ! empty( $incpages ) ) {
   5807 			$inclusions = ' AND ID IN (' . implode( ',', $incpages ) . ')';
   5808 		}
   5809 	}
   5810 
   5811 	$exclusions = '';
   5812 	if ( ! empty( $exclude ) ) {
   5813 		$expages = wp_parse_id_list( $exclude );
   5814 		if ( ! empty( $expages ) ) {
   5815 			$exclusions = ' AND ID NOT IN (' . implode( ',', $expages ) . ')';
   5816 		}
   5817 	}
   5818 
   5819 	$author_query = '';
   5820 	if ( ! empty( $parsed_args['authors'] ) ) {
   5821 		$post_authors = wp_parse_list( $parsed_args['authors'] );
   5822 
   5823 		if ( ! empty( $post_authors ) ) {
   5824 			foreach ( $post_authors as $post_author ) {
   5825 				// Do we have an author id or an author login?
   5826 				if ( 0 == (int) $post_author ) {
   5827 					$post_author = get_user_by( 'login', $post_author );
   5828 					if ( empty( $post_author ) ) {
   5829 						continue;
   5830 					}
   5831 					if ( empty( $post_author->ID ) ) {
   5832 						continue;
   5833 					}
   5834 					$post_author = $post_author->ID;
   5835 				}
   5836 
   5837 				if ( '' === $author_query ) {
   5838 					$author_query = $wpdb->prepare( ' post_author = %d ', $post_author );
   5839 				} else {
   5840 					$author_query .= $wpdb->prepare( ' OR post_author = %d ', $post_author );
   5841 				}
   5842 			}
   5843 			if ( '' !== $author_query ) {
   5844 				$author_query = " AND ($author_query)";
   5845 			}
   5846 		}
   5847 	}
   5848 
   5849 	$join  = '';
   5850 	$where = "$exclusions $inclusions ";
   5851 	if ( '' !== $meta_key || '' !== $meta_value ) {
   5852 		$join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
   5853 
   5854 		// meta_key and meta_value might be slashed.
   5855 		$meta_key   = wp_unslash( $meta_key );
   5856 		$meta_value = wp_unslash( $meta_value );
   5857 		if ( '' !== $meta_key ) {
   5858 			$where .= $wpdb->prepare( " AND $wpdb->postmeta.meta_key = %s", $meta_key );
   5859 		}
   5860 		if ( '' !== $meta_value ) {
   5861 			$where .= $wpdb->prepare( " AND $wpdb->postmeta.meta_value = %s", $meta_value );
   5862 		}
   5863 	}
   5864 
   5865 	if ( is_array( $parent ) ) {
   5866 		$post_parent__in = implode( ',', array_map( 'absint', (array) $parent ) );
   5867 		if ( ! empty( $post_parent__in ) ) {
   5868 			$where .= " AND post_parent IN ($post_parent__in)";
   5869 		}
   5870 	} elseif ( $parent >= 0 ) {
   5871 		$where .= $wpdb->prepare( ' AND post_parent = %d ', $parent );
   5872 	}
   5873 
   5874 	if ( 1 === count( $post_status ) ) {
   5875 		$where_post_type = $wpdb->prepare( 'post_type = %s AND post_status = %s', $parsed_args['post_type'], reset( $post_status ) );
   5876 	} else {
   5877 		$post_status     = implode( "', '", $post_status );
   5878 		$where_post_type = $wpdb->prepare( "post_type = %s AND post_status IN ('$post_status')", $parsed_args['post_type'] );
   5879 	}
   5880 
   5881 	$orderby_array = array();
   5882 	$allowed_keys  = array(
   5883 		'author',
   5884 		'post_author',
   5885 		'date',
   5886 		'post_date',
   5887 		'title',
   5888 		'post_title',
   5889 		'name',
   5890 		'post_name',
   5891 		'modified',
   5892 		'post_modified',
   5893 		'modified_gmt',
   5894 		'post_modified_gmt',
   5895 		'menu_order',
   5896 		'parent',
   5897 		'post_parent',
   5898 		'ID',
   5899 		'rand',
   5900 		'comment_count',
   5901 	);
   5902 
   5903 	foreach ( explode( ',', $parsed_args['sort_column'] ) as $orderby ) {
   5904 		$orderby = trim( $orderby );
   5905 		if ( ! in_array( $orderby, $allowed_keys, true ) ) {
   5906 			continue;
   5907 		}
   5908 
   5909 		switch ( $orderby ) {
   5910 			case 'menu_order':
   5911 				break;
   5912 			case 'ID':
   5913 				$orderby = "$wpdb->posts.ID";
   5914 				break;
   5915 			case 'rand':
   5916 				$orderby = 'RAND()';
   5917 				break;
   5918 			case 'comment_count':
   5919 				$orderby = "$wpdb->posts.comment_count";
   5920 				break;
   5921 			default:
   5922 				if ( 0 === strpos( $orderby, 'post_' ) ) {
   5923 					$orderby = "$wpdb->posts." . $orderby;
   5924 				} else {
   5925 					$orderby = "$wpdb->posts.post_" . $orderby;
   5926 				}
   5927 		}
   5928 
   5929 		$orderby_array[] = $orderby;
   5930 
   5931 	}
   5932 	$sort_column = ! empty( $orderby_array ) ? implode( ',', $orderby_array ) : "$wpdb->posts.post_title";
   5933 
   5934 	$sort_order = strtoupper( $parsed_args['sort_order'] );
   5935 	if ( '' !== $sort_order && ! in_array( $sort_order, array( 'ASC', 'DESC' ), true ) ) {
   5936 		$sort_order = 'ASC';
   5937 	}
   5938 
   5939 	$query  = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
   5940 	$query .= $author_query;
   5941 	$query .= ' ORDER BY ' . $sort_column . ' ' . $sort_order;
   5942 
   5943 	if ( ! empty( $number ) ) {
   5944 		$query .= ' LIMIT ' . $offset . ',' . $number;
   5945 	}
   5946 
   5947 	$pages = $wpdb->get_results( $query );
   5948 
   5949 	if ( empty( $pages ) ) {
   5950 		wp_cache_set( $cache_key, array(), 'posts' );
   5951 
   5952 		/** This filter is documented in wp-includes/post.php */
   5953 		$pages = apply_filters( 'get_pages', array(), $parsed_args );
   5954 
   5955 		return $pages;
   5956 	}
   5957 
   5958 	// Sanitize before caching so it'll only get done once.
   5959 	$num_pages = count( $pages );
   5960 	for ( $i = 0; $i < $num_pages; $i++ ) {
   5961 		$pages[ $i ] = sanitize_post( $pages[ $i ], 'raw' );
   5962 	}
   5963 
   5964 	// Update cache.
   5965 	update_post_cache( $pages );
   5966 
   5967 	if ( $child_of || $hierarchical ) {
   5968 		$pages = get_page_children( $child_of, $pages );
   5969 	}
   5970 
   5971 	if ( ! empty( $parsed_args['exclude_tree'] ) ) {
   5972 		$exclude = wp_parse_id_list( $parsed_args['exclude_tree'] );
   5973 		foreach ( $exclude as $id ) {
   5974 			$children = get_page_children( $id, $pages );
   5975 			foreach ( $children as $child ) {
   5976 				$exclude[] = $child->ID;
   5977 			}
   5978 		}
   5979 
   5980 		$num_pages = count( $pages );
   5981 		for ( $i = 0; $i < $num_pages; $i++ ) {
   5982 			if ( in_array( $pages[ $i ]->ID, $exclude, true ) ) {
   5983 				unset( $pages[ $i ] );
   5984 			}
   5985 		}
   5986 	}
   5987 
   5988 	$page_structure = array();
   5989 	foreach ( $pages as $page ) {
   5990 		$page_structure[] = $page->ID;
   5991 	}
   5992 
   5993 	wp_cache_set( $cache_key, $page_structure, 'posts' );
   5994 
   5995 	// Convert to WP_Post instances.
   5996 	$pages = array_map( 'get_post', $pages );
   5997 
   5998 	/**
   5999 	 * Filters the retrieved list of pages.
   6000 	 *
   6001 	 * @since 2.1.0
   6002 	 *
   6003 	 * @param WP_Post[] $pages       Array of page objects.
   6004 	 * @param array     $parsed_args Array of get_pages() arguments.
   6005 	 */
   6006 	return apply_filters( 'get_pages', $pages, $parsed_args );
   6007 }
   6008 
   6009 //
   6010 // Attachment functions.
   6011 //
   6012 
   6013 /**
   6014  * Determines whether an attachment URI is local and really an attachment.
   6015  *
   6016  * For more information on this and similar theme functions, check out
   6017  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
   6018  * Conditional Tags} article in the Theme Developer Handbook.
   6019  *
   6020  * @since 2.0.0
   6021  *
   6022  * @param string $url URL to check
   6023  * @return bool True on success, false on failure.
   6024  */
   6025 function is_local_attachment( $url ) {
   6026 	if ( strpos( $url, home_url() ) === false ) {
   6027 		return false;
   6028 	}
   6029 	if ( strpos( $url, home_url( '/?attachment_id=' ) ) !== false ) {
   6030 		return true;
   6031 	}
   6032 
   6033 	$id = url_to_postid( $url );
   6034 	if ( $id ) {
   6035 		$post = get_post( $id );
   6036 		if ( 'attachment' === $post->post_type ) {
   6037 			return true;
   6038 		}
   6039 	}
   6040 	return false;
   6041 }
   6042 
   6043 /**
   6044  * Insert an attachment.
   6045  *
   6046  * If you set the 'ID' in the $args parameter, it will mean that you are
   6047  * updating and attempt to update the attachment. You can also set the
   6048  * attachment name or title by setting the key 'post_name' or 'post_title'.
   6049  *
   6050  * You can set the dates for the attachment manually by setting the 'post_date'
   6051  * and 'post_date_gmt' keys' values.
   6052  *
   6053  * By default, the comments will use the default settings for whether the
   6054  * comments are allowed. You can close them manually or keep them open by
   6055  * setting the value for the 'comment_status' key.
   6056  *
   6057  * @since 2.0.0
   6058  * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
   6059  * @since 5.6.0 Added the `$fire_after_hooks` parameter.
   6060  *
   6061  * @see wp_insert_post()
   6062  *
   6063  * @param string|array $args             Arguments for inserting an attachment.
   6064  * @param string|false $file             Optional. Filename.
   6065  * @param int          $parent           Optional. Parent post ID.
   6066  * @param bool         $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
   6067  * @param bool         $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
   6068  * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
   6069  */
   6070 function wp_insert_attachment( $args, $file = false, $parent = 0, $wp_error = false, $fire_after_hooks = true ) {
   6071 	$defaults = array(
   6072 		'file'        => $file,
   6073 		'post_parent' => 0,
   6074 	);
   6075 
   6076 	$data = wp_parse_args( $args, $defaults );
   6077 
   6078 	if ( ! empty( $parent ) ) {
   6079 		$data['post_parent'] = $parent;
   6080 	}
   6081 
   6082 	$data['post_type'] = 'attachment';
   6083 
   6084 	return wp_insert_post( $data, $wp_error, $fire_after_hooks );
   6085 }
   6086 
   6087 /**
   6088  * Trash or delete an attachment.
   6089  *
   6090  * When an attachment is permanently deleted, the file will also be removed.
   6091  * Deletion removes all post meta fields, taxonomy, comments, etc. associated
   6092  * with the attachment (except the main post).
   6093  *
   6094  * The attachment is moved to the Trash instead of permanently deleted unless Trash
   6095  * for media is disabled, item is already in the Trash, or $force_delete is true.
   6096  *
   6097  * @since 2.0.0
   6098  *
   6099  * @global wpdb $wpdb WordPress database abstraction object.
   6100  *
   6101  * @param int  $post_id      Attachment ID.
   6102  * @param bool $force_delete Optional. Whether to bypass Trash and force deletion.
   6103  *                           Default false.
   6104  * @return WP_Post|false|null Post data on success, false or null on failure.
   6105  */
   6106 function wp_delete_attachment( $post_id, $force_delete = false ) {
   6107 	global $wpdb;
   6108 
   6109 	$post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) );
   6110 
   6111 	if ( ! $post ) {
   6112 		return $post;
   6113 	}
   6114 
   6115 	$post = get_post( $post );
   6116 
   6117 	if ( 'attachment' !== $post->post_type ) {
   6118 		return false;
   6119 	}
   6120 
   6121 	if ( ! $force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status ) {
   6122 		return wp_trash_post( $post_id );
   6123 	}
   6124 
   6125 	/**
   6126 	 * Filters whether an attachment deletion should take place.
   6127 	 *
   6128 	 * @since 5.5.0
   6129 	 *
   6130 	 * @param bool|null $delete       Whether to go forward with deletion.
   6131 	 * @param WP_Post   $post         Post object.
   6132 	 * @param bool      $force_delete Whether to bypass the Trash.
   6133 	 */
   6134 	$check = apply_filters( 'pre_delete_attachment', null, $post, $force_delete );
   6135 	if ( null !== $check ) {
   6136 		return $check;
   6137 	}
   6138 
   6139 	delete_post_meta( $post_id, '_wp_trash_meta_status' );
   6140 	delete_post_meta( $post_id, '_wp_trash_meta_time' );
   6141 
   6142 	$meta         = wp_get_attachment_metadata( $post_id );
   6143 	$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
   6144 	$file         = get_attached_file( $post_id );
   6145 
   6146 	if ( is_multisite() ) {
   6147 		clean_dirsize_cache( $file );
   6148 	}
   6149 
   6150 	/**
   6151 	 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
   6152 	 *
   6153 	 * @since 2.0.0
   6154 	 * @since 5.5.0 Added the `$post` parameter.
   6155 	 *
   6156 	 * @param int     $post_id Attachment ID.
   6157 	 * @param WP_Post $post    Post object.
   6158 	 */
   6159 	do_action( 'delete_attachment', $post_id, $post );
   6160 
   6161 	wp_delete_object_term_relationships( $post_id, array( 'category', 'post_tag' ) );
   6162 	wp_delete_object_term_relationships( $post_id, get_object_taxonomies( $post->post_type ) );
   6163 
   6164 	// Delete all for any posts.
   6165 	delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
   6166 
   6167 	wp_defer_comment_counting( true );
   6168 
   6169 	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
   6170 	foreach ( $comment_ids as $comment_id ) {
   6171 		wp_delete_comment( $comment_id, true );
   6172 	}
   6173 
   6174 	wp_defer_comment_counting( false );
   6175 
   6176 	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ) );
   6177 	foreach ( $post_meta_ids as $mid ) {
   6178 		delete_metadata_by_mid( 'post', $mid );
   6179 	}
   6180 
   6181 	/** This action is documented in wp-includes/post.php */
   6182 	do_action( 'delete_post', $post_id, $post );
   6183 	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
   6184 	if ( ! $result ) {
   6185 		return false;
   6186 	}
   6187 	/** This action is documented in wp-includes/post.php */
   6188 	do_action( 'deleted_post', $post_id, $post );
   6189 
   6190 	wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
   6191 
   6192 	clean_post_cache( $post );
   6193 
   6194 	return $post;
   6195 }
   6196 
   6197 /**
   6198  * Deletes all files that belong to the given attachment.
   6199  *
   6200  * @since 4.9.7
   6201  *
   6202  * @global wpdb $wpdb WordPress database abstraction object.
   6203  *
   6204  * @param int    $post_id      Attachment ID.
   6205  * @param array  $meta         The attachment's meta data.
   6206  * @param array  $backup_sizes The meta data for the attachment's backup images.
   6207  * @param string $file         Absolute path to the attachment's file.
   6208  * @return bool True on success, false on failure.
   6209  */
   6210 function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
   6211 	global $wpdb;
   6212 
   6213 	$uploadpath = wp_get_upload_dir();
   6214 	$deleted    = true;
   6215 
   6216 	if ( ! empty( $meta['thumb'] ) ) {
   6217 		// Don't delete the thumb if another attachment uses it.
   6218 		if ( ! $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s AND post_id <> %d", '%' . $wpdb->esc_like( $meta['thumb'] ) . '%', $post_id ) ) ) {
   6219 			$thumbfile = str_replace( wp_basename( $file ), $meta['thumb'], $file );
   6220 
   6221 			if ( ! empty( $thumbfile ) ) {
   6222 				$thumbfile = path_join( $uploadpath['basedir'], $thumbfile );
   6223 				$thumbdir  = path_join( $uploadpath['basedir'], dirname( $file ) );
   6224 
   6225 				if ( ! wp_delete_file_from_directory( $thumbfile, $thumbdir ) ) {
   6226 					$deleted = false;
   6227 				}
   6228 			}
   6229 		}
   6230 	}
   6231 
   6232 	// Remove intermediate and backup images if there are any.
   6233 	if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
   6234 		$intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
   6235 
   6236 		foreach ( $meta['sizes'] as $size => $sizeinfo ) {
   6237 			$intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
   6238 
   6239 			if ( ! empty( $intermediate_file ) ) {
   6240 				$intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
   6241 
   6242 				if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
   6243 					$deleted = false;
   6244 				}
   6245 			}
   6246 		}
   6247 	}
   6248 
   6249 	if ( ! empty( $meta['original_image'] ) ) {
   6250 		if ( empty( $intermediate_dir ) ) {
   6251 			$intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
   6252 		}
   6253 
   6254 		$original_image = str_replace( wp_basename( $file ), $meta['original_image'], $file );
   6255 
   6256 		if ( ! empty( $original_image ) ) {
   6257 			$original_image = path_join( $uploadpath['basedir'], $original_image );
   6258 
   6259 			if ( ! wp_delete_file_from_directory( $original_image, $intermediate_dir ) ) {
   6260 				$deleted = false;
   6261 			}
   6262 		}
   6263 	}
   6264 
   6265 	if ( is_array( $backup_sizes ) ) {
   6266 		$del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
   6267 
   6268 		foreach ( $backup_sizes as $size ) {
   6269 			$del_file = path_join( dirname( $meta['file'] ), $size['file'] );
   6270 
   6271 			if ( ! empty( $del_file ) ) {
   6272 				$del_file = path_join( $uploadpath['basedir'], $del_file );
   6273 
   6274 				if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
   6275 					$deleted = false;
   6276 				}
   6277 			}
   6278 		}
   6279 	}
   6280 
   6281 	if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
   6282 		$deleted = false;
   6283 	}
   6284 
   6285 	return $deleted;
   6286 }
   6287 
   6288 /**
   6289  * Retrieves attachment metadata for attachment ID.
   6290  *
   6291  * @since 2.1.0
   6292  *
   6293  * @param int  $attachment_id Attachment post ID. Defaults to global $post.
   6294  * @param bool $unfiltered    Optional. If true, filters are not run. Default false.
   6295  * @return array|false {
   6296  *     Attachment metadata. False on failure.
   6297  *
   6298  *     @type int    $width      The width of the attachment.
   6299  *     @type int    $height     The height of the attachment.
   6300  *     @type string $file       The file path relative to `wp-content/uploads`.
   6301  *     @type array  $sizes      Keys are size slugs, each value is an array containing
   6302  *                              'file', 'width', 'height', and 'mime-type'.
   6303  *     @type array  $image_meta Image metadata.
   6304  * }
   6305  */
   6306 function wp_get_attachment_metadata( $attachment_id = 0, $unfiltered = false ) {
   6307 	$attachment_id = (int) $attachment_id;
   6308 
   6309 	if ( ! $attachment_id ) {
   6310 		$post = get_post();
   6311 
   6312 		if ( ! $post ) {
   6313 			return false;
   6314 		}
   6315 
   6316 		$attachment_id = $post->ID;
   6317 	}
   6318 
   6319 	$data = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
   6320 
   6321 	if ( ! $data ) {
   6322 		return false;
   6323 	}
   6324 
   6325 	if ( $unfiltered ) {
   6326 		return $data;
   6327 	}
   6328 
   6329 	/**
   6330 	 * Filters the attachment meta data.
   6331 	 *
   6332 	 * @since 2.1.0
   6333 	 *
   6334 	 * @param array $data          Array of meta data for the given attachment.
   6335 	 * @param int   $attachment_id Attachment post ID.
   6336 	 */
   6337 	return apply_filters( 'wp_get_attachment_metadata', $data, $attachment_id );
   6338 }
   6339 
   6340 /**
   6341  * Updates metadata for an attachment.
   6342  *
   6343  * @since 2.1.0
   6344  *
   6345  * @param int   $attachment_id Attachment post ID.
   6346  * @param array $data          Attachment meta data.
   6347  * @return int|false False if $post is invalid.
   6348  */
   6349 function wp_update_attachment_metadata( $attachment_id, $data ) {
   6350 	$attachment_id = (int) $attachment_id;
   6351 
   6352 	$post = get_post( $attachment_id );
   6353 
   6354 	if ( ! $post ) {
   6355 		return false;
   6356 	}
   6357 
   6358 	/**
   6359 	 * Filters the updated attachment meta data.
   6360 	 *
   6361 	 * @since 2.1.0
   6362 	 *
   6363 	 * @param array $data          Array of updated attachment meta data.
   6364 	 * @param int   $attachment_id Attachment post ID.
   6365 	 */
   6366 	$data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID );
   6367 	if ( $data ) {
   6368 		return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
   6369 	} else {
   6370 		return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
   6371 	}
   6372 }
   6373 
   6374 /**
   6375  * Retrieve the URL for an attachment.
   6376  *
   6377  * @since 2.1.0
   6378  *
   6379  * @global string $pagenow
   6380  *
   6381  * @param int $attachment_id Optional. Attachment post ID. Defaults to global $post.
   6382  * @return string|false Attachment URL, otherwise false.
   6383  */
   6384 function wp_get_attachment_url( $attachment_id = 0 ) {
   6385 	global $pagenow;
   6386 
   6387 	$attachment_id = (int) $attachment_id;
   6388 
   6389 	$post = get_post( $attachment_id );
   6390 
   6391 	if ( ! $post ) {
   6392 		return false;
   6393 	}
   6394 
   6395 	if ( 'attachment' !== $post->post_type ) {
   6396 		return false;
   6397 	}
   6398 
   6399 	$url = '';
   6400 	// Get attached file.
   6401 	$file = get_post_meta( $post->ID, '_wp_attached_file', true );
   6402 	if ( $file ) {
   6403 		// Get upload directory.
   6404 		$uploads = wp_get_upload_dir();
   6405 		if ( $uploads && false === $uploads['error'] ) {
   6406 			// Check that the upload base exists in the file location.
   6407 			if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
   6408 				// Replace file location with url location.
   6409 				$url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
   6410 			} elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
   6411 				// Get the directory name relative to the basedir (back compat for pre-2.7 uploads).
   6412 				$url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . wp_basename( $file );
   6413 			} else {
   6414 				// It's a newly-uploaded file, therefore $file is relative to the basedir.
   6415 				$url = $uploads['baseurl'] . "/$file";
   6416 			}
   6417 		}
   6418 	}
   6419 
   6420 	/*
   6421 	 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
   6422 	 * not recommended to rely upon this.
   6423 	 */
   6424 	if ( ! $url ) {
   6425 		$url = get_the_guid( $post->ID );
   6426 	}
   6427 
   6428 	// On SSL front end, URLs should be HTTPS.
   6429 	if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow ) {
   6430 		$url = set_url_scheme( $url );
   6431 	}
   6432 
   6433 	/**
   6434 	 * Filters the attachment URL.
   6435 	 *
   6436 	 * @since 2.1.0
   6437 	 *
   6438 	 * @param string $url           URL for the given attachment.
   6439 	 * @param int    $attachment_id Attachment post ID.
   6440 	 */
   6441 	$url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
   6442 
   6443 	if ( ! $url ) {
   6444 		return false;
   6445 	}
   6446 
   6447 	return $url;
   6448 }
   6449 
   6450 /**
   6451  * Retrieves the caption for an attachment.
   6452  *
   6453  * @since 4.6.0
   6454  *
   6455  * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
   6456  * @return string|false Attachment caption on success, false on failure.
   6457  */
   6458 function wp_get_attachment_caption( $post_id = 0 ) {
   6459 	$post_id = (int) $post_id;
   6460 	$post    = get_post( $post_id );
   6461 
   6462 	if ( ! $post ) {
   6463 		return false;
   6464 	}
   6465 
   6466 	if ( 'attachment' !== $post->post_type ) {
   6467 		return false;
   6468 	}
   6469 
   6470 	$caption = $post->post_excerpt;
   6471 
   6472 	/**
   6473 	 * Filters the attachment caption.
   6474 	 *
   6475 	 * @since 4.6.0
   6476 	 *
   6477 	 * @param string $caption Caption for the given attachment.
   6478 	 * @param int    $post_id Attachment ID.
   6479 	 */
   6480 	return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
   6481 }
   6482 
   6483 /**
   6484  * Retrieve thumbnail for an attachment.
   6485  *
   6486  * @since 2.1.0
   6487  *
   6488  * @param int $post_id Optional. Attachment ID. Default 0.
   6489  * @return string|false Thumbnail file path on success, false on failure.
   6490  */
   6491 function wp_get_attachment_thumb_file( $post_id = 0 ) {
   6492 	$post_id = (int) $post_id;
   6493 	$post    = get_post( $post_id );
   6494 
   6495 	if ( ! $post ) {
   6496 		return false;
   6497 	}
   6498 
   6499 	$imagedata = wp_get_attachment_metadata( $post->ID );
   6500 	if ( ! is_array( $imagedata ) ) {
   6501 		return false;
   6502 	}
   6503 
   6504 	$file = get_attached_file( $post->ID );
   6505 
   6506 	if ( ! empty( $imagedata['thumb'] ) ) {
   6507 		$thumbfile = str_replace( wp_basename( $file ), $imagedata['thumb'], $file );
   6508 		if ( file_exists( $thumbfile ) ) {
   6509 			/**
   6510 			 * Filters the attachment thumbnail file path.
   6511 			 *
   6512 			 * @since 2.1.0
   6513 			 *
   6514 			 * @param string $thumbfile File path to the attachment thumbnail.
   6515 			 * @param int    $post_id   Attachment ID.
   6516 			 */
   6517 			return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
   6518 		}
   6519 	}
   6520 	return false;
   6521 }
   6522 
   6523 /**
   6524  * Retrieve URL for an attachment thumbnail.
   6525  *
   6526  * @since 2.1.0
   6527  *
   6528  * @param int $post_id Optional. Attachment ID. Default 0.
   6529  * @return string|false Thumbnail URL on success, false on failure.
   6530  */
   6531 function wp_get_attachment_thumb_url( $post_id = 0 ) {
   6532 	$post_id = (int) $post_id;
   6533 	$post    = get_post( $post_id );
   6534 
   6535 	if ( ! $post ) {
   6536 		return false;
   6537 	}
   6538 
   6539 	$url = wp_get_attachment_url( $post->ID );
   6540 	if ( ! $url ) {
   6541 		return false;
   6542 	}
   6543 
   6544 	$sized = image_downsize( $post_id, 'thumbnail' );
   6545 	if ( $sized ) {
   6546 		return $sized[0];
   6547 	}
   6548 
   6549 	$thumb = wp_get_attachment_thumb_file( $post->ID );
   6550 	if ( ! $thumb ) {
   6551 		return false;
   6552 	}
   6553 
   6554 	$url = str_replace( wp_basename( $url ), wp_basename( $thumb ), $url );
   6555 
   6556 	/**
   6557 	 * Filters the attachment thumbnail URL.
   6558 	 *
   6559 	 * @since 2.1.0
   6560 	 *
   6561 	 * @param string $url     URL for the attachment thumbnail.
   6562 	 * @param int    $post_id Attachment ID.
   6563 	 */
   6564 	return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
   6565 }
   6566 
   6567 /**
   6568  * Verifies an attachment is of a given type.
   6569  *
   6570  * @since 4.2.0
   6571  *
   6572  * @param string      $type Attachment type. Accepts 'image', 'audio', or 'video'.
   6573  * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
   6574  * @return bool True if one of the accepted types, false otherwise.
   6575  */
   6576 function wp_attachment_is( $type, $post = null ) {
   6577 	$post = get_post( $post );
   6578 
   6579 	if ( ! $post ) {
   6580 		return false;
   6581 	}
   6582 
   6583 	$file = get_attached_file( $post->ID );
   6584 
   6585 	if ( ! $file ) {
   6586 		return false;
   6587 	}
   6588 
   6589 	if ( 0 === strpos( $post->post_mime_type, $type . '/' ) ) {
   6590 		return true;
   6591 	}
   6592 
   6593 	$check = wp_check_filetype( $file );
   6594 
   6595 	if ( empty( $check['ext'] ) ) {
   6596 		return false;
   6597 	}
   6598 
   6599 	$ext = $check['ext'];
   6600 
   6601 	if ( 'import' !== $post->post_mime_type ) {
   6602 		return $type === $ext;
   6603 	}
   6604 
   6605 	switch ( $type ) {
   6606 		case 'image':
   6607 			$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' );
   6608 			return in_array( $ext, $image_exts, true );
   6609 
   6610 		case 'audio':
   6611 			return in_array( $ext, wp_get_audio_extensions(), true );
   6612 
   6613 		case 'video':
   6614 			return in_array( $ext, wp_get_video_extensions(), true );
   6615 
   6616 		default:
   6617 			return $type === $ext;
   6618 	}
   6619 }
   6620 
   6621 /**
   6622  * Determines whether an attachment is an image.
   6623  *
   6624  * For more information on this and similar theme functions, check out
   6625  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
   6626  * Conditional Tags} article in the Theme Developer Handbook.
   6627  *
   6628  * @since 2.1.0
   6629  * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
   6630  *              allowed WP_Post object to be passed.
   6631  *
   6632  * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
   6633  * @return bool Whether the attachment is an image.
   6634  */
   6635 function wp_attachment_is_image( $post = null ) {
   6636 	return wp_attachment_is( 'image', $post );
   6637 }
   6638 
   6639 /**
   6640  * Retrieve the icon for a MIME type or attachment.
   6641  *
   6642  * @since 2.1.0
   6643  *
   6644  * @param string|int $mime MIME type or attachment ID.
   6645  * @return string|false Icon, false otherwise.
   6646  */
   6647 function wp_mime_type_icon( $mime = 0 ) {
   6648 	if ( ! is_numeric( $mime ) ) {
   6649 		$icon = wp_cache_get( "mime_type_icon_$mime" );
   6650 	}
   6651 
   6652 	$post_id = 0;
   6653 	if ( empty( $icon ) ) {
   6654 		$post_mimes = array();
   6655 		if ( is_numeric( $mime ) ) {
   6656 			$mime = (int) $mime;
   6657 			$post = get_post( $mime );
   6658 			if ( $post ) {
   6659 				$post_id = (int) $post->ID;
   6660 				$file    = get_attached_file( $post_id );
   6661 				$ext     = preg_replace( '/^.+?\.([^.]+)$/', '$1', $file );
   6662 				if ( ! empty( $ext ) ) {
   6663 					$post_mimes[] = $ext;
   6664 					$ext_type     = wp_ext2type( $ext );
   6665 					if ( $ext_type ) {
   6666 						$post_mimes[] = $ext_type;
   6667 					}
   6668 				}
   6669 				$mime = $post->post_mime_type;
   6670 			} else {
   6671 				$mime = 0;
   6672 			}
   6673 		} else {
   6674 			$post_mimes[] = $mime;
   6675 		}
   6676 
   6677 		$icon_files = wp_cache_get( 'icon_files' );
   6678 
   6679 		if ( ! is_array( $icon_files ) ) {
   6680 			/**
   6681 			 * Filters the icon directory path.
   6682 			 *
   6683 			 * @since 2.0.0
   6684 			 *
   6685 			 * @param string $path Icon directory absolute path.
   6686 			 */
   6687 			$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
   6688 
   6689 			/**
   6690 			 * Filters the icon directory URI.
   6691 			 *
   6692 			 * @since 2.0.0
   6693 			 *
   6694 			 * @param string $uri Icon directory URI.
   6695 			 */
   6696 			$icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
   6697 
   6698 			/**
   6699 			 * Filters the array of icon directory URIs.
   6700 			 *
   6701 			 * @since 2.5.0
   6702 			 *
   6703 			 * @param string[] $uris Array of icon directory URIs keyed by directory absolute path.
   6704 			 */
   6705 			$dirs       = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
   6706 			$icon_files = array();
   6707 			while ( $dirs ) {
   6708 				$keys = array_keys( $dirs );
   6709 				$dir  = array_shift( $keys );
   6710 				$uri  = array_shift( $dirs );
   6711 				$dh   = opendir( $dir );
   6712 				if ( $dh ) {
   6713 					while ( false !== $file = readdir( $dh ) ) {
   6714 						$file = wp_basename( $file );
   6715 						if ( '.' === substr( $file, 0, 1 ) ) {
   6716 							continue;
   6717 						}
   6718 
   6719 						$ext = strtolower( substr( $file, -4 ) );
   6720 						if ( ! in_array( $ext, array( '.png', '.gif', '.jpg' ), true ) ) {
   6721 							if ( is_dir( "$dir/$file" ) ) {
   6722 								$dirs[ "$dir/$file" ] = "$uri/$file";
   6723 							}
   6724 							continue;
   6725 						}
   6726 						$icon_files[ "$dir/$file" ] = "$uri/$file";
   6727 					}
   6728 					closedir( $dh );
   6729 				}
   6730 			}
   6731 			wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
   6732 		}
   6733 
   6734 		$types = array();
   6735 		// Icon wp_basename - extension = MIME wildcard.
   6736 		foreach ( $icon_files as $file => $uri ) {
   6737 			$types[ preg_replace( '/^([^.]*).*$/', '$1', wp_basename( $file ) ) ] =& $icon_files[ $file ];
   6738 		}
   6739 
   6740 		if ( ! empty( $mime ) ) {
   6741 			$post_mimes[] = substr( $mime, 0, strpos( $mime, '/' ) );
   6742 			$post_mimes[] = substr( $mime, strpos( $mime, '/' ) + 1 );
   6743 			$post_mimes[] = str_replace( '/', '_', $mime );
   6744 		}
   6745 
   6746 		$matches            = wp_match_mime_types( array_keys( $types ), $post_mimes );
   6747 		$matches['default'] = array( 'default' );
   6748 
   6749 		foreach ( $matches as $match => $wilds ) {
   6750 			foreach ( $wilds as $wild ) {
   6751 				if ( ! isset( $types[ $wild ] ) ) {
   6752 					continue;
   6753 				}
   6754 
   6755 				$icon = $types[ $wild ];
   6756 				if ( ! is_numeric( $mime ) ) {
   6757 					wp_cache_add( "mime_type_icon_$mime", $icon );
   6758 				}
   6759 				break 2;
   6760 			}
   6761 		}
   6762 	}
   6763 
   6764 	/**
   6765 	 * Filters the mime type icon.
   6766 	 *
   6767 	 * @since 2.1.0
   6768 	 *
   6769 	 * @param string $icon    Path to the mime type icon.
   6770 	 * @param string $mime    Mime type.
   6771 	 * @param int    $post_id Attachment ID. Will equal 0 if the function passed
   6772 	 *                        the mime type.
   6773 	 */
   6774 	return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id );
   6775 }
   6776 
   6777 /**
   6778  * Check for changed slugs for published post objects and save the old slug.
   6779  *
   6780  * The function is used when a post object of any type is updated,
   6781  * by comparing the current and previous post objects.
   6782  *
   6783  * If the slug was changed and not already part of the old slugs then it will be
   6784  * added to the post meta field ('_wp_old_slug') for storing old slugs for that
   6785  * post.
   6786  *
   6787  * The most logically usage of this function is redirecting changed post objects, so
   6788  * that those that linked to an changed post will be redirected to the new post.
   6789  *
   6790  * @since 2.1.0
   6791  *
   6792  * @param int     $post_id     Post ID.
   6793  * @param WP_Post $post        The Post Object
   6794  * @param WP_Post $post_before The Previous Post Object
   6795  */
   6796 function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
   6797 	// Don't bother if it hasn't changed.
   6798 	if ( $post->post_name == $post_before->post_name ) {
   6799 		return;
   6800 	}
   6801 
   6802 	// We're only concerned with published, non-hierarchical objects.
   6803 	if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
   6804 		return;
   6805 	}
   6806 
   6807 	$old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
   6808 
   6809 	// If we haven't added this old slug before, add it now.
   6810 	if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs, true ) ) {
   6811 		add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
   6812 	}
   6813 
   6814 	// If the new slug was used previously, delete it from the list.
   6815 	if ( in_array( $post->post_name, $old_slugs, true ) ) {
   6816 		delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
   6817 	}
   6818 }
   6819 
   6820 /**
   6821  * Check for changed dates for published post objects and save the old date.
   6822  *
   6823  * The function is used when a post object of any type is updated,
   6824  * by comparing the current and previous post objects.
   6825  *
   6826  * If the date was changed and not already part of the old dates then it will be
   6827  * added to the post meta field ('_wp_old_date') for storing old dates for that
   6828  * post.
   6829  *
   6830  * The most logically usage of this function is redirecting changed post objects, so
   6831  * that those that linked to an changed post will be redirected to the new post.
   6832  *
   6833  * @since 4.9.3
   6834  *
   6835  * @param int     $post_id     Post ID.
   6836  * @param WP_Post $post        The Post Object
   6837  * @param WP_Post $post_before The Previous Post Object
   6838  */
   6839 function wp_check_for_changed_dates( $post_id, $post, $post_before ) {
   6840 	$previous_date = gmdate( 'Y-m-d', strtotime( $post_before->post_date ) );
   6841 	$new_date      = gmdate( 'Y-m-d', strtotime( $post->post_date ) );
   6842 
   6843 	// Don't bother if it hasn't changed.
   6844 	if ( $new_date == $previous_date ) {
   6845 		return;
   6846 	}
   6847 
   6848 	// We're only concerned with published, non-hierarchical objects.
   6849 	if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
   6850 		return;
   6851 	}
   6852 
   6853 	$old_dates = (array) get_post_meta( $post_id, '_wp_old_date' );
   6854 
   6855 	// If we haven't added this old date before, add it now.
   6856 	if ( ! empty( $previous_date ) && ! in_array( $previous_date, $old_dates, true ) ) {
   6857 		add_post_meta( $post_id, '_wp_old_date', $previous_date );
   6858 	}
   6859 
   6860 	// If the new slug was used previously, delete it from the list.
   6861 	if ( in_array( $new_date, $old_dates, true ) ) {
   6862 		delete_post_meta( $post_id, '_wp_old_date', $new_date );
   6863 	}
   6864 }
   6865 
   6866 /**
   6867  * Retrieve the private post SQL based on capability.
   6868  *
   6869  * This function provides a standardized way to appropriately select on the
   6870  * post_status of a post type. The function will return a piece of SQL code
   6871  * that can be added to a WHERE clause; this SQL is constructed to allow all
   6872  * published posts, and all private posts to which the user has access.
   6873  *
   6874  * @since 2.2.0
   6875  * @since 4.3.0 Added the ability to pass an array to `$post_type`.
   6876  *
   6877  * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
   6878  * @return string SQL code that can be added to a where clause.
   6879  */
   6880 function get_private_posts_cap_sql( $post_type ) {
   6881 	return get_posts_by_author_sql( $post_type, false );
   6882 }
   6883 
   6884 /**
   6885  * Retrieve the post SQL based on capability, author, and type.
   6886  *
   6887  * @since 3.0.0
   6888  * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
   6889  *
   6890  * @see get_private_posts_cap_sql()
   6891  * @global wpdb $wpdb WordPress database abstraction object.
   6892  *
   6893  * @param string|string[] $post_type   Single post type or an array of post types.
   6894  * @param bool            $full        Optional. Returns a full WHERE statement instead of just
   6895  *                                     an 'andalso' term. Default true.
   6896  * @param int             $post_author Optional. Query posts having a single author ID. Default null.
   6897  * @param bool            $public_only Optional. Only return public posts. Skips cap checks for
   6898  *                                     $current_user.  Default false.
   6899  * @return string SQL WHERE code that can be added to a query.
   6900  */
   6901 function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
   6902 	global $wpdb;
   6903 
   6904 	if ( is_array( $post_type ) ) {
   6905 		$post_types = $post_type;
   6906 	} else {
   6907 		$post_types = array( $post_type );
   6908 	}
   6909 
   6910 	$post_type_clauses = array();
   6911 	foreach ( $post_types as $post_type ) {
   6912 		$post_type_obj = get_post_type_object( $post_type );
   6913 		if ( ! $post_type_obj ) {
   6914 			continue;
   6915 		}
   6916 
   6917 		/**
   6918 		 * Filters the capability to read private posts for a custom post type
   6919 		 * when generating SQL for getting posts by author.
   6920 		 *
   6921 		 * @since 2.2.0
   6922 		 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
   6923 		 *
   6924 		 * @param string $cap Capability.
   6925 		 */
   6926 		$cap = apply_filters_deprecated( 'pub_priv_sql_capability', array( '' ), '3.2.0' );
   6927 		if ( ! $cap ) {
   6928 			$cap = current_user_can( $post_type_obj->cap->read_private_posts );
   6929 		}
   6930 
   6931 		// Only need to check the cap if $public_only is false.
   6932 		$post_status_sql = "post_status = 'publish'";
   6933 		if ( false === $public_only ) {
   6934 			if ( $cap ) {
   6935 				// Does the user have the capability to view private posts? Guess so.
   6936 				$post_status_sql .= " OR post_status = 'private'";
   6937 			} elseif ( is_user_logged_in() ) {
   6938 				// Users can view their own private posts.
   6939 				$id = get_current_user_id();
   6940 				if ( null === $post_author || ! $full ) {
   6941 					$post_status_sql .= " OR post_status = 'private' AND post_author = $id";
   6942 				} elseif ( $id == (int) $post_author ) {
   6943 					$post_status_sql .= " OR post_status = 'private'";
   6944 				} // Else none.
   6945 			} // Else none.
   6946 		}
   6947 
   6948 		$post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
   6949 	}
   6950 
   6951 	if ( empty( $post_type_clauses ) ) {
   6952 		return $full ? 'WHERE 1 = 0' : '1 = 0';
   6953 	}
   6954 
   6955 	$sql = '( ' . implode( ' OR ', $post_type_clauses ) . ' )';
   6956 
   6957 	if ( null !== $post_author ) {
   6958 		$sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
   6959 	}
   6960 
   6961 	if ( $full ) {
   6962 		$sql = 'WHERE ' . $sql;
   6963 	}
   6964 
   6965 	return $sql;
   6966 }
   6967 
   6968 /**
   6969  * Retrieves the most recent time that a post on the site was published.
   6970  *
   6971  * The server timezone is the default and is the difference between GMT and
   6972  * server time. The 'blog' value is the date when the last post was posted.
   6973  * The 'gmt' is when the last post was posted in GMT formatted date.
   6974  *
   6975  * @since 0.71
   6976  * @since 4.4.0 The `$post_type` argument was added.
   6977  *
   6978  * @param string $timezone  Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
   6979  *                          'server' uses the server's internal timezone.
   6980  *                          'blog' uses the `post_date` field, which proxies to the timezone set for the site.
   6981  *                          'gmt' uses the `post_date_gmt` field.
   6982  *                          Default 'server'.
   6983  * @param string $post_type Optional. The post type to check. Default 'any'.
   6984  * @return string The date of the last post, or false on failure.
   6985  */
   6986 function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
   6987 	$lastpostdate = _get_last_post_time( $timezone, 'date', $post_type );
   6988 
   6989 	/**
   6990 	 * Filters the most recent time that a post on the site was published.
   6991 	 *
   6992 	 * @since 2.3.0
   6993 	 * @since 5.5.0 Added the `$post_type` parameter.
   6994 	 *
   6995 	 * @param string|false $lastpostdate The most recent time that a post was published,
   6996 	 *                                   in 'Y-m-d H:i:s' format. False on failure.
   6997 	 * @param string       $timezone     Location to use for getting the post published date.
   6998 	 *                                   See get_lastpostdate() for accepted `$timezone` values.
   6999 	 * @param string       $post_type    The post type to check.
   7000 	 */
   7001 	return apply_filters( 'get_lastpostdate', $lastpostdate, $timezone, $post_type );
   7002 }
   7003 
   7004 /**
   7005  * Get the most recent time that a post on the site was modified.
   7006  *
   7007  * The server timezone is the default and is the difference between GMT and
   7008  * server time. The 'blog' value is just when the last post was modified.
   7009  * The 'gmt' is when the last post was modified in GMT time.
   7010  *
   7011  * @since 1.2.0
   7012  * @since 4.4.0 The `$post_type` argument was added.
   7013  *
   7014  * @param string $timezone  Optional. The timezone for the timestamp. See get_lastpostdate()
   7015  *                          for information on accepted values.
   7016  *                          Default 'server'.
   7017  * @param string $post_type Optional. The post type to check. Default 'any'.
   7018  * @return string The timestamp in 'Y-m-d H:i:s' format, or false on failure.
   7019  */
   7020 function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
   7021 	/**
   7022 	 * Pre-filter the return value of get_lastpostmodified() before the query is run.
   7023 	 *
   7024 	 * @since 4.4.0
   7025 	 *
   7026 	 * @param string|false $lastpostmodified The most recent time that a post was modified,
   7027 	 *                                       in 'Y-m-d H:i:s' format, or false. Returning anything
   7028 	 *                                       other than false will short-circuit the function.
   7029 	 * @param string       $timezone         Location to use for getting the post modified date.
   7030 	 *                                       See get_lastpostdate() for accepted `$timezone` values.
   7031 	 * @param string       $post_type        The post type to check.
   7032 	 */
   7033 	$lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
   7034 
   7035 	if ( false !== $lastpostmodified ) {
   7036 		return $lastpostmodified;
   7037 	}
   7038 
   7039 	$lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
   7040 	$lastpostdate     = get_lastpostdate( $timezone, $post_type );
   7041 
   7042 	if ( $lastpostdate > $lastpostmodified ) {
   7043 		$lastpostmodified = $lastpostdate;
   7044 	}
   7045 
   7046 	/**
   7047 	 * Filters the most recent time that a post on the site was modified.
   7048 	 *
   7049 	 * @since 2.3.0
   7050 	 * @since 5.5.0 Added the `$post_type` parameter.
   7051 	 *
   7052 	 * @param string|false $lastpostmodified The most recent time that a post was modified,
   7053 	 *                                       in 'Y-m-d H:i:s' format. False on failure.
   7054 	 * @param string       $timezone         Location to use for getting the post modified date.
   7055 	 *                                       See get_lastpostdate() for accepted `$timezone` values.
   7056 	 * @param string       $post_type        The post type to check.
   7057 	 */
   7058 	return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone, $post_type );
   7059 }
   7060 
   7061 /**
   7062  * Gets the timestamp of the last time any post was modified or published.
   7063  *
   7064  * @since 3.1.0
   7065  * @since 4.4.0 The `$post_type` argument was added.
   7066  * @access private
   7067  *
   7068  * @global wpdb $wpdb WordPress database abstraction object.
   7069  *
   7070  * @param string $timezone  The timezone for the timestamp. See get_lastpostdate().
   7071  *                          for information on accepted values.
   7072  * @param string $field     Post field to check. Accepts 'date' or 'modified'.
   7073  * @param string $post_type Optional. The post type to check. Default 'any'.
   7074  * @return string|false The timestamp in 'Y-m-d H:i:s' format, or false on failure.
   7075  */
   7076 function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
   7077 	global $wpdb;
   7078 
   7079 	if ( ! in_array( $field, array( 'date', 'modified' ), true ) ) {
   7080 		return false;
   7081 	}
   7082 
   7083 	$timezone = strtolower( $timezone );
   7084 
   7085 	$key = "lastpost{$field}:$timezone";
   7086 	if ( 'any' !== $post_type ) {
   7087 		$key .= ':' . sanitize_key( $post_type );
   7088 	}
   7089 
   7090 	$date = wp_cache_get( $key, 'timeinfo' );
   7091 	if ( false !== $date ) {
   7092 		return $date;
   7093 	}
   7094 
   7095 	if ( 'any' === $post_type ) {
   7096 		$post_types = get_post_types( array( 'public' => true ) );
   7097 		array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
   7098 		$post_types = "'" . implode( "', '", $post_types ) . "'";
   7099 	} else {
   7100 		$post_types = "'" . sanitize_key( $post_type ) . "'";
   7101 	}
   7102 
   7103 	switch ( $timezone ) {
   7104 		case 'gmt':
   7105 			$date = $wpdb->get_var( "SELECT post_{$field}_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1" );
   7106 			break;
   7107 		case 'blog':
   7108 			$date = $wpdb->get_var( "SELECT post_{$field} FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1" );
   7109 			break;
   7110 		case 'server':
   7111 			$add_seconds_server = gmdate( 'Z' );
   7112 			$date               = $wpdb->get_var( "SELECT DATE_ADD(post_{$field}_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1" );
   7113 			break;
   7114 	}
   7115 
   7116 	if ( $date ) {
   7117 		wp_cache_set( $key, $date, 'timeinfo' );
   7118 
   7119 		return $date;
   7120 	}
   7121 
   7122 	return false;
   7123 }
   7124 
   7125 /**
   7126  * Updates posts in cache.
   7127  *
   7128  * @since 1.5.1
   7129  *
   7130  * @param WP_Post[] $posts Array of post objects (passed by reference).
   7131  */
   7132 function update_post_cache( &$posts ) {
   7133 	if ( ! $posts ) {
   7134 		return;
   7135 	}
   7136 
   7137 	foreach ( $posts as $post ) {
   7138 		wp_cache_add( $post->ID, $post, 'posts' );
   7139 	}
   7140 }
   7141 
   7142 /**
   7143  * Will clean the post in the cache.
   7144  *
   7145  * Cleaning means delete from the cache of the post. Will call to clean the term
   7146  * object cache associated with the post ID.
   7147  *
   7148  * This function not run if $_wp_suspend_cache_invalidation is not empty. See
   7149  * wp_suspend_cache_invalidation().
   7150  *
   7151  * @since 2.0.0
   7152  *
   7153  * @global bool $_wp_suspend_cache_invalidation
   7154  *
   7155  * @param int|WP_Post $post Post ID or post object to remove from the cache.
   7156  */
   7157 function clean_post_cache( $post ) {
   7158 	global $_wp_suspend_cache_invalidation;
   7159 
   7160 	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
   7161 		return;
   7162 	}
   7163 
   7164 	$post = get_post( $post );
   7165 
   7166 	if ( ! $post ) {
   7167 		return;
   7168 	}
   7169 
   7170 	wp_cache_delete( $post->ID, 'posts' );
   7171 	wp_cache_delete( $post->ID, 'post_meta' );
   7172 
   7173 	clean_object_term_cache( $post->ID, $post->post_type );
   7174 
   7175 	wp_cache_delete( 'wp_get_archives', 'general' );
   7176 
   7177 	/**
   7178 	 * Fires immediately after the given post's cache is cleaned.
   7179 	 *
   7180 	 * @since 2.5.0
   7181 	 *
   7182 	 * @param int     $post_id Post ID.
   7183 	 * @param WP_Post $post    Post object.
   7184 	 */
   7185 	do_action( 'clean_post_cache', $post->ID, $post );
   7186 
   7187 	if ( 'page' === $post->post_type ) {
   7188 		wp_cache_delete( 'all_page_ids', 'posts' );
   7189 
   7190 		/**
   7191 		 * Fires immediately after the given page's cache is cleaned.
   7192 		 *
   7193 		 * @since 2.5.0
   7194 		 *
   7195 		 * @param int $post_id Post ID.
   7196 		 */
   7197 		do_action( 'clean_page_cache', $post->ID );
   7198 	}
   7199 
   7200 	wp_cache_set( 'last_changed', microtime(), 'posts' );
   7201 }
   7202 
   7203 /**
   7204  * Call major cache updating functions for list of Post objects.
   7205  *
   7206  * @since 1.5.0
   7207  *
   7208  * @param WP_Post[] $posts             Array of Post objects
   7209  * @param string    $post_type         Optional. Post type. Default 'post'.
   7210  * @param bool      $update_term_cache Optional. Whether to update the term cache. Default true.
   7211  * @param bool      $update_meta_cache Optional. Whether to update the meta cache. Default true.
   7212  */
   7213 function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
   7214 	// No point in doing all this work if we didn't match any posts.
   7215 	if ( ! $posts ) {
   7216 		return;
   7217 	}
   7218 
   7219 	update_post_cache( $posts );
   7220 
   7221 	$post_ids = array();
   7222 	foreach ( $posts as $post ) {
   7223 		$post_ids[] = $post->ID;
   7224 	}
   7225 
   7226 	if ( ! $post_type ) {
   7227 		$post_type = 'any';
   7228 	}
   7229 
   7230 	if ( $update_term_cache ) {
   7231 		if ( is_array( $post_type ) ) {
   7232 			$ptypes = $post_type;
   7233 		} elseif ( 'any' === $post_type ) {
   7234 			$ptypes = array();
   7235 			// Just use the post_types in the supplied posts.
   7236 			foreach ( $posts as $post ) {
   7237 				$ptypes[] = $post->post_type;
   7238 			}
   7239 			$ptypes = array_unique( $ptypes );
   7240 		} else {
   7241 			$ptypes = array( $post_type );
   7242 		}
   7243 
   7244 		if ( ! empty( $ptypes ) ) {
   7245 			update_object_term_cache( $post_ids, $ptypes );
   7246 		}
   7247 	}
   7248 
   7249 	if ( $update_meta_cache ) {
   7250 		update_postmeta_cache( $post_ids );
   7251 	}
   7252 }
   7253 
   7254 /**
   7255  * Updates metadata cache for list of post IDs.
   7256  *
   7257  * Performs SQL query to retrieve the metadata for the post IDs and updates the
   7258  * metadata cache for the posts. Therefore, the functions, which call this
   7259  * function, do not need to perform SQL queries on their own.
   7260  *
   7261  * @since 2.1.0
   7262  *
   7263  * @param int[] $post_ids Array of post IDs.
   7264  * @return array|false An array of metadata on success, false if there is nothing to update.
   7265  */
   7266 function update_postmeta_cache( $post_ids ) {
   7267 	return update_meta_cache( 'post', $post_ids );
   7268 }
   7269 
   7270 /**
   7271  * Will clean the attachment in the cache.
   7272  *
   7273  * Cleaning means delete from the cache. Optionally will clean the term
   7274  * object cache associated with the attachment ID.
   7275  *
   7276  * This function will not run if $_wp_suspend_cache_invalidation is not empty.
   7277  *
   7278  * @since 3.0.0
   7279  *
   7280  * @global bool $_wp_suspend_cache_invalidation
   7281  *
   7282  * @param int  $id          The attachment ID in the cache to clean.
   7283  * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
   7284  */
   7285 function clean_attachment_cache( $id, $clean_terms = false ) {
   7286 	global $_wp_suspend_cache_invalidation;
   7287 
   7288 	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
   7289 		return;
   7290 	}
   7291 
   7292 	$id = (int) $id;
   7293 
   7294 	wp_cache_delete( $id, 'posts' );
   7295 	wp_cache_delete( $id, 'post_meta' );
   7296 
   7297 	if ( $clean_terms ) {
   7298 		clean_object_term_cache( $id, 'attachment' );
   7299 	}
   7300 
   7301 	/**
   7302 	 * Fires after the given attachment's cache is cleaned.
   7303 	 *
   7304 	 * @since 3.0.0
   7305 	 *
   7306 	 * @param int $id Attachment ID.
   7307 	 */
   7308 	do_action( 'clean_attachment_cache', $id );
   7309 }
   7310 
   7311 //
   7312 // Hooks.
   7313 //
   7314 
   7315 /**
   7316  * Hook for managing future post transitions to published.
   7317  *
   7318  * @since 2.3.0
   7319  * @access private
   7320  *
   7321  * @see wp_clear_scheduled_hook()
   7322  * @global wpdb $wpdb WordPress database abstraction object.
   7323  *
   7324  * @param string  $new_status New post status.
   7325  * @param string  $old_status Previous post status.
   7326  * @param WP_Post $post       Post object.
   7327  */
   7328 function _transition_post_status( $new_status, $old_status, $post ) {
   7329 	global $wpdb;
   7330 
   7331 	if ( 'publish' !== $old_status && 'publish' === $new_status ) {
   7332 		// Reset GUID if transitioning to publish and it is empty.
   7333 		if ( '' === get_the_guid( $post->ID ) ) {
   7334 			$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
   7335 		}
   7336 
   7337 		/**
   7338 		 * Fires when a post's status is transitioned from private to published.
   7339 		 *
   7340 		 * @since 1.5.0
   7341 		 * @deprecated 2.3.0 Use {@see 'private_to_publish'} instead.
   7342 		 *
   7343 		 * @param int $post_id Post ID.
   7344 		 */
   7345 		do_action_deprecated( 'private_to_published', array( $post->ID ), '2.3.0', 'private_to_publish' );
   7346 	}
   7347 
   7348 	// If published posts changed clear the lastpostmodified cache.
   7349 	if ( 'publish' === $new_status || 'publish' === $old_status ) {
   7350 		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
   7351 			wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
   7352 			wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
   7353 			wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
   7354 		}
   7355 	}
   7356 
   7357 	if ( $new_status !== $old_status ) {
   7358 		wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
   7359 		wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
   7360 	}
   7361 
   7362 	// Always clears the hook in case the post status bounced from future to draft.
   7363 	wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
   7364 }
   7365 
   7366 /**
   7367  * Hook used to schedule publication for a post marked for the future.
   7368  *
   7369  * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
   7370  *
   7371  * @since 2.3.0
   7372  * @access private
   7373  *
   7374  * @param int     $deprecated Not used. Can be set to null. Never implemented. Not marked
   7375  *                            as deprecated with _deprecated_argument() as it conflicts with
   7376  *                            wp_transition_post_status() and the default filter for _future_post_hook().
   7377  * @param WP_Post $post       Post object.
   7378  */
   7379 function _future_post_hook( $deprecated, $post ) {
   7380 	wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
   7381 	wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT' ), 'publish_future_post', array( $post->ID ) );
   7382 }
   7383 
   7384 /**
   7385  * Hook to schedule pings and enclosures when a post is published.
   7386  *
   7387  * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
   7388  *
   7389  * @since 2.3.0
   7390  * @access private
   7391  *
   7392  * @param int $post_id The ID in the database table of the post being published.
   7393  */
   7394 function _publish_post_hook( $post_id ) {
   7395 	if ( defined( 'XMLRPC_REQUEST' ) ) {
   7396 		/**
   7397 		 * Fires when _publish_post_hook() is called during an XML-RPC request.
   7398 		 *
   7399 		 * @since 2.1.0
   7400 		 *
   7401 		 * @param int $post_id Post ID.
   7402 		 */
   7403 		do_action( 'xmlrpc_publish_post', $post_id );
   7404 	}
   7405 
   7406 	if ( defined( 'WP_IMPORTING' ) ) {
   7407 		return;
   7408 	}
   7409 
   7410 	if ( get_option( 'default_pingback_flag' ) ) {
   7411 		add_post_meta( $post_id, '_pingme', '1', true );
   7412 	}
   7413 	add_post_meta( $post_id, '_encloseme', '1', true );
   7414 
   7415 	$to_ping = get_to_ping( $post_id );
   7416 	if ( ! empty( $to_ping ) ) {
   7417 		add_post_meta( $post_id, '_trackbackme', '1' );
   7418 	}
   7419 
   7420 	if ( ! wp_next_scheduled( 'do_pings' ) ) {
   7421 		wp_schedule_single_event( time(), 'do_pings' );
   7422 	}
   7423 }
   7424 
   7425 /**
   7426  * Returns the ID of the post's parent.
   7427  *
   7428  * @since 3.1.0
   7429  *
   7430  * @param int|WP_Post $post Post ID or post object. Defaults to global $post.
   7431  * @return int|false Post parent ID (which can be 0 if there is no parent),
   7432  *                   or false if the post does not exist.
   7433  */
   7434 function wp_get_post_parent_id( $post ) {
   7435 	$post = get_post( $post );
   7436 	if ( ! $post || is_wp_error( $post ) ) {
   7437 		return false;
   7438 	}
   7439 	return (int) $post->post_parent;
   7440 }
   7441 
   7442 /**
   7443  * Check the given subset of the post hierarchy for hierarchy loops.
   7444  *
   7445  * Prevents loops from forming and breaks those that it finds. Attached
   7446  * to the {@see 'wp_insert_post_parent'} filter.
   7447  *
   7448  * @since 3.1.0
   7449  *
   7450  * @see wp_find_hierarchy_loop()
   7451  *
   7452  * @param int $post_parent ID of the parent for the post we're checking.
   7453  * @param int $post_ID     ID of the post we're checking.
   7454  * @return int The new post_parent for the post, 0 otherwise.
   7455  */
   7456 function wp_check_post_hierarchy_for_loops( $post_parent, $post_ID ) {
   7457 	// Nothing fancy here - bail.
   7458 	if ( ! $post_parent ) {
   7459 		return 0;
   7460 	}
   7461 
   7462 	// New post can't cause a loop.
   7463 	if ( ! $post_ID ) {
   7464 		return $post_parent;
   7465 	}
   7466 
   7467 	// Can't be its own parent.
   7468 	if ( $post_parent == $post_ID ) {
   7469 		return 0;
   7470 	}
   7471 
   7472 	// Now look for larger loops.
   7473 	$loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_ID, $post_parent );
   7474 	if ( ! $loop ) {
   7475 		return $post_parent; // No loop.
   7476 	}
   7477 
   7478 	// Setting $post_parent to the given value causes a loop.
   7479 	if ( isset( $loop[ $post_ID ] ) ) {
   7480 		return 0;
   7481 	}
   7482 
   7483 	// There's a loop, but it doesn't contain $post_ID. Break the loop.
   7484 	foreach ( array_keys( $loop ) as $loop_member ) {
   7485 		wp_update_post(
   7486 			array(
   7487 				'ID'          => $loop_member,
   7488 				'post_parent' => 0,
   7489 			)
   7490 		);
   7491 	}
   7492 
   7493 	return $post_parent;
   7494 }
   7495 
   7496 /**
   7497  * Sets the post thumbnail (featured image) for the given post.
   7498  *
   7499  * @since 3.1.0
   7500  *
   7501  * @param int|WP_Post $post         Post ID or post object where thumbnail should be attached.
   7502  * @param int         $thumbnail_id Thumbnail to attach.
   7503  * @return int|bool True on success, false on failure.
   7504  */
   7505 function set_post_thumbnail( $post, $thumbnail_id ) {
   7506 	$post         = get_post( $post );
   7507 	$thumbnail_id = absint( $thumbnail_id );
   7508 	if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
   7509 		if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) ) {
   7510 			return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
   7511 		} else {
   7512 			return delete_post_meta( $post->ID, '_thumbnail_id' );
   7513 		}
   7514 	}
   7515 	return false;
   7516 }
   7517 
   7518 /**
   7519  * Removes the thumbnail (featured image) from the given post.
   7520  *
   7521  * @since 3.3.0
   7522  *
   7523  * @param int|WP_Post $post Post ID or post object from which the thumbnail should be removed.
   7524  * @return bool True on success, false on failure.
   7525  */
   7526 function delete_post_thumbnail( $post ) {
   7527 	$post = get_post( $post );
   7528 	if ( $post ) {
   7529 		return delete_post_meta( $post->ID, '_thumbnail_id' );
   7530 	}
   7531 	return false;
   7532 }
   7533 
   7534 /**
   7535  * Delete auto-drafts for new posts that are > 7 days old.
   7536  *
   7537  * @since 3.4.0
   7538  *
   7539  * @global wpdb $wpdb WordPress database abstraction object.
   7540  */
   7541 function wp_delete_auto_drafts() {
   7542 	global $wpdb;
   7543 
   7544 	// Cleanup old auto-drafts more than 7 days old.
   7545 	$old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
   7546 	foreach ( (array) $old_posts as $delete ) {
   7547 		// Force delete.
   7548 		wp_delete_post( $delete, true );
   7549 	}
   7550 }
   7551 
   7552 /**
   7553  * Queues posts for lazy-loading of term meta.
   7554  *
   7555  * @since 4.5.0
   7556  *
   7557  * @param array $posts Array of WP_Post objects.
   7558  */
   7559 function wp_queue_posts_for_term_meta_lazyload( $posts ) {
   7560 	$post_type_taxonomies = array();
   7561 	$term_ids             = array();
   7562 	foreach ( $posts as $post ) {
   7563 		if ( ! ( $post instanceof WP_Post ) ) {
   7564 			continue;
   7565 		}
   7566 
   7567 		if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
   7568 			$post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
   7569 		}
   7570 
   7571 		foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
   7572 			// Term cache should already be primed by `update_post_term_cache()`.
   7573 			$terms = get_object_term_cache( $post->ID, $taxonomy );
   7574 			if ( false !== $terms ) {
   7575 				foreach ( $terms as $term ) {
   7576 					if ( ! in_array( $term->term_id, $term_ids, true ) ) {
   7577 						$term_ids[] = $term->term_id;
   7578 					}
   7579 				}
   7580 			}
   7581 		}
   7582 	}
   7583 
   7584 	if ( $term_ids ) {
   7585 		$lazyloader = wp_metadata_lazyloader();
   7586 		$lazyloader->queue_objects( 'term', $term_ids );
   7587 	}
   7588 }
   7589 
   7590 /**
   7591  * Update the custom taxonomies' term counts when a post's status is changed.
   7592  *
   7593  * For example, default posts term counts (for custom taxonomies) don't include
   7594  * private / draft posts.
   7595  *
   7596  * @since 3.3.0
   7597  * @access private
   7598  *
   7599  * @param string  $new_status New post status.
   7600  * @param string  $old_status Old post status.
   7601  * @param WP_Post $post       Post object.
   7602  */
   7603 function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
   7604 	// Update counts for the post's terms.
   7605 	foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
   7606 		$tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
   7607 		wp_update_term_count( $tt_ids, $taxonomy );
   7608 	}
   7609 }
   7610 
   7611 /**
   7612  * Adds any posts from the given IDs to the cache that do not already exist in cache.
   7613  *
   7614  * @since 3.4.0
   7615  * @access private
   7616  *
   7617  * @see update_post_caches()
   7618  *
   7619  * @global wpdb $wpdb WordPress database abstraction object.
   7620  *
   7621  * @param array $ids               ID list.
   7622  * @param bool  $update_term_cache Optional. Whether to update the term cache. Default true.
   7623  * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
   7624  */
   7625 function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
   7626 	global $wpdb;
   7627 
   7628 	$non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
   7629 	if ( ! empty( $non_cached_ids ) ) {
   7630 		$fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) );
   7631 
   7632 		update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
   7633 	}
   7634 }
   7635 
   7636 /**
   7637  * Adds a suffix if any trashed posts have a given slug.
   7638  *
   7639  * Store its desired (i.e. current) slug so it can try to reclaim it
   7640  * if the post is untrashed.
   7641  *
   7642  * For internal use.
   7643  *
   7644  * @since 4.5.0
   7645  * @access private
   7646  *
   7647  * @param string $post_name Slug.
   7648  * @param int    $post_ID   Optional. Post ID that should be ignored. Default 0.
   7649  */
   7650 function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID = 0 ) {
   7651 	$trashed_posts_with_desired_slug = get_posts(
   7652 		array(
   7653 			'name'         => $post_name,
   7654 			'post_status'  => 'trash',
   7655 			'post_type'    => 'any',
   7656 			'nopaging'     => true,
   7657 			'post__not_in' => array( $post_ID ),
   7658 		)
   7659 	);
   7660 
   7661 	if ( ! empty( $trashed_posts_with_desired_slug ) ) {
   7662 		foreach ( $trashed_posts_with_desired_slug as $_post ) {
   7663 			wp_add_trashed_suffix_to_post_name_for_post( $_post );
   7664 		}
   7665 	}
   7666 }
   7667 
   7668 /**
   7669  * Adds a trashed suffix for a given post.
   7670  *
   7671  * Store its desired (i.e. current) slug so it can try to reclaim it
   7672  * if the post is untrashed.
   7673  *
   7674  * For internal use.
   7675  *
   7676  * @since 4.5.0
   7677  * @access private
   7678  *
   7679  * @param WP_Post $post The post.
   7680  * @return string New slug for the post.
   7681  */
   7682 function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
   7683 	global $wpdb;
   7684 
   7685 	$post = get_post( $post );
   7686 
   7687 	if ( '__trashed' === substr( $post->post_name, -9 ) ) {
   7688 		return $post->post_name;
   7689 	}
   7690 	add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
   7691 	$post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
   7692 	$wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
   7693 	clean_post_cache( $post->ID );
   7694 	return $post_name;
   7695 }
   7696 
   7697 /**
   7698  * Filters the SQL clauses of an attachment query to include filenames.
   7699  *
   7700  * @since 4.7.0
   7701  * @access private
   7702  *
   7703  * @global wpdb $wpdb WordPress database abstraction object.
   7704  *
   7705  * @param string[] $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
   7706  *                          DISTINCT, fields (SELECT), and LIMITS clauses.
   7707  * @return string[] The modified array of clauses.
   7708  */
   7709 function _filter_query_attachment_filenames( $clauses ) {
   7710 	global $wpdb;
   7711 	remove_filter( 'posts_clauses', __FUNCTION__ );
   7712 
   7713 	// Add a LEFT JOIN of the postmeta table so we don't trample existing JOINs.
   7714 	$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
   7715 
   7716 	$clauses['groupby'] = "{$wpdb->posts}.ID";
   7717 
   7718 	$clauses['where'] = preg_replace(
   7719 		"/\({$wpdb->posts}.post_content (NOT LIKE|LIKE) (\'[^']+\')\)/",
   7720 		'$0 OR ( sq1.meta_value $1 $2 )',
   7721 		$clauses['where']
   7722 	);
   7723 
   7724 	return $clauses;
   7725 }
   7726 
   7727 /**
   7728  * Sets the last changed time for the 'posts' cache group.
   7729  *
   7730  * @since 5.0.0
   7731  */
   7732 function wp_cache_set_posts_last_changed() {
   7733 	wp_cache_set( 'last_changed', microtime(), 'posts' );
   7734 }
   7735 
   7736 /**
   7737  * Get all available post MIME types for a given post type.
   7738  *
   7739  * @since 2.5.0
   7740  *
   7741  * @global wpdb $wpdb WordPress database abstraction object.
   7742  *
   7743  * @param string $type
   7744  * @return mixed
   7745  */
   7746 function get_available_post_mime_types( $type = 'attachment' ) {
   7747 	global $wpdb;
   7748 
   7749 	$types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s", $type ) );
   7750 	return $types;
   7751 }
   7752 
   7753 /**
   7754  * Retrieves the path to an uploaded image file.
   7755  *
   7756  * Similar to `get_attached_file()` however some images may have been processed after uploading
   7757  * to make them suitable for web use. In this case the attached "full" size file is usually replaced
   7758  * with a scaled down version of the original image. This function always returns the path
   7759  * to the originally uploaded image file.
   7760  *
   7761  * @since 5.3.0
   7762  * @since 5.4.0 Added the `$unfiltered` parameter.
   7763  *
   7764  * @param int  $attachment_id Attachment ID.
   7765  * @param bool $unfiltered Optional. Passed through to `get_attached_file()`. Default false.
   7766  * @return string|false Path to the original image file or false if the attachment is not an image.
   7767  */
   7768 function wp_get_original_image_path( $attachment_id, $unfiltered = false ) {
   7769 	if ( ! wp_attachment_is_image( $attachment_id ) ) {
   7770 		return false;
   7771 	}
   7772 
   7773 	$image_meta = wp_get_attachment_metadata( $attachment_id );
   7774 	$image_file = get_attached_file( $attachment_id, $unfiltered );
   7775 
   7776 	if ( empty( $image_meta['original_image'] ) ) {
   7777 		$original_image = $image_file;
   7778 	} else {
   7779 		$original_image = path_join( dirname( $image_file ), $image_meta['original_image'] );
   7780 	}
   7781 
   7782 	/**
   7783 	 * Filters the path to the original image.
   7784 	 *
   7785 	 * @since 5.3.0
   7786 	 *
   7787 	 * @param string $original_image Path to original image file.
   7788 	 * @param int    $attachment_id  Attachment ID.
   7789 	 */
   7790 	return apply_filters( 'wp_get_original_image_path', $original_image, $attachment_id );
   7791 }
   7792 
   7793 /**
   7794  * Retrieve the URL to an original attachment image.
   7795  *
   7796  * Similar to `wp_get_attachment_url()` however some images may have been
   7797  * processed after uploading. In this case this function returns the URL
   7798  * to the originally uploaded image file.
   7799  *
   7800  * @since 5.3.0
   7801  *
   7802  * @param int $attachment_id Attachment post ID.
   7803  * @return string|false Attachment image URL, false on error or if the attachment is not an image.
   7804  */
   7805 function wp_get_original_image_url( $attachment_id ) {
   7806 	if ( ! wp_attachment_is_image( $attachment_id ) ) {
   7807 		return false;
   7808 	}
   7809 
   7810 	$image_url = wp_get_attachment_url( $attachment_id );
   7811 
   7812 	if ( ! $image_url ) {
   7813 		return false;
   7814 	}
   7815 
   7816 	$image_meta = wp_get_attachment_metadata( $attachment_id );
   7817 
   7818 	if ( empty( $image_meta['original_image'] ) ) {
   7819 		$original_image_url = $image_url;
   7820 	} else {
   7821 		$original_image_url = path_join( dirname( $image_url ), $image_meta['original_image'] );
   7822 	}
   7823 
   7824 	/**
   7825 	 * Filters the URL to the original attachment image.
   7826 	 *
   7827 	 * @since 5.3.0
   7828 	 *
   7829 	 * @param string $original_image_url URL to original image.
   7830 	 * @param int    $attachment_id      Attachment ID.
   7831 	 */
   7832 	return apply_filters( 'wp_get_original_image_url', $original_image_url, $attachment_id );
   7833 }
   7834 
   7835 /**
   7836  * Filter callback which sets the status of an untrashed post to its previous status.
   7837  *
   7838  * This can be used as a callback on the `wp_untrash_post_status` filter.
   7839  *
   7840  * @since 5.6.0
   7841  *
   7842  * @param string $new_status      The new status of the post being restored.
   7843  * @param int    $post_id         The ID of the post being restored.
   7844  * @param string $previous_status The status of the post at the point where it was trashed.
   7845  * @return string The new status of the post.
   7846  */
   7847 function wp_untrash_post_set_previous_status( $new_status, $post_id, $previous_status ) {
   7848 	return $previous_status;
   7849 }