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 */
20function 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 */
554function 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 */
592function 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 */
626function _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 */
697function 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 */
777function 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 */
821function 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 */
864function 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 */
909function 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 */
934function 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 */
955function 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 */
1021function 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 */
1042function 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 */
1060function _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 */
1116function 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 */
1211function 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 */
1238function 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 */
1258function 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 */
1281function 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 */
1293function 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 */
1315function 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 */
1343function 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 */
1505function 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 */
1556function 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 */
1646function 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 */
1705function _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 */
1788function 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 */
1875function _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 */
1918function _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 */
1965function 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 */
1988function 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 */
2004function 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 */
2025function 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 */
2045function 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 */
2067function 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 */
2091function 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 */
2117function 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 */
2147function 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 */
2192function 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 */
2250function 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 */
2276function 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 */
2302function 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 */
2328function 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 */
2346function 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 */
2362function 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 */
2379function 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 */
2394function 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 */
2413function 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 */
2438function 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 */
2463function 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 */
2507function 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 */
2553function 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 */
2708function 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 */
2745function 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 */
2790function _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 */
2823function 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 */
2899function 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 */
2931function 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 */
3041function 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 */
3094function 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 */
3163function 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 */
3314function _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 */
3347function 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 */
3423function 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 */
3523function 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 */
3584function 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 */
3658function 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 */
3684function 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 */
3705function 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 */
3730function 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 */
3834function 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 */
4505function 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 */
4582function 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 */
4660function 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 */
4698function 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 */
4736function 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 */
4921function _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, true );
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 */
4946function 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 */
4964function 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 */
4985function 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 */
5030function 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 */
5088function 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 */
5161function 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 */
5200function 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 */
5241function 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 */
5279function 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 */
5308function 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 */
5336function 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 */
5369function 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 */
5397function 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 */
5415function 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 */
5518function 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 */
5564function 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 */
5605function 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 */
5636function _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 */
5656function 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 */
5729function 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 */
6025function 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 */
6070function 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 */
6106function 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 */
6210function 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 */
6306function 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 */
6349function 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 */
6384function 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 */
6458function 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 */
6491function 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 */
6531function 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 */
6576function 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 */
6635function 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 */
6647function 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 */
6796function 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 */
6839function 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 */
6880function 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 */
6901function 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 */
6986function 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 */
7020function 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 */
7076function _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 */
7132function 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 */
7157function 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 */
7213function 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 */
7266function 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 */
7285function 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 */
7328function _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 */
7379function _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 */
7394function _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 */
7434function 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 */
7456function 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 */
7505function 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 */
7526function 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 */
7541function 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 */
7559function 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 */
7603function _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 */
7625function _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 */
7650function 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 */
7682function 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 */
7709function _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 */
7732function 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 */
7746function 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 */
7768function 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 */
7805function 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 */
7847function wp_untrash_post_set_previous_status( $new_status, $post_id, $previous_status ) {
7848	return $previous_status;
7849}
7850