]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/post.php
WordPress 3.9
[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