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