Make WordPress Core

Ticket #44952: post.php

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