]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/post.php
Wordpress 3.0-scripts
[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 function create_initial_post_types() {
18         register_post_type( 'post', array(
19                 'public'  => true,
20                 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
21                 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
22                 'capability_type' => 'post',
23                 'hierarchical' => false,
24                 'rewrite' => false,
25                 'query_var' => false,
26                 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions' ),
27         ) );
28
29         register_post_type( 'page', array(
30                 'public' => true,
31                 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
32                 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
33                 'capability_type' => 'page',
34                 'hierarchical' => true,
35                 'rewrite' => false,
36                 'query_var' => false,
37                 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
38         ) );
39
40         register_post_type( 'attachment', array(
41                 'labels' => array(
42                         'name' => __( 'Media' ),
43                 ),
44                 'public' => true,
45                 'show_ui' => false,
46                 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
47                 '_edit_link' => 'media.php?attachment_id=%d', /* internal use only. don't use this when registering your own post type. */
48                 'capability_type' => 'post',
49                 'hierarchical' => false,
50                 'rewrite' => false,
51                 'query_var' => false,
52                 'can_export' => false,
53                 'show_in_nav_menus' => false,
54         ) );
55
56         register_post_type( 'revision', array(
57                 'labels' => array(
58                         'name' => __( 'Revisions' ),
59                         'singular_name' => __( 'Revision' ),
60                 ),
61                 'public' => false,
62                 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
63                 '_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
64                 'capability_type' => 'post',
65                 'hierarchical' => false,
66                 'rewrite' => false,
67                 'query_var' => false,
68         ) );
69
70         register_post_type( 'nav_menu_item', array(
71                 'labels' => array(
72                         'name' => __( 'Navigation Menu Items' ),
73                         'singular_name' => __( 'Navigation Menu Item' ),
74                 ),
75                 'public' => false,
76                 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
77                 'capability_type' => 'post',
78                 'hierarchical' => false,
79                 'rewrite' => false,
80                 'query_var' => false,
81         ) );
82
83         register_post_status( 'publish', array(
84                 'label'       => _x( 'Published', 'post' ),
85                 'public'      => true,
86                 '_builtin'    => true, /* internal use only. */
87                 'label_count' => _n_noop( 'Published <span class="count">(%s)</span>', 'Published <span class="count">(%s)</span>' ),
88         ) );
89
90         register_post_status( 'future', array(
91                 'label'       => _x( 'Scheduled', 'post' ),
92                 'protected'   => true,
93                 '_builtin'    => true, /* internal use only. */
94                 'label_count' => _n_noop('Scheduled <span class="count">(%s)</span>', 'Scheduled <span class="count">(%s)</span>' ),
95         ) );
96
97         register_post_status( 'draft', array(
98                 'label'       => _x( 'Draft', 'post' ),
99                 'protected'   => true,
100                 '_builtin'    => true, /* internal use only. */
101                 'label_count' => _n_noop( 'Draft <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>' ),
102         ) );
103
104         register_post_status( 'pending', array(
105                 'label'       => _x( 'Pending', 'post' ),
106                 'protected'   => true,
107                 '_builtin'    => true, /* internal use only. */
108                 'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>' ),
109         ) );
110
111         register_post_status( 'private', array(
112                 'label'       => _x( 'Private', 'post' ),
113                 'private'     => true,
114                 '_builtin'    => true, /* internal use only. */
115                 'label_count' => _n_noop( 'Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>' ),
116         ) );
117
118         register_post_status( 'trash', array(
119                 'label'       => _x( 'Trash', 'post' ),
120                 'internal'    => true,
121                 '_builtin'    => true, /* internal use only. */
122                 'label_count' => _n_noop( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>' ),
123                 'show_in_admin_status_list' => true,
124         ) );
125
126         register_post_status( 'auto-draft', array(
127                 'label'    => 'auto-draft',
128                 'internal' => true,
129                 '_builtin' => true, /* internal use only. */
130         ) );
131
132         register_post_status( 'inherit', array(
133                 'label'    => 'inherit',
134                 'internal' => true,
135                 '_builtin' => true, /* internal use only. */
136                 'exclude_from_search' => false,
137         ) );
138 }
139 add_action( 'init', 'create_initial_post_types', 0 ); // highest priority
140
141 /**
142  * Retrieve attached file path based on attachment ID.
143  *
144  * You can optionally send it through the 'get_attached_file' filter, but by
145  * default it will just return the file path unfiltered.
146  *
147  * The function works by getting the single post meta name, named
148  * '_wp_attached_file' and returning it. This is a convenience function to
149  * prevent looking up the meta name and provide a mechanism for sending the
150  * attached filename through a filter.
151  *
152  * @since 2.0.0
153  * @uses apply_filters() Calls 'get_attached_file' on file path and attachment ID.
154  *
155  * @param int $attachment_id Attachment ID.
156  * @param bool $unfiltered Whether to apply filters.
157  * @return string The file path to the attached file.
158  */
159 function get_attached_file( $attachment_id, $unfiltered = false ) {
160         $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
161         // If the file is relative, prepend upload dir
162         if ( 0 !== strpos($file, '/') && !preg_match('|^.:\\\|', $file) && ( ($uploads = wp_upload_dir()) && false === $uploads['error'] ) )
163                 $file = $uploads['basedir'] . "/$file";
164         if ( $unfiltered )
165                 return $file;
166         return apply_filters( 'get_attached_file', $file, $attachment_id );
167 }
168
169 /**
170  * Update attachment file path based on attachment ID.
171  *
172  * Used to update the file path of the attachment, which uses post meta name
173  * '_wp_attached_file' to store the path of the attachment.
174  *
175  * @since 2.1.0
176  * @uses apply_filters() Calls 'update_attached_file' on file path and attachment ID.
177  *
178  * @param int $attachment_id Attachment ID
179  * @param string $file File path for the attachment
180  * @return bool False on failure, true on success.
181  */
182 function update_attached_file( $attachment_id, $file ) {
183         if ( !get_post( $attachment_id ) )
184                 return false;
185
186         $file = apply_filters( 'update_attached_file', $file, $attachment_id );
187         $file = _wp_relative_upload_path($file);
188
189         return update_post_meta( $attachment_id, '_wp_attached_file', $file );
190 }
191
192 /**
193  * Return relative path to an uploaded file.
194  *
195  * The path is relative to the current upload dir.
196  *
197  * @since 2.9.0
198  * @uses apply_filters() Calls '_wp_relative_upload_path' on file path.
199  *
200  * @param string $path Full path to the file
201  * @return string relative path on success, unchanged path on failure.
202  */
203 function _wp_relative_upload_path( $path ) {
204         $new_path = $path;
205
206         if ( ($uploads = wp_upload_dir()) && false === $uploads['error'] ) {
207                 if ( 0 === strpos($new_path, $uploads['basedir']) ) {
208                                 $new_path = str_replace($uploads['basedir'], '', $new_path);
209                                 $new_path = ltrim($new_path, '/');
210                 }
211         }
212
213         return apply_filters( '_wp_relative_upload_path', $new_path, $path );
214 }
215
216 /**
217  * Retrieve all children of the post parent ID.
218  *
219  * Normally, without any enhancements, the children would apply to pages. In the
220  * context of the inner workings of WordPress, pages, posts, and attachments
221  * share the same table, so therefore the functionality could apply to any one
222  * of them. It is then noted that while this function does not work on posts, it
223  * does not mean that it won't work on posts. It is recommended that you know
224  * what context you wish to retrieve the children of.
225  *
226  * Attachments may also be made the child of a post, so if that is an accurate
227  * statement (which needs to be verified), it would then be possible to get
228  * all of the attachments for a post. Attachments have since changed since
229  * version 2.5, so this is most likely unaccurate, but serves generally as an
230  * example of what is possible.
231  *
232  * The arguments listed as defaults are for this function and also of the
233  * {@link get_posts()} function. The arguments are combined with the
234  * get_children defaults and are then passed to the {@link get_posts()}
235  * function, which accepts additional arguments. You can replace the defaults in
236  * this function, listed below and the additional arguments listed in the
237  * {@link get_posts()} function.
238  *
239  * The 'post_parent' is the most important argument and important attention
240  * needs to be paid to the $args parameter. If you pass either an object or an
241  * integer (number), then just the 'post_parent' is grabbed and everything else
242  * is lost. If you don't specify any arguments, then it is assumed that you are
243  * in The Loop and the post parent will be grabbed for from the current post.
244  *
245  * The 'post_parent' argument is the ID to get the children. The 'numberposts'
246  * is the amount of posts to retrieve that has a default of '-1', which is
247  * used to get all of the posts. Giving a number higher than 0 will only
248  * retrieve that amount of posts.
249  *
250  * The 'post_type' and 'post_status' arguments can be used to choose what
251  * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
252  * post types are 'post', 'pages', and 'attachments'. The 'post_status'
253  * argument will accept any post status within the write administration panels.
254  *
255  * @see get_posts() Has additional arguments that can be replaced.
256  * @internal Claims made in the long description might be inaccurate.
257  *
258  * @since 2.0.0
259  *
260  * @param mixed $args Optional. User defined arguments for replacing the defaults.
261  * @param string $output Optional. Constant for return type, either OBJECT (default), ARRAY_A, ARRAY_N.
262  * @return array|bool False on failure and the type will be determined by $output parameter.
263  */
264 function &get_children($args = '', $output = OBJECT) {
265         $kids = array();
266         if ( empty( $args ) ) {
267                 if ( isset( $GLOBALS['post'] ) ) {
268                         $args = array('post_parent' => (int) $GLOBALS['post']->post_parent );
269                 } else {
270                         return $kids;
271                 }
272         } elseif ( is_object( $args ) ) {
273                 $args = array('post_parent' => (int) $args->post_parent );
274         } elseif ( is_numeric( $args ) ) {
275                 $args = array('post_parent' => (int) $args);
276         }
277
278         $defaults = array(
279                 'numberposts' => -1, 'post_type' => 'any',
280                 'post_status' => 'any', 'post_parent' => 0,
281         );
282
283         $r = wp_parse_args( $args, $defaults );
284
285         $children = get_posts( $r );
286
287         if ( !$children )
288                 return $kids;
289
290         update_post_cache($children);
291
292         foreach ( $children as $key => $child )
293                 $kids[$child->ID] =& $children[$key];
294
295         if ( $output == OBJECT ) {
296                 return $kids;
297         } elseif ( $output == ARRAY_A ) {
298                 foreach ( (array) $kids as $kid )
299                         $weeuns[$kid->ID] = get_object_vars($kids[$kid->ID]);
300                 return $weeuns;
301         } elseif ( $output == ARRAY_N ) {
302                 foreach ( (array) $kids as $kid )
303                         $babes[$kid->ID] = array_values(get_object_vars($kids[$kid->ID]));
304                 return $babes;
305         } else {
306                 return $kids;
307         }
308 }
309
310 /**
311  * Get extended entry info (<!--more-->).
312  *
313  * There should not be any space after the second dash and before the word
314  * 'more'. There can be text or space(s) after the word 'more', but won't be
315  * referenced.
316  *
317  * The returned array has 'main' and 'extended' keys. Main has the text before
318  * the <code><!--more--></code>. The 'extended' key has the content after the
319  * <code><!--more--></code> comment.
320  *
321  * @since 1.0.0
322  *
323  * @param string $post Post content.
324  * @return array Post before ('main') and after ('extended').
325  */
326 function get_extended($post) {
327         //Match the new style more links
328         if ( preg_match('/<!--more(.*?)?-->/', $post, $matches) ) {
329                 list($main, $extended) = explode($matches[0], $post, 2);
330         } else {
331                 $main = $post;
332                 $extended = '';
333         }
334
335         // Strip leading and trailing whitespace
336         $main = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $main);
337         $extended = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $extended);
338
339         return array('main' => $main, 'extended' => $extended);
340 }
341
342 /**
343  * Retrieves post data given a post ID or post object.
344  *
345  * See {@link sanitize_post()} for optional $filter values. Also, the parameter
346  * $post, must be given as a variable, since it is passed by reference.
347  *
348  * @since 1.5.1
349  * @uses $wpdb
350  * @link http://codex.wordpress.org/Function_Reference/get_post
351  *
352  * @param int|object $post Post ID or post object.
353  * @param string $output Optional, default is Object. Either OBJECT, ARRAY_A, or ARRAY_N.
354  * @param string $filter Optional, default is raw.
355  * @return mixed Post data
356  */
357 function &get_post(&$post, $output = OBJECT, $filter = 'raw') {
358         global $wpdb;
359         $null = null;
360
361         if ( empty($post) ) {
362                 if ( isset($GLOBALS['post']) )
363                         $_post = & $GLOBALS['post'];
364                 else
365                         return $null;
366         } elseif ( is_object($post) && empty($post->filter) ) {
367                 _get_post_ancestors($post);
368                 $_post = sanitize_post($post, 'raw');
369                 wp_cache_add($post->ID, $_post, 'posts');
370         } else {
371                 if ( is_object($post) )
372                         $post_id = $post->ID;
373                 else
374                         $post_id = $post;
375
376                 $post_id = (int) $post_id;
377                 if ( ! $_post = wp_cache_get($post_id, 'posts') ) {
378                         $_post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d LIMIT 1", $post_id));
379                         if ( ! $_post )
380                                 return $null;
381                         _get_post_ancestors($_post);
382                         $_post = sanitize_post($_post, 'raw');
383                         wp_cache_add($_post->ID, $_post, 'posts');
384                 }
385         }
386
387         if ($filter != 'raw')
388                 $_post = sanitize_post($_post, $filter);
389
390         if ( $output == OBJECT ) {
391                 return $_post;
392         } elseif ( $output == ARRAY_A ) {
393                 $__post = get_object_vars($_post);
394                 return $__post;
395         } elseif ( $output == ARRAY_N ) {
396                 $__post = array_values(get_object_vars($_post));
397                 return $__post;
398         } else {
399                 return $_post;
400         }
401 }
402
403 /**
404  * Retrieve ancestors of a post.
405  *
406  * @since 2.5.0
407  *
408  * @param int|object $post Post ID or post object
409  * @return array Ancestor IDs or empty array if none are found.
410  */
411 function get_post_ancestors($post) {
412         $post = get_post($post);
413
414         if ( !empty($post->ancestors) )
415                 return $post->ancestors;
416
417         return array();
418 }
419
420 /**
421  * Retrieve data from a post field based on Post ID.
422  *
423  * Examples of the post field will be, 'post_type', 'post_status', 'content',
424  * etc and based off of the post object property or key names.
425  *
426  * The context values are based off of the taxonomy filter functions and
427  * supported values are found within those functions.
428  *
429  * @since 2.3.0
430  * @uses sanitize_post_field() See for possible $context values.
431  *
432  * @param string $field Post field name
433  * @param id $post Post ID
434  * @param string $context Optional. How to filter the field. Default is display.
435  * @return WP_Error|string Value in post field or WP_Error on failure
436  */
437 function get_post_field( $field, $post, $context = 'display' ) {
438         $post = (int) $post;
439         $post = get_post( $post );
440
441         if ( is_wp_error($post) )
442                 return $post;
443
444         if ( !is_object($post) )
445                 return '';
446
447         if ( !isset($post->$field) )
448                 return '';
449
450         return sanitize_post_field($field, $post->$field, $post->ID, $context);
451 }
452
453 /**
454  * Retrieve the mime type of an attachment based on the ID.
455  *
456  * This function can be used with any post type, but it makes more sense with
457  * attachments.
458  *
459  * @since 2.0.0
460  *
461  * @param int $ID Optional. Post ID.
462  * @return bool|string False on failure or returns the mime type
463  */
464 function get_post_mime_type($ID = '') {
465         $post = & get_post($ID);
466
467         if ( is_object($post) )
468                 return $post->post_mime_type;
469
470         return false;
471 }
472
473 /**
474  * Retrieve the post status based on the Post ID.
475  *
476  * If the post ID is of an attachment, then the parent post status will be given
477  * instead.
478  *
479  * @since 2.0.0
480  *
481  * @param int $ID Post ID
482  * @return string|bool Post status or false on failure.
483  */
484 function get_post_status($ID = '') {
485         $post = get_post($ID);
486
487         if ( !is_object($post) )
488                 return false;
489
490         // Unattached attachments are assumed to be published.
491         if ( ('attachment' == $post->post_type) && ('inherit' == $post->post_status) && ( 0 == $post->post_parent) )
492                 return 'publish';
493
494         if ( ('attachment' == $post->post_type) && $post->post_parent && ($post->ID != $post->post_parent) )
495                 return get_post_status($post->post_parent);
496
497         return $post->post_status;
498 }
499
500 /**
501  * Retrieve all of the WordPress supported post statuses.
502  *
503  * Posts have a limited set of valid status values, this provides the
504  * post_status values and descriptions.
505  *
506  * @since 2.5.0
507  *
508  * @return array List of post statuses.
509  */
510 function get_post_statuses( ) {
511         $status = array(
512                 'draft'                 => __('Draft'),
513                 'pending'               => __('Pending Review'),
514                 'private'               => __('Private'),
515                 'publish'               => __('Published')
516         );
517
518         return $status;
519 }
520
521 /**
522  * Retrieve all of the WordPress support page statuses.
523  *
524  * Pages have a limited set of valid status values, this provides the
525  * post_status values and descriptions.
526  *
527  * @since 2.5.0
528  *
529  * @return array List of page statuses.
530  */
531 function get_page_statuses( ) {
532         $status = array(
533                 'draft'                 => __('Draft'),
534                 'private'               => __('Private'),
535                 'publish'               => __('Published')
536         );
537
538         return $status;
539 }
540
541 /**
542  * Register a post type. Do not use before init.
543  *
544  * A simple function for creating or modifying a post status based on the
545  * parameters given. The function will accept an array (second optional
546  * parameter), along with a string for the post status name.
547  *
548  *
549  * Optional $args contents:
550  *
551  * label - A descriptive name for the post status marked for translation. Defaults to $post_status.
552  * public - Whether posts of this status should be shown in the admin UI. Defaults to true.
553  * exclude_from_search - Whether to exclude posts with this post status from search results. Defaults to true.
554  *
555  * @package WordPress
556  * @subpackage Post
557  * @since 3.0.0
558  * @uses $wp_post_statuses Inserts new post status object into the list
559  *
560  * @param string $post_status Name of the post status.
561  * @param array|string $args See above description.
562  */
563 function register_post_status($post_status, $args = array()) {
564         global $wp_post_statuses;
565
566         if (!is_array($wp_post_statuses))
567                 $wp_post_statuses = array();
568
569         // Args prefixed with an underscore are reserved for internal use.
570         $defaults = array('label' => false, 'label_count' => false, 'exclude_from_search' => null, '_builtin' => false, '_edit_link' => 'post.php?post=%d', 'capability_type' => 'post', 'hierarchical' => false, 'public' => null, 'internal' => null, 'protected' => null, 'private' => null, 'show_in_admin_all' => null, 'publicly_queryable' => null, 'show_in_admin_status_list' => null, 'show_in_admin_all_list' => null, 'single_view_cap' => null);
571         $args = wp_parse_args($args, $defaults);
572         $args = (object) $args;
573
574         $post_status = sanitize_user($post_status, true);
575         $args->name = $post_status;
576
577         if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private )
578                 $args->internal = true;
579
580         if ( null === $args->public  )
581                 $args->public = false;
582
583         if ( null === $args->private  )
584                 $args->private = false;
585
586         if ( null === $args->protected  )
587                 $args->protected = false;
588
589         if ( null === $args->internal  )
590                 $args->internal = false;
591
592         if ( null === $args->publicly_queryable )
593                 $args->publicly_queryable = $args->public;
594
595         if ( null === $args->exclude_from_search )
596                 $args->exclude_from_search = $args->internal;
597
598         if ( null === $args->show_in_admin_all_list )
599                 $args->show_in_admin_all_list = !$args->internal;
600
601         if ( null === $args->show_in_admin_status_list )
602                         $args->show_in_admin_status_list = !$args->internal;
603
604         if ( null === $args->single_view_cap )
605                 $args->single_view_cap = $args->public ? '' : 'edit';
606
607         if ( false === $args->label )
608                 $args->label = $post_status;
609
610         if ( false === $args->label_count )
611                 $args->label_count = array( $args->label, $args->label );
612
613         $wp_post_statuses[$post_status] = $args;
614
615         return $args;
616 }
617
618 /**
619  * Retrieve a post status object by name
620  *
621  * @package WordPress
622  * @subpackage Post
623  * @since 3.0.0
624  * @uses $wp_post_statuses
625  * @see register_post_status
626  * @see get_post_statuses
627  *
628  * @param string $post_type The name of a registered post status
629  * @return object A post status object
630  */
631 function get_post_status_object( $post_status ) {
632         global $wp_post_statuses;
633
634         if ( empty($wp_post_statuses[$post_status]) )
635                 return null;
636
637         return $wp_post_statuses[$post_status];
638 }
639
640 /**
641  * Get a list of all registered post status objects.
642  *
643  * @package WordPress
644  * @subpackage Post
645  * @since 3.0.0
646  * @uses $wp_post_statuses
647  * @see register_post_status
648  * @see get_post_status_object
649  *
650  * @param array|string $args An array of key => value arguments to match against the post status objects.
651  * @param string $output The type of output to return, either post status 'names' or 'objects'. 'names' is the default.
652  * @param string $operator The logical operation to perform. 'or' means only one element
653  *  from the array needs to match; 'and' means all elements must match. The default is 'and'.
654  * @return array A list of post type names or objects
655  */
656 function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
657         global $wp_post_statuses;
658
659         $field = ('names' == $output) ? 'name' : false;
660
661         return wp_filter_object_list($wp_post_statuses, $args, $operator, $field);
662 }
663
664 /**
665  * Whether the post type is hierarchical.
666  *
667  * A false return value might also mean that the post type does not exist.
668  *
669  * @since 3.0.0
670  * @see get_post_type_object
671  *
672  * @param string $post Post type name
673  * @return bool Whether post type is hierarchical.
674  */
675 function is_post_type_hierarchical( $post_type ) {
676         if ( ! post_type_exists( $post_type ) )
677                 return false;
678
679         $post_type = get_post_type_object( $post_type );
680         return $post_type->hierarchical;
681 }
682
683 /**
684  * Checks if a post type is registered.
685  *
686  * @since 3.0.0
687  * @uses get_post_type_object()
688  *
689  * @param string Post type name
690  * @return bool Whether post type is registered.
691  */
692 function post_type_exists( $post_type ) {
693         return (bool) get_post_type_object( $post_type );
694 }
695
696 /**
697  * Retrieve the post type of the current post or of a given post.
698  *
699  * @since 2.1.0
700  *
701  * @uses $post The Loop current post global
702  *
703  * @param mixed $the_post Optional. Post object or post ID.
704  * @return bool|string post type or false on failure.
705  */
706 function get_post_type( $the_post = false ) {
707         global $post;
708
709         if ( false === $the_post )
710                 $the_post = $post;
711         elseif ( is_numeric($the_post) )
712                 $the_post = get_post($the_post);
713
714         if ( is_object($the_post) )
715                 return $the_post->post_type;
716
717         return false;
718 }
719
720 /**
721  * Retrieve a post type object by name
722  *
723  * @package WordPress
724  * @subpackage Post
725  * @since 3.0.0
726  * @uses $wp_post_types
727  * @see register_post_type
728  * @see get_post_types
729  *
730  * @param string $post_type The name of a registered post type
731  * @return object A post type object
732  */
733 function get_post_type_object( $post_type ) {
734         global $wp_post_types;
735
736         if ( empty($wp_post_types[$post_type]) )
737                 return null;
738
739         return $wp_post_types[$post_type];
740 }
741
742 /**
743  * Get a list of all registered post type objects.
744  *
745  * @package WordPress
746  * @subpackage Post
747  * @since 2.9.0
748  * @uses $wp_post_types
749  * @see register_post_type
750  *
751  * @param array|string $args An array of key => value arguments to match against the post type objects.
752  * @param string $output The type of output to return, either post type 'names' or 'objects'. 'names' is the default.
753  * @param string $operator The logical operation to perform. 'or' means only one element
754  *  from the array needs to match; 'and' means all elements must match. The default is 'and'.
755  * @return array A list of post type names or objects
756  */
757 function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
758         global $wp_post_types;
759
760         $field = ('names' == $output) ? 'name' : false;
761
762         return wp_filter_object_list($wp_post_types, $args, $operator, $field);
763 }
764
765 /**
766  * Register a post type. Do not use before init.
767  *
768  * A simple function for creating or modifying a post type based on the
769  * parameters given. The function will accept an array (second optional
770  * parameter), along with a string for the post type name.
771  *
772  *
773  * Optional $args contents:
774  *
775  * - label - Name of the post type shown in the menu. Usually plural. If not set, labels['name'] will be used.
776  * - description - A short descriptive summary of what the post type is. Defaults to blank.
777  * - public - Whether posts of this type should be shown in the admin UI. Defaults to false.
778  * - exclude_from_search - Whether to exclude posts with this post type from search results. Defaults to true if the type is not public, false if the type is public.
779  * - publicly_queryable - Whether post_type queries can be performed from the front page.  Defaults to whatever public is set as.
780  * - show_ui - Whether to generate a default UI for managing this post type. Defaults to true if the type is public, false if the type is not public.
781  * - menu_position - The position in the menu order the post type should appear. Defaults to the bottom.
782  * - menu_icon - The url to the icon to be used for this menu. Defaults to use the posts icon.
783  * - capability_type - The post type to use for checking read, edit, and delete capabilities. Defaults to "post".
784  * - capabilities - Array of capabilities for this post type. You can see accepted values in {@link get_post_type_capabilities()}. By default the capability_type is used to construct capabilities.
785  * - hierarchical - Whether the post type is hierarchical. Defaults to false.
786  * - supports - An alias for calling add_post_type_support() directly. See add_post_type_support() for Documentation. Defaults to none.
787  * - register_meta_box_cb - Provide a callback function that will be called when setting up the meta boxes for the edit form.  Do remove_meta_box() and add_meta_box() calls in the callback.
788  * - taxonomies - An array of taxonomy identifiers that will be registered for the post type.  Default is no taxonomies. Taxonomies can be registered later with register_taxonomy() or register_taxonomy_for_object_type().
789  * - labels - An array of labels for this post type. You can see accepted values in {@link get_post_type_labels()}. By default post labels are used for non-hierarchical types and page labels for hierarchical ones.
790  * - permalink_epmask - The default rewrite endpoint bitmasks.
791  * - rewrite - false to prevent rewrite, or array('slug'=>$slug) to customize permastruct; default will use $post_type as slug.
792  * - query_var - false to prevent queries, or string to value of the query var to use for this post type
793  * - can_export - true allows this post type to be exported.
794  * - show_in_nav_menus - true makes this post type available for selection in navigation menus.
795  * - _builtin - true if this post type is a native or "built-in" post_type.  THIS IS FOR INTERNAL USE ONLY!
796  * - _edit_link - URL segement to use for edit link of this post type.  Set to 'post.php?post=%d'.  THIS IS FOR INTERNAL USE ONLY!
797  *
798  * @since 2.9.0
799  * @uses $wp_post_types Inserts new post type object into the list
800  *
801  * @param string $post_type Name of the post type.
802  * @param array|string $args See above description.
803  * @return object the registered post type object
804  */
805 function register_post_type($post_type, $args = array()) {
806         global $wp_post_types, $wp_rewrite, $wp;
807
808         if ( !is_array($wp_post_types) )
809                 $wp_post_types = array();
810
811         // Args prefixed with an underscore are reserved for internal use.
812         $defaults = array(
813                 'labels' => array(), 'description' => '', 'publicly_queryable' => null, 'exclude_from_search' => null,
814                 '_builtin' => false, '_edit_link' => 'post.php?post=%d', 'capability_type' => 'post', 'capabilities' => array(), 'hierarchical' => false,
815                 'public' => false, 'rewrite' => true, 'query_var' => true, 'supports' => array(), 'register_meta_box_cb' => null,
816                 'taxonomies' => array(), 'show_ui' => null, 'menu_position' => null, 'menu_icon' => null,
817                 'permalink_epmask' => EP_PERMALINK, 'can_export' => true, 'show_in_nav_menus' => null
818         );
819         $args = wp_parse_args($args, $defaults);
820         $args = (object) $args;
821
822         $post_type = sanitize_user($post_type, true);
823         $args->name = $post_type;
824
825         // If not set, default to the setting for public.
826         if ( null === $args->publicly_queryable )
827                 $args->publicly_queryable = $args->public;
828
829         // If not set, default to the setting for public.
830         if ( null === $args->show_ui )
831                 $args->show_ui = $args->public;
832
833         // Whether to show this type in nav-menus.php.  Defaults to the setting for public.
834         if ( null === $args->show_in_nav_menus )
835                 $args->show_in_nav_menus = $args->public;
836
837         // If not set, default to true if not public, false if public.
838         if ( null === $args->exclude_from_search )
839                 $args->exclude_from_search = !$args->public;
840
841         if ( empty($args->capability_type) )
842                 $args->capability_type = 'post';
843
844         $args->cap = get_post_type_capabilities( $args );
845         unset($args->capabilities);
846
847         if ( ! empty($args->supports) ) {
848                 add_post_type_support($post_type, $args->supports);
849                 unset($args->supports);
850         } else {
851                 // Add default features
852                 add_post_type_support($post_type, array('title', 'editor'));
853         }
854
855         if ( false !== $args->query_var && !empty($wp) ) {
856                 if ( true === $args->query_var )
857                         $args->query_var = $post_type;
858                 $args->query_var = sanitize_title_with_dashes($args->query_var);
859                 $wp->add_query_var($args->query_var);
860         }
861
862         if ( false !== $args->rewrite && '' != get_option('permalink_structure') ) {
863                 if ( !is_array($args->rewrite) )
864                         $args->rewrite = array();
865                 if ( !isset($args->rewrite['slug']) )
866                         $args->rewrite['slug'] = $post_type;
867                 if ( !isset($args->rewrite['with_front']) )
868                         $args->rewrite['with_front'] = true;
869                 if ( $args->hierarchical )
870                         $wp_rewrite->add_rewrite_tag("%$post_type%", '(.+?)', $args->query_var ? "{$args->query_var}=" : "post_type=$post_type&name=");
871                 else
872                         $wp_rewrite->add_rewrite_tag("%$post_type%", '([^/]+)', $args->query_var ? "{$args->query_var}=" : "post_type=$post_type&name=");
873                 $wp_rewrite->add_permastruct($post_type, "{$args->rewrite['slug']}/%$post_type%", $args->rewrite['with_front'], $args->permalink_epmask);
874         }
875
876         if ( $args->register_meta_box_cb )
877                 add_action('add_meta_boxes_' . $post_type, $args->register_meta_box_cb, 10, 1);
878
879         $args->labels = get_post_type_labels( $args );
880         $args->label = $args->labels->name;
881
882         $wp_post_types[$post_type] = $args;
883
884         add_action( 'future_' . $post_type, '_future_post_hook', 5, 2 );
885
886         foreach ( $args->taxonomies as $taxonomy ) {
887                 register_taxonomy_for_object_type( $taxonomy, $post_type );
888         }
889
890         return $args;
891 }
892
893 /**
894  * Builds an object with all post type capabilities out of a post type object
895  *
896  * Accepted keys of the capabilities array in the post type object:
897  * - edit_post - The meta capability that controls editing a particular object of this post type. Defaults to "edit_ . $capability_type" (edit_post).
898  * - edit_posts - The capability that controls editing objects of this post type as a class. Defaults to "edit_ . $capability_type . s" (edit_posts).
899  * - edit_others_posts - The capability that controls editing objects of this post type that are owned by other users. Defaults to "edit_others_ . $capability_type . s" (edit_others_posts).
900  * - publish_posts - The capability that controls publishing objects of this post type. Defaults to "publish_ . $capability_type . s" (publish_posts).
901  * - read_post - The meta capability that controls reading a particular object of this post type. Defaults to "read_ . $capability_type" (read_post).
902  * - read_private_posts - The capability that controls reading private posts. Defaults to "read_private . $capability_type . s" (read_private_posts).
903  * - delete_post - The meta capability that controls deleting a particular object of this post type. Defaults to "delete_ . $capability_type" (delete_post).
904  *
905  * @since 3.0.0
906  * @param object $args
907  * @return object object with all the capabilities as member variables
908  */
909 function get_post_type_capabilities( $args ) {
910         $defaults = array(
911                 'edit_post'          => 'edit_'         . $args->capability_type,
912                 'edit_posts'         => 'edit_'         . $args->capability_type . 's',
913                 'edit_others_posts'  => 'edit_others_'  . $args->capability_type . 's',
914                 'publish_posts'      => 'publish_'      . $args->capability_type . 's',
915                 'read_post'          => 'read_'         . $args->capability_type,
916                 'read_private_posts' => 'read_private_' . $args->capability_type . 's',
917                 'delete_post'        => 'delete_'       . $args->capability_type,
918         );
919         $labels = array_merge( $defaults, $args->capabilities );
920         return (object) $labels;
921 }
922
923 /**
924  * Builds an object with all post type labels out of a post type object
925  *
926  * Accepted keys of the label array in the post type object:
927  * - name - general name for the post type, usually plural. The same and overriden by $post_type_object->label. Default is Posts/Pages
928  * - singular_name - name for one object of this post type. Default is Post/Page
929  * - 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>
930  * - add_new_item - Default is Add New Post/Add New Page
931  * - edit_item - Default is Edit Post/Edit Page
932  * - new_item - Default is New Post/New Page
933  * - view_item - Default is View Post/View Page
934  * - search_items - Default is Search Posts/Search Pages
935  * - not_found - Default is No posts found/No pages found
936  * - not_found_in_trash - Default is No posts found in Trash/No pages found in Trash
937  * - parent_item_colon - This string isn't used on non-hierarchical types. In hierarchical ones the default is Parent Page:
938  *
939  * Above, the first default value is for non-hierarchical post types (like posts) and the second one is for hierarchical post types (like pages.)
940  *
941  * @since 3.0.0
942  * @param object $post_type_object
943  * @return object object with all the labels as member variables
944  */
945 function get_post_type_labels( $post_type_object ) {
946         $nohier_vs_hier_defaults = array(
947                 'name' => array( _x('Posts', 'post type general name'), _x('Pages', 'post type general name') ),
948                 'singular_name' => array( _x('Post', 'post type singular name'), _x('Page', 'post type singular name') ),
949                 'add_new' => array( _x('Add New', 'post'), _x('Add New', 'page') ),
950                 'add_new_item' => array( __('Add New Post'), __('Add New Page') ),
951                 'edit_item' => array( __('Edit Post'), __('Edit Page') ),
952                 'new_item' => array( __('New Post'), __('New Page') ),
953                 'view_item' => array( __('View Post'), __('View Page') ),
954                 'search_items' => array( __('Search Posts'), __('Search Pages') ),
955                 'not_found' => array( __('No posts found'), __('No pages found') ),
956                 'not_found_in_trash' => array( __('No posts found in Trash'), __('No pages found in Trash') ),
957                 'parent_item_colon' => array( null, __('Parent Page:') )
958         );
959         return _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
960 }
961
962 /**
963  * Builds an object with custom-something object (post type, taxonomy) labels out of a custom-something object
964  *
965  * @access private
966  */
967 function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
968
969         if ( isset( $object->label ) && empty( $object->labels['name'] ) )
970                 $object->labels['name'] = $object->label;
971
972         if ( !isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) )
973                 $object->labels['singular_name'] = $object->labels['name'];
974
975         $defaults = array_map( create_function( '$x', $object->hierarchical? 'return $x[1];' : 'return $x[0];' ), $nohier_vs_hier_defaults );
976         $labels = array_merge( $defaults, $object->labels );
977         return (object)$labels;
978 }
979
980 /**
981  * Register support of certain features for a post type.
982  *
983  * All features are directly associated with a functional area of the edit screen, such as the
984  * editor or a meta box: 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author',
985  * 'excerpt', 'page-attributes', 'thumbnail', and 'custom-fields'.
986  *
987  * Additionally, the 'revisions' feature dictates whether the post type will store revisions,
988  * and the 'comments' feature dicates whether the comments count will show on the edit screen.
989  *
990  * @since 3.0.0
991  * @param string $post_type The post type for which to add the feature
992  * @param string|array $feature the feature being added, can be an array of feature strings or a single string
993  */
994 function add_post_type_support( $post_type, $feature ) {
995         global $_wp_post_type_features;
996
997         $features = (array) $feature;
998         foreach ($features as $feature) {
999                 if ( func_num_args() == 2 )
1000                         $_wp_post_type_features[$post_type][$feature] = true;
1001                 else
1002                         $_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
1003         }
1004 }
1005
1006 /**
1007  * Remove support for a feature from a post type.
1008  *
1009  * @since 3.0.0
1010  * @param string $post_type The post type for which to remove the feature
1011  * @param string $feature The feature being removed
1012  */
1013 function remove_post_type_support( $post_type, $feature ) {
1014         global $_wp_post_type_features;
1015
1016         if ( !isset($_wp_post_type_features[$post_type]) )
1017                 return;
1018
1019         if ( isset($_wp_post_type_features[$post_type][$feature]) )
1020                 unset($_wp_post_type_features[$post_type][$feature]);
1021 }
1022
1023 /**
1024  * Checks a post type's support for a given feature
1025  *
1026  * @since 3.0.0
1027  * @param string $post_type The post type being checked
1028  * @param string $feature the feature being checked
1029  * @return boolean
1030  */
1031
1032 function post_type_supports( $post_type, $feature ) {
1033         global $_wp_post_type_features;
1034
1035         if ( !isset( $_wp_post_type_features[$post_type][$feature] ) )
1036                 return false;
1037
1038         // If no args passed then no extra checks need be performed
1039         if ( func_num_args() <= 2 )
1040                 return true;
1041
1042         // @todo Allow pluggable arg checking
1043         //$args = array_slice( func_get_args(), 2 );
1044
1045         return true;
1046 }
1047
1048 /**
1049  * Updates the post type for the post ID.
1050  *
1051  * The page or post cache will be cleaned for the post ID.
1052  *
1053  * @since 2.5.0
1054  *
1055  * @uses $wpdb
1056  *
1057  * @param int $post_id Post ID to change post type. Not actually optional.
1058  * @param string $post_type Optional, default is post. Supported values are 'post' or 'page' to
1059  *  name a few.
1060  * @return int Amount of rows changed. Should be 1 for success and 0 for failure.
1061  */
1062 function set_post_type( $post_id = 0, $post_type = 'post' ) {
1063         global $wpdb;
1064
1065         $post_type = sanitize_post_field('post_type', $post_type, $post_id, 'db');
1066         $return = $wpdb->update($wpdb->posts, array('post_type' => $post_type), array('ID' => $post_id) );
1067
1068         if ( 'page' == $post_type )
1069                 clean_page_cache($post_id);
1070         else
1071                 clean_post_cache($post_id);
1072
1073         return $return;
1074 }
1075
1076 /**
1077  * Retrieve list of latest posts or posts matching criteria.
1078  *
1079  * The defaults are as follows:
1080  *     'numberposts' - Default is 5. Total number of posts to retrieve.
1081  *     'offset' - Default is 0. See {@link WP_Query::query()} for more.
1082  *     'category' - What category to pull the posts from.
1083  *     'orderby' - Default is 'post_date'. How to order the posts.
1084  *     'order' - Default is 'DESC'. The order to retrieve the posts.
1085  *     'include' - See {@link WP_Query::query()} for more.
1086  *     'exclude' - See {@link WP_Query::query()} for more.
1087  *     'meta_key' - See {@link WP_Query::query()} for more.
1088  *     'meta_value' - See {@link WP_Query::query()} for more.
1089  *     'post_type' - Default is 'post'. Can be 'page', or 'attachment' to name a few.
1090  *     'post_parent' - The parent of the post or post type.
1091  *     'post_status' - Default is 'published'. Post status to retrieve.
1092  *
1093  * @since 1.2.0
1094  * @uses $wpdb
1095  * @uses WP_Query::query() See for more default arguments and information.
1096  * @link http://codex.wordpress.org/Template_Tags/get_posts
1097  *
1098  * @param array $args Optional. Overrides defaults.
1099  * @return array List of posts.
1100  */
1101 function get_posts($args = null) {
1102         $defaults = array(
1103                 'numberposts' => 5, 'offset' => 0,
1104                 'category' => 0, 'orderby' => 'post_date',
1105                 'order' => 'DESC', 'include' => array(),
1106                 'exclude' => array(), 'meta_key' => '',
1107                 'meta_value' =>'', 'post_type' => 'post',
1108                 'suppress_filters' => true
1109         );
1110
1111         $r = wp_parse_args( $args, $defaults );
1112         if ( empty( $r['post_status'] ) )
1113                 $r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish';
1114         if ( ! empty($r['numberposts']) )
1115                 $r['posts_per_page'] = $r['numberposts'];
1116         if ( ! empty($r['category']) )
1117                 $r['cat'] = $r['category'];
1118         if ( ! empty($r['include']) ) {
1119                 $incposts = wp_parse_id_list( $r['include'] );
1120                 $r['posts_per_page'] = count($incposts);  // only the number of posts included
1121                 $r['post__in'] = $incposts;
1122         } elseif ( ! empty($r['exclude']) )
1123                 $r['post__not_in'] = wp_parse_id_list( $r['exclude'] );
1124
1125         $r['caller_get_posts'] = true;
1126
1127         $get_posts = new WP_Query;
1128         return $get_posts->query($r);
1129
1130 }
1131
1132 //
1133 // Post meta functions
1134 //
1135
1136 /**
1137  * Add meta data field to a post.
1138  *
1139  * Post meta data is called "Custom Fields" on the Administration Panels.
1140  *
1141  * @since 1.5.0
1142  * @uses $wpdb
1143  * @link http://codex.wordpress.org/Function_Reference/add_post_meta
1144  *
1145  * @param int $post_id Post ID.
1146  * @param string $key Metadata name.
1147  * @param mixed $value Metadata value.
1148  * @param bool $unique Optional, default is false. Whether the same key should not be added.
1149  * @return bool False for failure. True for success.
1150  */
1151 function add_post_meta($post_id, $meta_key, $meta_value, $unique = false) {
1152         // make sure meta is added to the post, not a revision
1153         if ( $the_post = wp_is_post_revision($post_id) )
1154                 $post_id = $the_post;
1155
1156         return add_metadata('post', $post_id, $meta_key, $meta_value, $unique);
1157 }
1158
1159 /**
1160  * Remove metadata matching criteria from a post.
1161  *
1162  * You can match based on the key, or key and value. Removing based on key and
1163  * value, will keep from removing duplicate metadata with the same key. It also
1164  * allows removing all metadata matching key, if needed.
1165  *
1166  * @since 1.5.0
1167  * @uses $wpdb
1168  * @link http://codex.wordpress.org/Function_Reference/delete_post_meta
1169  *
1170  * @param int $post_id post ID
1171  * @param string $meta_key Metadata name.
1172  * @param mixed $meta_value Optional. Metadata value.
1173  * @return bool False for failure. True for success.
1174  */
1175 function delete_post_meta($post_id, $meta_key, $meta_value = '') {
1176         // make sure meta is added to the post, not a revision
1177         if ( $the_post = wp_is_post_revision($post_id) )
1178                 $post_id = $the_post;
1179
1180         return delete_metadata('post', $post_id, $meta_key, $meta_value);
1181 }
1182
1183 /**
1184  * Retrieve post meta field for a post.
1185  *
1186  * @since 1.5.0
1187  * @uses $wpdb
1188  * @link http://codex.wordpress.org/Function_Reference/get_post_meta
1189  *
1190  * @param int $post_id Post ID.
1191  * @param string $key The meta key to retrieve.
1192  * @param bool $single Whether to return a single value.
1193  * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
1194  *  is true.
1195  */
1196 function get_post_meta($post_id, $key, $single = false) {
1197         return get_metadata('post', $post_id, $key, $single);
1198 }
1199
1200 /**
1201  * Update post meta field based on post ID.
1202  *
1203  * Use the $prev_value parameter to differentiate between meta fields with the
1204  * same key and post ID.
1205  *
1206  * If the meta field for the post does not exist, it will be added.
1207  *
1208  * @since 1.5
1209  * @uses $wpdb
1210  * @link http://codex.wordpress.org/Function_Reference/update_post_meta
1211  *
1212  * @param int $post_id Post ID.
1213  * @param string $key Metadata key.
1214  * @param mixed $value Metadata value.
1215  * @param mixed $prev_value Optional. Previous value to check before removing.
1216  * @return bool False on failure, true if success.
1217  */
1218 function update_post_meta($post_id, $meta_key, $meta_value, $prev_value = '') {
1219         // make sure meta is added to the post, not a revision
1220         if ( $the_post = wp_is_post_revision($post_id) )
1221                 $post_id = $the_post;
1222
1223         return update_metadata('post', $post_id, $meta_key, $meta_value, $prev_value);
1224 }
1225
1226 /**
1227  * Delete everything from post meta matching meta key.
1228  *
1229  * @since 2.3.0
1230  * @uses $wpdb
1231  *
1232  * @param string $post_meta_key Key to search for when deleting.
1233  * @return bool Whether the post meta key was deleted from the database
1234  */
1235 function delete_post_meta_by_key($post_meta_key) {
1236         if ( !$post_meta_key )
1237                 return false;
1238
1239         global $wpdb;
1240         $post_ids = $wpdb->get_col($wpdb->prepare("SELECT DISTINCT post_id FROM $wpdb->postmeta WHERE meta_key = %s", $post_meta_key));
1241         if ( $post_ids ) {
1242                 $postmetaids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = %s", $post_meta_key ) );
1243                 $in = implode( ',', array_fill(1, count($postmetaids), '%d'));
1244                 do_action( 'delete_postmeta', $postmetaids );
1245                 $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->postmeta WHERE meta_id IN($in)", $postmetaids ));
1246                 do_action( 'deleted_postmeta', $postmetaids );
1247                 foreach ( $post_ids as $post_id )
1248                         wp_cache_delete($post_id, 'post_meta');
1249                 return true;
1250         }
1251         return false;
1252 }
1253
1254 /**
1255  * Retrieve post meta fields, based on post ID.
1256  *
1257  * The post meta fields are retrieved from the cache, so the function is
1258  * optimized to be called more than once. It also applies to the functions, that
1259  * use this function.
1260  *
1261  * @since 1.2.0
1262  * @link http://codex.wordpress.org/Function_Reference/get_post_custom
1263  *
1264  * @uses $id Current Loop Post ID
1265  *
1266  * @param int $post_id post ID
1267  * @return array
1268  */
1269 function get_post_custom($post_id = 0) {
1270         global $id;
1271
1272         if ( !$post_id )
1273                 $post_id = (int) $id;
1274
1275         $post_id = (int) $post_id;
1276
1277         if ( ! wp_cache_get($post_id, 'post_meta') )
1278                 update_postmeta_cache($post_id);
1279
1280         return wp_cache_get($post_id, 'post_meta');
1281 }
1282
1283 /**
1284  * Retrieve meta field names for a post.
1285  *
1286  * If there are no meta fields, then nothing (null) will be returned.
1287  *
1288  * @since 1.2.0
1289  * @link http://codex.wordpress.org/Function_Reference/get_post_custom_keys
1290  *
1291  * @param int $post_id post ID
1292  * @return array|null Either array of the keys, or null if keys could not be retrieved.
1293  */
1294 function get_post_custom_keys( $post_id = 0 ) {
1295         $custom = get_post_custom( $post_id );
1296
1297         if ( !is_array($custom) )
1298                 return;
1299
1300         if ( $keys = array_keys($custom) )
1301                 return $keys;
1302 }
1303
1304 /**
1305  * Retrieve values for a custom post field.
1306  *
1307  * The parameters must not be considered optional. All of the post meta fields
1308  * will be retrieved and only the meta field key values returned.
1309  *
1310  * @since 1.2.0
1311  * @link http://codex.wordpress.org/Function_Reference/get_post_custom_values
1312  *
1313  * @param string $key Meta field key.
1314  * @param int $post_id Post ID
1315  * @return array Meta field values.
1316  */
1317 function get_post_custom_values( $key = '', $post_id = 0 ) {
1318         if ( !$key )
1319                 return null;
1320
1321         $custom = get_post_custom($post_id);
1322
1323         return isset($custom[$key]) ? $custom[$key] : null;
1324 }
1325
1326 /**
1327  * Check if post is sticky.
1328  *
1329  * Sticky posts should remain at the top of The Loop. If the post ID is not
1330  * given, then The Loop ID for the current post will be used.
1331  *
1332  * @since 2.7.0
1333  *
1334  * @param int $post_id Optional. Post ID.
1335  * @return bool Whether post is sticky.
1336  */
1337 function is_sticky($post_id = null) {
1338         global $id;
1339
1340         $post_id = absint($post_id);
1341
1342         if ( !$post_id )
1343                 $post_id = absint($id);
1344
1345         $stickies = get_option('sticky_posts');
1346
1347         if ( !is_array($stickies) )
1348                 return false;
1349
1350         if ( in_array($post_id, $stickies) )
1351                 return true;
1352
1353         return false;
1354 }
1355
1356 /**
1357  * Sanitize every post field.
1358  *
1359  * If the context is 'raw', then the post object or array will get minimal santization of the int fields.
1360  *
1361  * @since 2.3.0
1362  * @uses sanitize_post_field() Used to sanitize the fields.
1363  *
1364  * @param object|array $post The Post Object or Array
1365  * @param string $context Optional, default is 'display'. How to sanitize post fields.
1366  * @return object|array The now sanitized Post Object or Array (will be the same type as $post)
1367  */
1368 function sanitize_post($post, $context = 'display') {
1369         if ( is_object($post) ) {
1370                 // Check if post already filtered for this context
1371                 if ( isset($post->filter) && $context == $post->filter )
1372                         return $post;
1373                 if ( !isset($post->ID) )
1374                         $post->ID = 0;
1375                 foreach ( array_keys(get_object_vars($post)) as $field )
1376                         $post->$field = sanitize_post_field($field, $post->$field, $post->ID, $context);
1377                 $post->filter = $context;
1378         } else {
1379                 // Check if post already filtered for this context
1380                 if ( isset($post['filter']) && $context == $post['filter'] )
1381                         return $post;
1382                 if ( !isset($post['ID']) )
1383                         $post['ID'] = 0;
1384                 foreach ( array_keys($post) as $field )
1385                         $post[$field] = sanitize_post_field($field, $post[$field], $post['ID'], $context);
1386                 $post['filter'] = $context;
1387         }
1388         return $post;
1389 }
1390
1391 /**
1392  * Sanitize post field based on context.
1393  *
1394  * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and 'js'. The
1395  * 'display' context is used by default. 'attribute' and 'js' contexts are treated like 'display'
1396  * when calling filters.
1397  *
1398  * @since 2.3.0
1399  * @uses apply_filters() Calls 'edit_$field' and '${field_no_prefix}_edit_pre' passing $value and
1400  *  $post_id if $context == 'edit' and field name prefix == 'post_'.
1401  *
1402  * @uses apply_filters() Calls 'edit_post_$field' passing $value and $post_id if $context == 'db'.
1403  * @uses apply_filters() Calls 'pre_$field' passing $value if $context == 'db' and field name prefix == 'post_'.
1404  * @uses apply_filters() Calls '${field}_pre' passing $value if $context == 'db' and field name prefix != 'post_'.
1405  *
1406  * @uses apply_filters() Calls '$field' passing $value, $post_id and $context if $context == anything
1407  *  other than 'raw', 'edit' and 'db' and field name prefix == 'post_'.
1408  * @uses apply_filters() Calls 'post_$field' passing $value if $context == anything other than 'raw',
1409  *  'edit' and 'db' and field name prefix != 'post_'.
1410  *
1411  * @param string $field The Post Object field name.
1412  * @param mixed $value The Post Object value.
1413  * @param int $post_id Post ID.
1414  * @param string $context How to sanitize post fields. Looks for 'raw', 'edit', 'db', 'display',
1415  *               'attribute' and 'js'.
1416  * @return mixed Sanitized value.
1417  */
1418 function sanitize_post_field($field, $value, $post_id, $context) {
1419         $int_fields = array('ID', 'post_parent', 'menu_order');
1420         if ( in_array($field, $int_fields) )
1421                 $value = (int) $value;
1422
1423         // Fields which contain arrays of ints.
1424         $array_int_fields = array( 'ancestors' );
1425         if ( in_array($field, $array_int_fields) ) {
1426                 $value = array_map( 'absint', $value);
1427                 return $value;
1428         }
1429
1430         if ( 'raw' == $context )
1431                 return $value;
1432
1433         $prefixed = false;
1434         if ( false !== strpos($field, 'post_') ) {
1435                 $prefixed = true;
1436                 $field_no_prefix = str_replace('post_', '', $field);
1437         }
1438
1439         if ( 'edit' == $context ) {
1440                 $format_to_edit = array('post_content', 'post_excerpt', 'post_title', 'post_password');
1441
1442                 if ( $prefixed ) {
1443                         $value = apply_filters("edit_$field", $value, $post_id);
1444                         // Old school
1445                         $value = apply_filters("${field_no_prefix}_edit_pre", $value, $post_id);
1446                 } else {
1447                         $value = apply_filters("edit_post_$field", $value, $post_id);
1448                 }
1449
1450                 if ( in_array($field, $format_to_edit) ) {
1451                         if ( 'post_content' == $field )
1452                                 $value = format_to_edit($value, user_can_richedit());
1453                         else
1454                                 $value = format_to_edit($value);
1455                 } else {
1456                         $value = esc_attr($value);
1457                 }
1458         } else if ( 'db' == $context ) {
1459                 if ( $prefixed ) {
1460                         $value = apply_filters("pre_$field", $value);
1461                         $value = apply_filters("${field_no_prefix}_save_pre", $value);
1462                 } else {
1463                         $value = apply_filters("pre_post_$field", $value);
1464                         $value = apply_filters("${field}_pre", $value);
1465                 }
1466         } else {
1467                 // Use display filters by default.
1468                 if ( $prefixed )
1469                         $value = apply_filters($field, $value, $post_id, $context);
1470                 else
1471                         $value = apply_filters("post_$field", $value, $post_id, $context);
1472         }
1473
1474         if ( 'attribute' == $context )
1475                 $value = esc_attr($value);
1476         else if ( 'js' == $context )
1477                 $value = esc_js($value);
1478
1479         return $value;
1480 }
1481
1482 /**
1483  * Make a post sticky.
1484  *
1485  * Sticky posts should be displayed at the top of the front page.
1486  *
1487  * @since 2.7.0
1488  *
1489  * @param int $post_id Post ID.
1490  */
1491 function stick_post($post_id) {
1492         $stickies = get_option('sticky_posts');
1493
1494         if ( !is_array($stickies) )
1495                 $stickies = array($post_id);
1496
1497         if ( ! in_array($post_id, $stickies) )
1498                 $stickies[] = $post_id;
1499
1500         update_option('sticky_posts', $stickies);
1501 }
1502
1503 /**
1504  * Unstick a post.
1505  *
1506  * Sticky posts should be displayed at the top of the front page.
1507  *
1508  * @since 2.7.0
1509  *
1510  * @param int $post_id Post ID.
1511  */
1512 function unstick_post($post_id) {
1513         $stickies = get_option('sticky_posts');
1514
1515         if ( !is_array($stickies) )
1516                 return;
1517
1518         if ( ! in_array($post_id, $stickies) )
1519                 return;
1520
1521         $offset = array_search($post_id, $stickies);
1522         if ( false === $offset )
1523                 return;
1524
1525         array_splice($stickies, $offset, 1);
1526
1527         update_option('sticky_posts', $stickies);
1528 }
1529
1530 /**
1531  * Count number of posts of a post type and is user has permissions to view.
1532  *
1533  * This function provides an efficient method of finding the amount of post's
1534  * type a blog has. Another method is to count the amount of items in
1535  * get_posts(), but that method has a lot of overhead with doing so. Therefore,
1536  * when developing for 2.5+, use this function instead.
1537  *
1538  * The $perm parameter checks for 'readable' value and if the user can read
1539  * private posts, it will display that for the user that is signed in.
1540  *
1541  * @since 2.5.0
1542  * @link http://codex.wordpress.org/Template_Tags/wp_count_posts
1543  *
1544  * @param string $type Optional. Post type to retrieve count
1545  * @param string $perm Optional. 'readable' or empty.
1546  * @return object Number of posts for each status
1547  */
1548 function wp_count_posts( $type = 'post', $perm = '' ) {
1549         global $wpdb;
1550
1551         $user = wp_get_current_user();
1552
1553         $cache_key = $type;
1554
1555         $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
1556         if ( 'readable' == $perm && is_user_logged_in() ) {
1557                 $post_type_object = get_post_type_object($type);
1558                 if ( !current_user_can( $post_type_object->cap->read_private_posts ) ) {
1559                         $cache_key .= '_' . $perm . '_' . $user->ID;
1560                         $query .= " AND (post_status != 'private' OR ( post_author = '$user->ID' AND post_status = 'private' ))";
1561                 }
1562         }
1563         $query .= ' GROUP BY post_status';
1564
1565         $count = wp_cache_get($cache_key, 'counts');
1566         if ( false !== $count )
1567                 return $count;
1568
1569         $count = $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
1570
1571         $stats = array();
1572         foreach ( get_post_stati() as $state )
1573                 $stats[$state] = 0;
1574
1575         foreach ( (array) $count as $row )
1576                 $stats[$row['post_status']] = $row['num_posts'];
1577
1578         $stats = (object) $stats;
1579         wp_cache_set($cache_key, $stats, 'counts');
1580
1581         return $stats;
1582 }
1583
1584
1585 /**
1586  * Count number of attachments for the mime type(s).
1587  *
1588  * If you set the optional mime_type parameter, then an array will still be
1589  * returned, but will only have the item you are looking for. It does not give
1590  * you the number of attachments that are children of a post. You can get that
1591  * by counting the number of children that post has.
1592  *
1593  * @since 2.5.0
1594  *
1595  * @param string|array $mime_type Optional. Array or comma-separated list of MIME patterns.
1596  * @return array Number of posts for each mime type.
1597  */
1598 function wp_count_attachments( $mime_type = '' ) {
1599         global $wpdb;
1600
1601         $and = wp_post_mime_type_where( $mime_type );
1602         $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 );
1603
1604         $stats = array( );
1605         foreach( (array) $count as $row ) {
1606                 $stats[$row['post_mime_type']] = $row['num_posts'];
1607         }
1608         $stats['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and");
1609
1610         return (object) $stats;
1611 }
1612
1613 /**
1614  * Check a MIME-Type against a list.
1615  *
1616  * If the wildcard_mime_types parameter is a string, it must be comma separated
1617  * list. If the real_mime_types is a string, it is also comma separated to
1618  * create the list.
1619  *
1620  * @since 2.5.0
1621  *
1622  * @param string|array $wildcard_mime_types e.g. audio/mpeg or image (same as image/*) or
1623  *  flash (same as *flash*).
1624  * @param string|array $real_mime_types post_mime_type values
1625  * @return array array(wildcard=>array(real types))
1626  */
1627 function wp_match_mime_types($wildcard_mime_types, $real_mime_types) {
1628         $matches = array();
1629         if ( is_string($wildcard_mime_types) )
1630                 $wildcard_mime_types = array_map('trim', explode(',', $wildcard_mime_types));
1631         if ( is_string($real_mime_types) )
1632                 $real_mime_types = array_map('trim', explode(',', $real_mime_types));
1633         $wild = '[-._a-z0-9]*';
1634         foreach ( (array) $wildcard_mime_types as $type ) {
1635                 $type = str_replace('*', $wild, $type);
1636                 $patternses[1][$type] = "^$type$";
1637                 if ( false === strpos($type, '/') ) {
1638                         $patternses[2][$type] = "^$type/";
1639                         $patternses[3][$type] = $type;
1640                 }
1641         }
1642         asort($patternses);
1643         foreach ( $patternses as $patterns )
1644                 foreach ( $patterns as $type => $pattern )
1645                         foreach ( (array) $real_mime_types as $real )
1646                                 if ( preg_match("#$pattern#", $real) && ( empty($matches[$type]) || false === array_search($real, $matches[$type]) ) )
1647                                         $matches[$type][] = $real;
1648         return $matches;
1649 }
1650
1651 /**
1652  * Convert MIME types into SQL.
1653  *
1654  * @since 2.5.0
1655  *
1656  * @param string|array $mime_types List of mime types or comma separated string of mime types.
1657  * @param string $table_alias Optional. Specify a table alias, if needed.
1658  * @return string The SQL AND clause for mime searching.
1659  */
1660 function wp_post_mime_type_where($post_mime_types, $table_alias = '') {
1661         $where = '';
1662         $wildcards = array('', '%', '%/%');
1663         if ( is_string($post_mime_types) )
1664                 $post_mime_types = array_map('trim', explode(',', $post_mime_types));
1665         foreach ( (array) $post_mime_types as $mime_type ) {
1666                 $mime_type = preg_replace('/\s/', '', $mime_type);
1667                 $slashpos = strpos($mime_type, '/');
1668                 if ( false !== $slashpos ) {
1669                         $mime_group = preg_replace('/[^-*.a-zA-Z0-9]/', '', substr($mime_type, 0, $slashpos));
1670                         $mime_subgroup = preg_replace('/[^-*.+a-zA-Z0-9]/', '', substr($mime_type, $slashpos + 1));
1671                         if ( empty($mime_subgroup) )
1672                                 $mime_subgroup = '*';
1673                         else
1674                                 $mime_subgroup = str_replace('/', '', $mime_subgroup);
1675                         $mime_pattern = "$mime_group/$mime_subgroup";
1676                 } else {
1677                         $mime_pattern = preg_replace('/[^-*.a-zA-Z0-9]/', '', $mime_type);
1678                         if ( false === strpos($mime_pattern, '*') )
1679                                 $mime_pattern .= '/*';
1680                 }
1681
1682                 $mime_pattern = preg_replace('/\*+/', '%', $mime_pattern);
1683
1684                 if ( in_array( $mime_type, $wildcards ) )
1685                         return '';
1686
1687                 if ( false !== strpos($mime_pattern, '%') )
1688                         $wheres[] = empty($table_alias) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
1689                 else
1690                         $wheres[] = empty($table_alias) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
1691         }
1692         if ( !empty($wheres) )
1693                 $where = ' AND (' . join(' OR ', $wheres) . ') ';
1694         return $where;
1695 }
1696
1697 /**
1698  * Trashes or deletes a post or page.
1699  *
1700  * When the post and page is permanently deleted, everything that is tied to it is deleted also.
1701  * This includes comments, post meta fields, and terms associated with the post.
1702  *
1703  * The post or page is moved to trash instead of permanently deleted unless trash is
1704  * disabled, item is already in the trash, or $force_delete is true.
1705  *
1706  * @since 1.0.0
1707  * @uses do_action() on 'delete_post' before deletion unless post type is 'attachment'.
1708  * @uses do_action() on 'deleted_post' after deletion unless post type is 'attachment'.
1709  * @uses wp_delete_attachment() if post type is 'attachment'.
1710  * @uses wp_trash_post() if item should be trashed.
1711  *
1712  * @param int $postid Post ID.
1713  * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
1714  * @return mixed False on failure
1715  */
1716 function wp_delete_post( $postid = 0, $force_delete = false ) {
1717         global $wpdb, $wp_rewrite;
1718
1719         if ( !$post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $postid)) )
1720                 return $post;
1721
1722         if ( !$force_delete && ( $post->post_type == 'post' || $post->post_type == 'page') && get_post_status( $postid ) != 'trash' && EMPTY_TRASH_DAYS )
1723                         return wp_trash_post($postid);
1724
1725         if ( $post->post_type == 'attachment' )
1726                 return wp_delete_attachment( $postid, $force_delete );
1727
1728         do_action('delete_post', $postid);
1729
1730         delete_post_meta($postid,'_wp_trash_meta_status');
1731         delete_post_meta($postid,'_wp_trash_meta_time');
1732
1733         wp_delete_object_term_relationships($postid, get_object_taxonomies($post->post_type));
1734
1735         $parent_data = array( 'post_parent' => $post->post_parent );
1736         $parent_where = array( 'post_parent' => $postid );
1737
1738         if ( 'page' == $post->post_type) {
1739                 // if the page is defined in option page_on_front or post_for_posts,
1740                 // adjust the corresponding options
1741                 if ( get_option('page_on_front') == $postid ) {
1742                         update_option('show_on_front', 'posts');
1743                         delete_option('page_on_front');
1744                 }
1745                 if ( get_option('page_for_posts') == $postid ) {
1746                         delete_option('page_for_posts');
1747                 }
1748
1749                 // Point children of this page to its parent, also clean the cache of affected children
1750                 $children_query = $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type='page'", $postid);
1751                 $children = $wpdb->get_results($children_query);
1752
1753                 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'page' ) );
1754         } else {
1755                 unstick_post($postid);
1756         }
1757
1758         // Do raw query.  wp_get_post_revisions() is filtered
1759         $revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
1760         // Use wp_delete_post (via wp_delete_post_revision) again.  Ensures any meta/misplaced data gets cleaned up.
1761         foreach ( $revision_ids as $revision_id )
1762                 wp_delete_post_revision( $revision_id );
1763
1764         // Point all attachments to this post up one level
1765         $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
1766
1767         $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ));
1768         if ( ! empty($comment_ids) ) {
1769                 do_action( 'delete_comment', $comment_ids );
1770                 foreach ( $comment_ids as $comment_id )
1771                         wp_delete_comment( $comment_id, true );
1772                 do_action( 'deleted_comment', $comment_ids );
1773         }
1774
1775         $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ));
1776         if ( !empty($post_meta_ids) ) {
1777                 do_action( 'delete_postmeta', $post_meta_ids );
1778                 $in_post_meta_ids = "'" . implode("', '", $post_meta_ids) . "'";
1779                 $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_id IN($in_post_meta_ids)" );
1780                 do_action( 'deleted_postmeta', $post_meta_ids );
1781         }
1782
1783         do_action( 'delete_post', $postid );
1784         $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->posts WHERE ID = %d", $postid ));
1785         do_action( 'deleted_post', $postid );
1786
1787         if ( 'page' == $post->post_type ) {
1788                 clean_page_cache($postid);
1789
1790                 foreach ( (array) $children as $child )
1791                         clean_page_cache($child->ID);
1792
1793                 $wp_rewrite->flush_rules(false);
1794         } else {
1795                 clean_post_cache($postid);
1796         }
1797
1798         wp_clear_scheduled_hook('publish_future_post', array( $postid ) );
1799
1800         do_action('deleted_post', $postid);
1801
1802         return $post;
1803 }
1804
1805 /**
1806  * Moves a post or page to the Trash
1807  *
1808  * If trash is disabled, the post or page is permanently deleted.
1809  *
1810  * @since 2.9.0
1811  * @uses do_action() on 'trash_post' before trashing
1812  * @uses do_action() on 'trashed_post' after trashing
1813  * @uses wp_delete_post() if trash is disabled
1814  *
1815  * @param int $postid Post ID.
1816  * @return mixed False on failure
1817  */
1818 function wp_trash_post($post_id = 0) {
1819         if ( !EMPTY_TRASH_DAYS )
1820                 return wp_delete_post($post_id, true);
1821
1822         if ( !$post = wp_get_single_post($post_id, ARRAY_A) )
1823                 return $post;
1824
1825         if ( $post['post_status'] == 'trash' )
1826                 return false;
1827
1828         do_action('trash_post', $post_id);
1829
1830         add_post_meta($post_id,'_wp_trash_meta_status', $post['post_status']);
1831         add_post_meta($post_id,'_wp_trash_meta_time', time());
1832
1833         $post['post_status'] = 'trash';
1834         wp_insert_post($post);
1835
1836         wp_trash_post_comments($post_id);
1837
1838         do_action('trashed_post', $post_id);
1839
1840         return $post;
1841 }
1842
1843 /**
1844  * Restores a post or page from the Trash
1845  *
1846  * @since 2.9.0
1847  * @uses do_action() on 'untrash_post' before undeletion
1848  * @uses do_action() on 'untrashed_post' after undeletion
1849  *
1850  * @param int $postid Post ID.
1851  * @return mixed False on failure
1852  */
1853 function wp_untrash_post($post_id = 0) {
1854         if ( !$post = wp_get_single_post($post_id, ARRAY_A) )
1855                 return $post;
1856
1857         if ( $post['post_status'] != 'trash' )
1858                 return false;
1859
1860         do_action('untrash_post', $post_id);
1861
1862         $post_status = get_post_meta($post_id, '_wp_trash_meta_status', true);
1863
1864         $post['post_status'] = $post_status;
1865
1866         delete_post_meta($post_id, '_wp_trash_meta_status');
1867         delete_post_meta($post_id, '_wp_trash_meta_time');
1868
1869         wp_insert_post($post);
1870
1871         wp_untrash_post_comments($post_id);
1872
1873         do_action('untrashed_post', $post_id);
1874
1875         return $post;
1876 }
1877
1878 /**
1879  * Moves comments for a post to the trash
1880  *
1881  * @since 2.9.0
1882  * @uses do_action() on 'trash_post_comments' before trashing
1883  * @uses do_action() on 'trashed_post_comments' after trashing
1884  *
1885  * @param int $post Post ID or object.
1886  * @return mixed False on failure
1887  */
1888 function wp_trash_post_comments($post = null) {
1889         global $wpdb;
1890
1891         $post = get_post($post);
1892         if ( empty($post) )
1893                 return;
1894
1895         $post_id = $post->ID;
1896
1897         do_action('trash_post_comments', $post_id);
1898
1899         $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id) );
1900         if ( empty($comments) )
1901                 return;
1902
1903         // Cache current status for each comment
1904         $statuses = array();
1905         foreach ( $comments as $comment )
1906                 $statuses[$comment->comment_ID] = $comment->comment_approved;
1907         add_post_meta($post_id, '_wp_trash_meta_comments_status', $statuses);
1908
1909         // Set status for all comments to post-trashed
1910         $result = $wpdb->update($wpdb->comments, array('comment_approved' => 'post-trashed'), array('comment_post_ID' => $post_id));
1911
1912         clean_comment_cache( array_keys($statuses) );
1913
1914         do_action('trashed_post_comments', $post_id, $statuses);
1915
1916         return $result;
1917 }
1918
1919 /**
1920  * Restore comments for a post from the trash
1921  *
1922  * @since 2.9.0
1923  * @uses do_action() on 'untrash_post_comments' before trashing
1924  * @uses do_action() on 'untrashed_post_comments' after trashing
1925  *
1926  * @param int $post Post ID or object.
1927  * @return mixed False on failure
1928  */
1929 function wp_untrash_post_comments($post = null) {
1930         global $wpdb;
1931
1932         $post = get_post($post);
1933         if ( empty($post) )
1934                 return;
1935
1936         $post_id = $post->ID;
1937
1938         $statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true);
1939
1940         if ( empty($statuses) )
1941                 return true;
1942
1943         do_action('untrash_post_comments', $post_id);
1944
1945         // Restore each comment to its original status
1946         $group_by_status = array();
1947         foreach ( $statuses as $comment_id => $comment_status )
1948                 $group_by_status[$comment_status][] = $comment_id;
1949
1950         foreach ( $group_by_status as $status => $comments ) {
1951                 // Sanity check. This shouldn't happen.
1952                 if ( 'post-trashed' == $status )
1953                         $status = '0';
1954                 $comments_in = implode( "', '", $comments );
1955                 $wpdb->query( "UPDATE $wpdb->comments SET comment_approved = '$status' WHERE comment_ID IN ('" . $comments_in . "')" );
1956         }
1957
1958         clean_comment_cache( array_keys($statuses) );
1959
1960         delete_post_meta($post_id, '_wp_trash_meta_comments_status');
1961
1962         do_action('untrashed_post_comments', $post_id);
1963 }
1964
1965 /**
1966  * Retrieve the list of categories for a post.
1967  *
1968  * Compatibility layer for themes and plugins. Also an easy layer of abstraction
1969  * away from the complexity of the taxonomy layer.
1970  *
1971  * @since 2.1.0
1972  *
1973  * @uses wp_get_object_terms() Retrieves the categories. Args details can be found here.
1974  *
1975  * @param int $post_id Optional. The Post ID.
1976  * @param array $args Optional. Overwrite the defaults.
1977  * @return array
1978  */
1979 function wp_get_post_categories( $post_id = 0, $args = array() ) {
1980         $post_id = (int) $post_id;
1981
1982         $defaults = array('fields' => 'ids');
1983         $args = wp_parse_args( $args, $defaults );
1984
1985         $cats = wp_get_object_terms($post_id, 'category', $args);
1986         return $cats;
1987 }
1988
1989 /**
1990  * Retrieve the tags for a post.
1991  *
1992  * There is only one default for this function, called 'fields' and by default
1993  * is set to 'all'. There are other defaults that can be overridden in
1994  * {@link wp_get_object_terms()}.
1995  *
1996  * @package WordPress
1997  * @subpackage Post
1998  * @since 2.3.0
1999  *
2000  * @uses wp_get_object_terms() Gets the tags for returning. Args can be found here
2001  *
2002  * @param int $post_id Optional. The Post ID
2003  * @param array $args Optional. Overwrite the defaults
2004  * @return array List of post tags.
2005  */
2006 function wp_get_post_tags( $post_id = 0, $args = array() ) {
2007         return wp_get_post_terms( $post_id, 'post_tag', $args);
2008 }
2009
2010 /**
2011  * Retrieve the terms for a post.
2012  *
2013  * There is only one default for this function, called 'fields' and by default
2014  * is set to 'all'. There are other defaults that can be overridden in
2015  * {@link wp_get_object_terms()}.
2016  *
2017  * @package WordPress
2018  * @subpackage Post
2019  * @since 2.8.0
2020  *
2021  * @uses wp_get_object_terms() Gets the tags for returning. Args can be found here
2022  *
2023  * @param int $post_id Optional. The Post ID
2024  * @param string $taxonomy The taxonomy for which to retrieve terms. Defaults to post_tag.
2025  * @param array $args Optional. Overwrite the defaults
2026  * @return array List of post tags.
2027  */
2028 function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
2029         $post_id = (int) $post_id;
2030
2031         $defaults = array('fields' => 'all');
2032         $args = wp_parse_args( $args, $defaults );
2033
2034         $tags = wp_get_object_terms($post_id, $taxonomy, $args);
2035
2036         return $tags;
2037 }
2038
2039 /**
2040  * Retrieve number of recent posts.
2041  *
2042  * @since 1.0.0
2043  * @uses $wpdb
2044  *
2045  * @param int $num Optional, default is 10. Number of posts to get.
2046  * @return array List of posts.
2047  */
2048 function wp_get_recent_posts($num = 10) {
2049         global $wpdb;
2050
2051         // Set the limit clause, if we got a limit
2052         $num = (int) $num;
2053         if ( $num ) {
2054                 $limit = "LIMIT $num";
2055         }
2056
2057         $sql = "SELECT * FROM $wpdb->posts WHERE post_type = 'post' AND post_status IN ( 'draft', 'publish', 'future', 'pending', 'private' ) ORDER BY post_date DESC $limit";
2058         $result = $wpdb->get_results($sql, ARRAY_A);
2059
2060         return $result ? $result : array();
2061 }
2062
2063 /**
2064  * Retrieve a single post, based on post ID.
2065  *
2066  * Has categories in 'post_category' property or key. Has tags in 'tags_input'
2067  * property or key.
2068  *
2069  * @since 1.0.0
2070  *
2071  * @param int $postid Post ID.
2072  * @param string $mode How to return result, either OBJECT, ARRAY_N, or ARRAY_A.
2073  * @return object|array Post object or array holding post contents and information
2074  */
2075 function wp_get_single_post($postid = 0, $mode = OBJECT) {
2076         $postid = (int) $postid;
2077
2078         $post = get_post($postid, $mode);
2079
2080         // Set categories and tags
2081         if ( $mode == OBJECT ) {
2082                 $post->post_category = array();
2083                 if ( is_object_in_taxonomy($post->post_type, 'category') )
2084                         $post->post_category = wp_get_post_categories($postid);
2085                 $post->tags_input = array();
2086                 if ( is_object_in_taxonomy($post->post_type, 'post_tag') )
2087                         $post->tags_input = wp_get_post_tags($postid, array('fields' => 'names'));
2088         } else {
2089                 $post['post_category'] = array();
2090                 if ( is_object_in_taxonomy($post['post_type'], 'category') )
2091                         $post['post_category'] = wp_get_post_categories($postid);
2092                 $post['tags_input'] = array();
2093                 if ( is_object_in_taxonomy($post['post_type'], 'post_tag') )
2094                         $post['tags_input'] = wp_get_post_tags($postid, array('fields' => 'names'));
2095         }
2096
2097         return $post;
2098 }
2099
2100 /**
2101  * Insert a post.
2102  *
2103  * If the $postarr parameter has 'ID' set to a value, then post will be updated.
2104  *
2105  * You can set the post date manually, but setting the values for 'post_date'
2106  * and 'post_date_gmt' keys. You can close the comments or open the comments by
2107  * setting the value for 'comment_status' key.
2108  *
2109  * The defaults for the parameter $postarr are:
2110  *     'post_status'   - Default is 'draft'.
2111  *     'post_type'     - Default is 'post'.
2112  *     'post_author'   - Default is current user ID ($user_ID). The ID of the user who added the post.
2113  *     'ping_status'   - Default is the value in 'default_ping_status' option.
2114  *                       Whether the attachment can accept pings.
2115  *     'post_parent'   - Default is 0. Set this for the post it belongs to, if any.
2116  *     'menu_order'    - Default is 0. The order it is displayed.
2117  *     'to_ping'       - Whether to ping.
2118  *     'pinged'        - Default is empty string.
2119  *     'post_password' - Default is empty string. The password to access the attachment.
2120  *     'guid'          - Global Unique ID for referencing the attachment.
2121  *     'post_content_filtered' - Post content filtered.
2122  *     'post_excerpt'  - Post excerpt.
2123  *
2124  * @since 1.0.0
2125  * @link http://core.trac.wordpress.org/ticket/9084 Bug report on 'wp_insert_post_data' filter.
2126  * @uses $wpdb
2127  * @uses $wp_rewrite
2128  * @uses $user_ID
2129  *
2130  * @uses do_action() Calls 'pre_post_update' on post ID if this is an update.
2131  * @uses do_action() Calls 'edit_post' action on post ID and post data if this is an update.
2132  * @uses do_action() Calls 'save_post' and 'wp_insert_post' on post id and post data just before
2133  *                   returning.
2134  *
2135  * @uses apply_filters() Calls 'wp_insert_post_data' passing $data, $postarr prior to database
2136  *                       update or insert.
2137  * @uses wp_transition_post_status()
2138  *
2139  * @param array $postarr Optional. Overrides defaults.
2140  * @param bool $wp_error Optional. Allow return of WP_Error on failure.
2141  * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
2142  */
2143 function wp_insert_post($postarr = array(), $wp_error = false) {
2144         global $wpdb, $wp_rewrite, $user_ID;
2145
2146         $defaults = array('post_status' => 'draft', 'post_type' => 'post', 'post_author' => $user_ID,
2147                 'ping_status' => get_option('default_ping_status'), 'post_parent' => 0,
2148                 'menu_order' => 0, 'to_ping' =>  '', 'pinged' => '', 'post_password' => '',
2149                 'guid' => '', 'post_content_filtered' => '', 'post_excerpt' => '', 'import_id' => 0,
2150                 'post_content' => '', 'post_title' => '');
2151
2152         $postarr = wp_parse_args($postarr, $defaults);
2153         $postarr = sanitize_post($postarr, 'db');
2154
2155         // export array as variables
2156         extract($postarr, EXTR_SKIP);
2157
2158         // Are we updating or creating?
2159         $update = false;
2160         if ( !empty($ID) ) {
2161                 $update = true;
2162                 $previous_status = get_post_field('post_status', $ID);
2163         } else {
2164                 $previous_status = 'new';
2165         }
2166
2167         if ( ('' == $post_content) && ('' == $post_title) && ('' == $post_excerpt) && ('attachment' != $post_type) ) {
2168                 if ( $wp_error )
2169                         return new WP_Error('empty_content', __('Content, title, and excerpt are empty.'));
2170                 else
2171                         return 0;
2172         }
2173
2174         if ( empty($post_type) )
2175                 $post_type = 'post';
2176
2177         if ( empty($post_status) )
2178                 $post_status = 'draft';
2179
2180         if ( !empty($post_category) )
2181                 $post_category = array_filter($post_category); // Filter out empty terms
2182
2183         // Make sure we set a valid category.
2184         if ( empty($post_category) || 0 == count($post_category) || !is_array($post_category) ) {
2185                 // 'post' requires at least one category.
2186                 if ( 'post' == $post_type && 'auto-draft' != $post_status )
2187                         $post_category = array( get_option('default_category') );
2188                 else
2189                         $post_category = array();
2190         }
2191
2192         if ( empty($post_author) )
2193                 $post_author = $user_ID;
2194
2195         $post_ID = 0;
2196
2197         // Get the post ID and GUID
2198         if ( $update ) {
2199                 $post_ID = (int) $ID;
2200                 $guid = get_post_field( 'guid', $post_ID );
2201                 $post_before = get_post($post_ID);
2202         }
2203
2204         // Don't allow contributors to set to set the post slug for pending review posts
2205         if ( 'pending' == $post_status && !current_user_can( 'publish_posts' ) )
2206                 $post_name = '';
2207
2208         // Create a valid post name.  Drafts and pending posts are allowed to have an empty
2209         // post name.
2210         if ( empty($post_name) ) {
2211                 if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2212                         $post_name = sanitize_title($post_title);
2213                 else
2214                         $post_name = '';
2215         } else {
2216                 $post_name = sanitize_title($post_name);
2217         }
2218
2219         // 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
2220         if ( empty($post_date) || '0000-00-00 00:00:00' == $post_date )
2221                 $post_date = current_time('mysql');
2222
2223         if ( empty($post_date_gmt) || '0000-00-00 00:00:00' == $post_date_gmt ) {
2224                 if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2225                         $post_date_gmt = get_gmt_from_date($post_date);
2226                 else
2227                         $post_date_gmt = '0000-00-00 00:00:00';
2228         }
2229
2230         if ( $update || '0000-00-00 00:00:00' == $post_date ) {
2231                 $post_modified     = current_time( 'mysql' );
2232                 $post_modified_gmt = current_time( 'mysql', 1 );
2233         } else {
2234                 $post_modified     = $post_date;
2235                 $post_modified_gmt = $post_date_gmt;
2236         }
2237
2238         if ( 'publish' == $post_status ) {
2239                 $now = gmdate('Y-m-d H:i:59');
2240                 if ( mysql2date('U', $post_date_gmt, false) > mysql2date('U', $now, false) )
2241                         $post_status = 'future';
2242         } elseif( 'future' == $post_status ) {
2243                 $now = gmdate('Y-m-d H:i:59');
2244                 if ( mysql2date('U', $post_date_gmt, false) <= mysql2date('U', $now, false) )
2245                         $post_status = 'publish';
2246         }
2247
2248         if ( empty($comment_status) ) {
2249                 if ( $update )
2250                         $comment_status = 'closed';
2251                 else
2252                         $comment_status = get_option('default_comment_status');
2253         }
2254         if ( empty($ping_status) )
2255                 $ping_status = get_option('default_ping_status');
2256
2257         if ( isset($to_ping) )
2258                 $to_ping = preg_replace('|\s+|', "\n", $to_ping);
2259         else
2260                 $to_ping = '';
2261
2262         if ( ! isset($pinged) )
2263                 $pinged = '';
2264
2265         if ( isset($post_parent) )
2266                 $post_parent = (int) $post_parent;
2267         else
2268                 $post_parent = 0;
2269
2270         if ( !empty($post_ID) ) {
2271                 if ( $post_parent == $post_ID ) {
2272                         // Post can't be its own parent
2273                         $post_parent = 0;
2274                 } elseif ( !empty($post_parent) ) {
2275                         $parent_post = get_post($post_parent);
2276                         // Check for circular dependency
2277                         if ( isset( $parent_post->post_parent ) && $parent_post->post_parent == $post_ID )
2278                                 $post_parent = 0;
2279                 }
2280         }
2281
2282         if ( isset($menu_order) )
2283                 $menu_order = (int) $menu_order;
2284         else
2285                 $menu_order = 0;
2286
2287         if ( !isset($post_password) || 'private' == $post_status )
2288                 $post_password = '';
2289
2290         $post_name = wp_unique_post_slug($post_name, $post_ID, $post_status, $post_type, $post_parent);
2291
2292         // expected_slashed (everything!)
2293         $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' ) );
2294         $data = apply_filters('wp_insert_post_data', $data, $postarr);
2295         $data = stripslashes_deep( $data );
2296         $where = array( 'ID' => $post_ID );
2297
2298         if ( $update ) {
2299                 do_action( 'pre_post_update', $post_ID );
2300                 if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
2301                         if ( $wp_error )
2302                                 return new WP_Error('db_update_error', __('Could not update post in the database'), $wpdb->last_error);
2303                         else
2304                                 return 0;
2305                 }
2306         } else {
2307                 if ( isset($post_mime_type) )
2308                         $data['post_mime_type'] = stripslashes( $post_mime_type ); // This isn't in the update
2309                 // If there is a suggested ID, use it if not already present
2310                 if ( !empty($import_id) ) {
2311                         $import_id = (int) $import_id;
2312                         if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
2313                                 $data['ID'] = $import_id;
2314                         }
2315                 }
2316                 if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
2317                         if ( $wp_error )
2318                                 return new WP_Error('db_insert_error', __('Could not insert post into the database'), $wpdb->last_error);
2319                         else
2320                                 return 0;
2321                 }
2322                 $post_ID = (int) $wpdb->insert_id;
2323
2324                 // use the newly generated $post_ID
2325                 $where = array( 'ID' => $post_ID );
2326         }
2327
2328         if ( empty($data['post_name']) && !in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
2329                 $data['post_name'] = sanitize_title($data['post_title'], $post_ID);
2330                 $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
2331         }
2332
2333         if ( is_object_in_taxonomy($post_type, 'category') )
2334                 wp_set_post_categories( $post_ID, $post_category );
2335
2336         if ( isset( $tags_input ) && is_object_in_taxonomy($post_type, 'post_tag') )
2337                 wp_set_post_tags( $post_ID, $tags_input );
2338
2339         // new-style support for all custom taxonomies
2340         if ( !empty($tax_input) ) {
2341                 foreach ( $tax_input as $taxonomy => $tags ) {
2342                         $taxonomy_obj = get_taxonomy($taxonomy);
2343                         if ( is_array($tags) ) // array = hierarchical, string = non-hierarchical.
2344                                 $tags = array_filter($tags);
2345                         if ( current_user_can($taxonomy_obj->cap->assign_terms) )
2346                                 wp_set_post_terms( $post_ID, $tags, $taxonomy );
2347                 }
2348         }
2349
2350         $current_guid = get_post_field( 'guid', $post_ID );
2351
2352         if ( 'page' == $data['post_type'] )
2353                 clean_page_cache($post_ID);
2354         else
2355                 clean_post_cache($post_ID);
2356
2357         // Set GUID
2358         if ( !$update && '' == $current_guid )
2359                 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
2360
2361         $post = get_post($post_ID);
2362
2363         if ( !empty($page_template) && 'page' == $data['post_type'] ) {
2364                 $post->page_template = $page_template;
2365                 $page_templates = get_page_templates();
2366                 if ( 'default' != $page_template && !in_array($page_template, $page_templates) ) {
2367                         if ( $wp_error )
2368                                 return new WP_Error('invalid_page_template', __('The page template is invalid.'));
2369                         else
2370                                 return 0;
2371                 }
2372                 update_post_meta($post_ID, '_wp_page_template',  $page_template);
2373         }
2374
2375         wp_transition_post_status($data['post_status'], $previous_status, $post);
2376
2377         if ( $update ) {
2378                 do_action('edit_post', $post_ID, $post);
2379                 $post_after = get_post($post_ID);
2380                 do_action( 'post_updated', $post_ID, $post_after, $post_before);
2381         }
2382
2383         do_action('save_post', $post_ID, $post);
2384         do_action('wp_insert_post', $post_ID, $post);
2385
2386         return $post_ID;
2387 }
2388
2389 /**
2390  * Update a post with new post data.
2391  *
2392  * The date does not have to be set for drafts. You can set the date and it will
2393  * not be overridden.
2394  *
2395  * @since 1.0.0
2396  *
2397  * @param array|object $postarr Post data. Arrays are expected to be escaped, objects are not.
2398  * @return int 0 on failure, Post ID on success.
2399  */
2400 function wp_update_post($postarr = array()) {
2401         if ( is_object($postarr) ) {
2402                 // non-escaped post was passed
2403                 $postarr = get_object_vars($postarr);
2404                 $postarr = add_magic_quotes($postarr);
2405         }
2406
2407         // First, get all of the original fields
2408         $post = wp_get_single_post($postarr['ID'], ARRAY_A);
2409
2410         // Escape data pulled from DB.
2411         $post = add_magic_quotes($post);
2412
2413         // Passed post category list overwrites existing category list if not empty.
2414         if ( isset($postarr['post_category']) && is_array($postarr['post_category'])
2415                          && 0 != count($postarr['post_category']) )
2416                 $post_cats = $postarr['post_category'];
2417         else
2418                 $post_cats = $post['post_category'];
2419
2420         // Drafts shouldn't be assigned a date unless explicitly done so by the user
2421         if ( in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) && empty($postarr['edit_date']) &&
2422                          ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
2423                 $clear_date = true;
2424         else
2425                 $clear_date = false;
2426
2427         // Merge old and new fields with new fields overwriting old ones.
2428         $postarr = array_merge($post, $postarr);
2429         $postarr['post_category'] = $post_cats;
2430         if ( $clear_date ) {
2431                 $postarr['post_date'] = current_time('mysql');
2432                 $postarr['post_date_gmt'] = '';
2433         }
2434
2435         if ($postarr['post_type'] == 'attachment')
2436                 return wp_insert_attachment($postarr);
2437
2438         return wp_insert_post($postarr);
2439 }
2440
2441 /**
2442  * Publish a post by transitioning the post status.
2443  *
2444  * @since 2.1.0
2445  * @uses $wpdb
2446  * @uses do_action() Calls 'edit_post', 'save_post', and 'wp_insert_post' on post_id and post data.
2447  *
2448  * @param int $post_id Post ID.
2449  * @return null
2450  */
2451 function wp_publish_post($post_id) {
2452         global $wpdb;
2453
2454         $post = get_post($post_id);
2455
2456         if ( empty($post) )
2457                 return;
2458
2459         if ( 'publish' == $post->post_status )
2460                 return;
2461
2462         $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post_id ) );
2463
2464         $old_status = $post->post_status;
2465         $post->post_status = 'publish';
2466         wp_transition_post_status('publish', $old_status, $post);
2467
2468         // Update counts for the post's terms.
2469         foreach ( (array) get_object_taxonomies('post') as $taxonomy ) {
2470                 $tt_ids = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'tt_ids'));
2471                 wp_update_term_count($tt_ids, $taxonomy);
2472         }
2473
2474         do_action('edit_post', $post_id, $post);
2475         do_action('save_post', $post_id, $post);
2476         do_action('wp_insert_post', $post_id, $post);
2477 }
2478
2479 /**
2480  * Publish future post and make sure post ID has future post status.
2481  *
2482  * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
2483  * from publishing drafts, etc.
2484  *
2485  * @since 2.5.0
2486  *
2487  * @param int $post_id Post ID.
2488  * @return null Nothing is returned. Which can mean that no action is required or post was published.
2489  */
2490 function check_and_publish_future_post($post_id) {
2491
2492         $post = get_post($post_id);
2493
2494         if ( empty($post) )
2495                 return;
2496
2497         if ( 'future' != $post->post_status )
2498                 return;
2499
2500         $time = strtotime( $post->post_date_gmt . ' GMT' );
2501
2502         if ( $time > time() ) { // Uh oh, someone jumped the gun!
2503                 wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
2504                 wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
2505                 return;
2506         }
2507
2508         return wp_publish_post($post_id);
2509 }
2510
2511
2512 /**
2513  * Computes a unique slug for the post, when given the desired slug and some post details.
2514  *
2515  * @global wpdb $wpdb
2516  * @global WP_Rewrite $wp_rewrite
2517  * @param string $slug the desired slug (post_name)
2518  * @param integer $post_ID
2519  * @param string $post_status no uniqueness checks are made if the post is still draft or pending
2520  * @param string $post_type
2521  * @param integer $post_parent
2522  * @return string unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
2523  */
2524 function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
2525         if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2526                 return $slug;
2527
2528         global $wpdb, $wp_rewrite;
2529
2530         $feeds = $wp_rewrite->feeds;
2531         if ( ! is_array( $feeds ) )
2532                 $feeds = array();
2533
2534         $hierarchical_post_types = apply_filters( 'hierarchical_post_types', array( 'page' ) );
2535         if ( 'attachment' == $post_type ) {
2536                 // Attachment slugs must be unique across all types.
2537                 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
2538                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
2539
2540                 if ( $post_name_check || in_array( $slug, $feeds ) ) {
2541                         $suffix = 2;
2542                         do {
2543                                 $alt_post_name = substr ($slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2544                                 $post_name_check = $wpdb->get_var( $wpdb->prepare($check_sql, $alt_post_name, $post_ID ) );
2545                                 $suffix++;
2546                         } while ( $post_name_check );
2547                         $slug = $alt_post_name;
2548                 }
2549         } elseif ( in_array( $post_type, $hierarchical_post_types ) ) {
2550                 // Page slugs must be unique within their own trees. Pages are in a separate
2551                 // namespace than posts so page slugs are allowed to overlap post slugs.
2552                 $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";
2553                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID, $post_parent ) );
2554
2555                 if ( $post_name_check || in_array( $slug, $feeds ) || preg_match( '@^(page)?\d+$@', $slug ) ) {
2556                         $suffix = 2;
2557                         do {
2558                                 $alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2559                                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID, $post_parent ) );
2560                                 $suffix++;
2561                         } while ( $post_name_check );
2562                         $slug = $alt_post_name;
2563                 }
2564         } else {
2565                 // Post slugs must be unique across all posts.
2566                 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
2567                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
2568
2569                 if ( $post_name_check || in_array( $slug, $feeds ) ) {
2570                         $suffix = 2;
2571                         do {
2572                                 $alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2573                                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
2574                                 $suffix++;
2575                         } while ( $post_name_check );
2576                         $slug = $alt_post_name;
2577                 }
2578         }
2579
2580         return $slug;
2581 }
2582
2583 /**
2584  * Adds tags to a post.
2585  *
2586  * @uses wp_set_post_tags() Same first two parameters, but the last parameter is always set to true.
2587  *
2588  * @package WordPress
2589  * @subpackage Post
2590  * @since 2.3.0
2591  *
2592  * @param int $post_id Post ID
2593  * @param string $tags The tags to set for the post, separated by commas.
2594  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2595  */
2596 function wp_add_post_tags($post_id = 0, $tags = '') {
2597         return wp_set_post_tags($post_id, $tags, true);
2598 }
2599
2600
2601 /**
2602  * Set the tags for a post.
2603  *
2604  * @since 2.3.0
2605  * @uses wp_set_object_terms() Sets the tags for the post.
2606  *
2607  * @param int $post_id Post ID.
2608  * @param string $tags The tags to set for the post, separated by commas.
2609  * @param bool $append If true, don't delete existing tags, just add on. If false, replace the tags with the new tags.
2610  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2611  */
2612 function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
2613         return wp_set_post_terms( $post_id, $tags, 'post_tag', $append);
2614 }
2615
2616 /**
2617  * Set the terms for a post.
2618  *
2619  * @since 2.8.0
2620  * @uses wp_set_object_terms() Sets the tags for the post.
2621  *
2622  * @param int $post_id Post ID.
2623  * @param string $tags The tags to set for the post, separated by commas.
2624  * @param bool $append If true, don't delete existing tags, just add on. If false, replace the tags with the new tags.
2625  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2626  */
2627 function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
2628         $post_id = (int) $post_id;
2629
2630         if ( !$post_id )
2631                 return false;
2632
2633         if ( empty($tags) )
2634                 $tags = array();
2635
2636         $tags = is_array($tags) ? $tags : explode( ',', trim($tags, " \n\t\r\0\x0B,") );
2637
2638         // Hierarchical taxonomies must always pass IDs rather than names so that children with the same
2639         // names but different parents aren't confused.
2640         if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2641                 $tags = array_map( 'intval', $tags );
2642                 $tags = array_unique( $tags );
2643         }
2644
2645         wp_set_object_terms($post_id, $tags, $taxonomy, $append);
2646 }
2647
2648 /**
2649  * Set categories for a post.
2650  *
2651  * If the post categories parameter is not set, then the default category is
2652  * going used.
2653  *
2654  * @since 2.1.0
2655  *
2656  * @param int $post_ID Post ID.
2657  * @param array $post_categories Optional. List of categories.
2658  * @return bool|mixed
2659  */
2660 function wp_set_post_categories($post_ID = 0, $post_categories = array()) {
2661         $post_ID = (int) $post_ID;
2662         $post_type = get_post_type( $post_ID );
2663         $post_status = get_post_status( $post_ID );
2664         // If $post_categories isn't already an array, make it one:
2665         if ( !is_array($post_categories) || empty($post_categories) ) {
2666                 if ( 'post' == $post_type && 'auto-draft' != $post_status )
2667                         $post_categories = array( get_option('default_category') );
2668                 else
2669                         $post_categories = array();
2670         } else if ( 1 == count($post_categories) && '' == reset($post_categories) ) {
2671                 return true;
2672         }
2673
2674         if ( !empty($post_categories) ) {
2675                 $post_categories = array_map('intval', $post_categories);
2676                 $post_categories = array_unique($post_categories);
2677         }
2678
2679         return wp_set_object_terms($post_ID, $post_categories, 'category');
2680 }
2681
2682 /**
2683  * Transition the post status of a post.
2684  *
2685  * Calls hooks to transition post status.
2686  *
2687  * The first is 'transition_post_status' with new status, old status, and post data.
2688  *
2689  * The next action called is 'OLDSTATUS_to_NEWSTATUS' the 'NEWSTATUS' is the
2690  * $new_status parameter and the 'OLDSTATUS' is $old_status parameter; it has the
2691  * post data.
2692  *
2693  * The final action is named 'NEWSTATUS_POSTTYPE', 'NEWSTATUS' is from the $new_status
2694  * parameter and POSTTYPE is post_type post data.
2695  *
2696  * @since 2.3.0
2697  * @link http://codex.wordpress.org/Post_Status_Transitions
2698  *
2699  * @uses do_action() Calls 'transition_post_status' on $new_status, $old_status and
2700  *  $post if there is a status change.
2701  * @uses do_action() Calls '${old_status}_to_$new_status' on $post if there is a status change.
2702  * @uses do_action() Calls '${new_status}_$post->post_type' on post ID and $post.
2703  *
2704  * @param string $new_status Transition to this post status.
2705  * @param string $old_status Previous post status.
2706  * @param object $post Post data.
2707  */
2708 function wp_transition_post_status($new_status, $old_status, $post) {
2709         do_action('transition_post_status', $new_status, $old_status, $post);
2710         do_action("${old_status}_to_$new_status", $post);
2711         do_action("${new_status}_$post->post_type", $post->ID, $post);
2712 }
2713
2714 //
2715 // Trackback and ping functions
2716 //
2717
2718 /**
2719  * Add a URL to those already pung.
2720  *
2721  * @since 1.5.0
2722  * @uses $wpdb
2723  *
2724  * @param int $post_id Post ID.
2725  * @param string $uri Ping URI.
2726  * @return int How many rows were updated.
2727  */
2728 function add_ping($post_id, $uri) {
2729         global $wpdb;
2730         $pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
2731         $pung = trim($pung);
2732         $pung = preg_split('/\s/', $pung);
2733         $pung[] = $uri;
2734         $new = implode("\n", $pung);
2735         $new = apply_filters('add_ping', $new);
2736         // expected_slashed ($new)
2737         $new = stripslashes($new);
2738         return $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post_id ) );
2739 }
2740
2741 /**
2742  * Retrieve enclosures already enclosed for a post.
2743  *
2744  * @since 1.5.0
2745  * @uses $wpdb
2746  *
2747  * @param int $post_id Post ID.
2748  * @return array List of enclosures
2749  */
2750 function get_enclosed($post_id) {
2751         $custom_fields = get_post_custom( $post_id );
2752         $pung = array();
2753         if ( !is_array( $custom_fields ) )
2754                 return $pung;
2755
2756         foreach ( $custom_fields as $key => $val ) {
2757                 if ( 'enclosure' != $key || !is_array( $val ) )
2758                         continue;
2759                 foreach( $val as $enc ) {
2760                         $enclosure = split( "\n", $enc );
2761                         $pung[] = trim( $enclosure[ 0 ] );
2762                 }
2763         }
2764         $pung = apply_filters('get_enclosed', $pung);
2765         return $pung;
2766 }
2767
2768 /**
2769  * Retrieve URLs already pinged for a post.
2770  *
2771  * @since 1.5.0
2772  * @uses $wpdb
2773  *
2774  * @param int $post_id Post ID.
2775  * @return array
2776  */
2777 function get_pung($post_id) {
2778         global $wpdb;
2779         $pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
2780         $pung = trim($pung);
2781         $pung = preg_split('/\s/', $pung);
2782         $pung = apply_filters('get_pung', $pung);
2783         return $pung;
2784 }
2785
2786 /**
2787  * Retrieve URLs that need to be pinged.
2788  *
2789  * @since 1.5.0
2790  * @uses $wpdb
2791  *
2792  * @param int $post_id Post ID
2793  * @return array
2794  */
2795 function get_to_ping($post_id) {
2796         global $wpdb;
2797         $to_ping = $wpdb->get_var( $wpdb->prepare( "SELECT to_ping FROM $wpdb->posts WHERE ID = %d", $post_id ));
2798         $to_ping = trim($to_ping);
2799         $to_ping = preg_split('/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY);
2800         $to_ping = apply_filters('get_to_ping',  $to_ping);
2801         return $to_ping;
2802 }
2803
2804 /**
2805  * Do trackbacks for a list of URLs.
2806  *
2807  * @since 1.0.0
2808  *
2809  * @param string $tb_list Comma separated list of URLs
2810  * @param int $post_id Post ID
2811  */
2812 function trackback_url_list($tb_list, $post_id) {
2813         if ( ! empty( $tb_list ) ) {
2814                 // get post data
2815                 $postdata = wp_get_single_post($post_id, ARRAY_A);
2816
2817                 // import postdata as variables
2818                 extract($postdata, EXTR_SKIP);
2819
2820                 // form an excerpt
2821                 $excerpt = strip_tags($post_excerpt ? $post_excerpt : $post_content);
2822
2823                 if (strlen($excerpt) > 255) {
2824                         $excerpt = substr($excerpt,0,252) . '...';
2825                 }
2826
2827                 $trackback_urls = explode(',', $tb_list);
2828                 foreach( (array) $trackback_urls as $tb_url) {
2829                         $tb_url = trim($tb_url);
2830                         trackback($tb_url, stripslashes($post_title), $excerpt, $post_id);
2831                 }
2832         }
2833 }
2834
2835 //
2836 // Page functions
2837 //
2838
2839 /**
2840  * Get a list of page IDs.
2841  *
2842  * @since 2.0.0
2843  * @uses $wpdb
2844  *
2845  * @return array List of page IDs.
2846  */
2847 function get_all_page_ids() {
2848         global $wpdb;
2849
2850         if ( ! $page_ids = wp_cache_get('all_page_ids', 'posts') ) {
2851                 $page_ids = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'page'");
2852                 wp_cache_add('all_page_ids', $page_ids, 'posts');
2853         }
2854
2855         return $page_ids;
2856 }
2857
2858 /**
2859  * Retrieves page data given a page ID or page object.
2860  *
2861  * @since 1.5.1
2862  *
2863  * @param mixed $page Page object or page ID. Passed by reference.
2864  * @param string $output What to output. OBJECT, ARRAY_A, or ARRAY_N.
2865  * @param string $filter How the return value should be filtered.
2866  * @return mixed Page data.
2867  */
2868 function &get_page(&$page, $output = OBJECT, $filter = 'raw') {
2869         $p = get_post($page, $output, $filter);
2870         return $p;
2871 }
2872
2873 /**
2874  * Retrieves a page given its path.
2875  *
2876  * @since 2.1.0
2877  * @uses $wpdb
2878  *
2879  * @param string $page_path Page path
2880  * @param string $output Optional. Output type. OBJECT, ARRAY_N, or ARRAY_A. Default OBJECT.
2881  * @param string $post_type Optional. Post type. Default page.
2882  * @return mixed Null when complete.
2883  */
2884 function get_page_by_path($page_path, $output = OBJECT, $post_type = 'page') {
2885         global $wpdb;
2886         $page_path = rawurlencode(urldecode($page_path));
2887         $page_path = str_replace('%2F', '/', $page_path);
2888         $page_path = str_replace('%20', ' ', $page_path);
2889         $page_paths = '/' . trim($page_path, '/');
2890         $leaf_path  = sanitize_title(basename($page_paths));
2891         $page_paths = explode('/', $page_paths);
2892         $full_path = '';
2893         foreach ( (array) $page_paths as $pathdir )
2894                 $full_path .= ( $pathdir != '' ? '/' : '' ) . sanitize_title($pathdir);
2895
2896         $pages = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_name = %s AND (post_type = %s OR post_type = 'attachment')", $leaf_path, $post_type ));
2897
2898         if ( empty($pages) )
2899                 return null;
2900
2901         foreach ( $pages as $page ) {
2902                 $path = '/' . $leaf_path;
2903                 $curpage = $page;
2904                 while ( $curpage->post_parent != 0 ) {
2905                         $curpage = $wpdb->get_row( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE ID = %d and post_type = %s", $curpage->post_parent, $post_type ));
2906                         $path = '/' . $curpage->post_name . $path;
2907                 }
2908
2909                 if ( $path == $full_path )
2910                         return get_page($page->ID, $output, $post_type);
2911         }
2912
2913         return null;
2914 }
2915
2916 /**
2917  * Retrieve a page given its title.
2918  *
2919  * @since 2.1.0
2920  * @uses $wpdb
2921  *
2922  * @param string $page_title Page title
2923  * @param string $output Optional. Output type. OBJECT, ARRAY_N, or ARRAY_A. Default OBJECT.
2924  * @param string $post_type Optional. Post type. Default page.
2925  * @return mixed
2926  */
2927 function get_page_by_title($page_title, $output = OBJECT, $post_type = 'page' ) {
2928         global $wpdb;
2929         $page = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type= %s", $page_title, $post_type ) );
2930         if ( $page )
2931                 return get_page($page, $output);
2932
2933         return null;
2934 }
2935
2936 /**
2937  * Retrieve child pages from list of pages matching page ID.
2938  *
2939  * Matches against the pages parameter against the page ID. Also matches all
2940  * children for the same to retrieve all children of a page. Does not make any
2941  * SQL queries to get the children.
2942  *
2943  * @since 1.5.1
2944  *
2945  * @param int $page_id Page ID.
2946  * @param array $pages List of pages' objects.
2947  * @return array
2948  */
2949 function &get_page_children($page_id, $pages) {
2950         $page_list = array();
2951         foreach ( (array) $pages as $page ) {
2952                 if ( $page->post_parent == $page_id ) {
2953                         $page_list[] = $page;
2954                         if ( $children = get_page_children($page->ID, $pages) )
2955                                 $page_list = array_merge($page_list, $children);
2956                 }
2957         }
2958         return $page_list;
2959 }
2960
2961 /**
2962  * Order the pages with children under parents in a flat list.
2963  *
2964  * It uses auxiliary structure to hold parent-children relationships and
2965  * runs in O(N) complexity
2966  *
2967  * @since 2.0.0
2968  *
2969  * @param array $posts Posts array.
2970  * @param int $parent Parent page ID.
2971  * @return array A list arranged by hierarchy. Children immediately follow their parents.
2972  */
2973 function &get_page_hierarchy( &$pages, $page_id = 0 ) {
2974
2975         if ( empty( $pages ) ) {
2976                 $result = array();
2977                 return $result;
2978         }
2979
2980         $children = array();
2981         foreach ( (array) $pages as $p ) {
2982
2983                 $parent_id = intval( $p->post_parent );
2984                 $children[ $parent_id ][] = $p;
2985          }
2986
2987          $result = array();
2988          _page_traverse_name( $page_id, $children, $result );
2989
2990         return $result;
2991 }
2992
2993 /**
2994  * function to traverse and return all the nested children post names of a root page.
2995  * $children contains parent-chilren relations
2996  *
2997  */
2998 function _page_traverse_name( $page_id, &$children, &$result ){
2999
3000         if ( isset( $children[ $page_id ] ) ){
3001
3002                 foreach( (array)$children[ $page_id ] as $child ) {
3003
3004                         $result[ $child->ID ] = $child->post_name;
3005                         _page_traverse_name( $child->ID, $children, $result );
3006                 }
3007         }
3008 }
3009
3010 /**
3011  * Builds URI for a page.
3012  *
3013  * Sub pages will be in the "directory" under the parent page post name.
3014  *
3015  * @since 1.5.0
3016  *
3017  * @param mixed $page Page object or page ID.
3018  * @return string Page URI.
3019  */
3020 function get_page_uri($page) {
3021         if ( ! is_object($page) )
3022                 $page = get_page($page);
3023         $uri = $page->post_name;
3024
3025         // A page cannot be it's own parent.
3026         if ( $page->post_parent == $page->ID )
3027                 return $uri;
3028
3029         while ($page->post_parent != 0) {
3030                 $page = get_page($page->post_parent);
3031                 $uri = $page->post_name . "/" . $uri;
3032         }
3033
3034         return $uri;
3035 }
3036
3037 /**
3038  * Retrieve a list of pages.
3039  *
3040  * The defaults that can be overridden are the following: 'child_of',
3041  * 'sort_order', 'sort_column', 'post_title', 'hierarchical', 'exclude',
3042  * 'include', 'meta_key', 'meta_value','authors', 'number', and 'offset'.
3043  *
3044  * @since 1.5.0
3045  * @uses $wpdb
3046  *
3047  * @param mixed $args Optional. Array or string of options that overrides defaults.
3048  * @return array List of pages matching defaults or $args
3049  */
3050 function &get_pages($args = '') {
3051         global $wpdb;
3052
3053         $defaults = array(
3054                 'child_of' => 0, 'sort_order' => 'ASC',
3055                 'sort_column' => 'post_title', 'hierarchical' => 1,
3056                 'exclude' => array(), 'include' => array(),
3057                 'meta_key' => '', 'meta_value' => '',
3058                 'authors' => '', 'parent' => -1, 'exclude_tree' => '',
3059                 'number' => '', 'offset' => 0,
3060                 'post_type' => 'page', 'post_status' => 'publish',
3061         );
3062
3063         $r = wp_parse_args( $args, $defaults );
3064         extract( $r, EXTR_SKIP );
3065         $number = (int) $number;
3066         $offset = (int) $offset;
3067
3068         // Make sure the post type is hierarchical
3069         $hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
3070         if ( !in_array( $post_type, $hierarchical_post_types ) )
3071                 return false;
3072
3073         // Make sure we have a valid post status
3074         if ( !in_array($post_status, get_post_stati()) )
3075                 return false;
3076
3077         $cache = array();
3078         $key = md5( serialize( compact(array_keys($defaults)) ) );
3079         if ( $cache = wp_cache_get( 'get_pages', 'posts' ) ) {
3080                 if ( is_array($cache) && isset( $cache[ $key ] ) ) {
3081                         $pages = apply_filters('get_pages', $cache[ $key ], $r );
3082                         return $pages;
3083                 }
3084         }
3085
3086         if ( !is_array($cache) )
3087                 $cache = array();
3088
3089         $inclusions = '';
3090         if ( !empty($include) ) {
3091                 $child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
3092                 $parent = -1;
3093                 $exclude = '';
3094                 $meta_key = '';
3095                 $meta_value = '';
3096                 $hierarchical = false;
3097                 $incpages = wp_parse_id_list( $include );
3098                 if ( ! empty( $incpages ) ) {
3099                         foreach ( $incpages as $incpage ) {
3100                                 if (empty($inclusions))
3101                                         $inclusions = $wpdb->prepare(' AND ( ID = %d ', $incpage);
3102                                 else
3103                                         $inclusions .= $wpdb->prepare(' OR ID = %d ', $incpage);
3104                         }
3105                 }
3106         }
3107         if (!empty($inclusions))
3108                 $inclusions .= ')';
3109
3110         $exclusions = '';
3111         if ( !empty($exclude) ) {
3112                 $expages = wp_parse_id_list( $exclude );
3113                 if ( ! empty( $expages ) ) {
3114                         foreach ( $expages as $expage ) {
3115                                 if (empty($exclusions))
3116                                         $exclusions = $wpdb->prepare(' AND ( ID <> %d ', $expage);
3117                                 else
3118                                         $exclusions .= $wpdb->prepare(' AND ID <> %d ', $expage);
3119                         }
3120                 }
3121         }
3122         if (!empty($exclusions))
3123                 $exclusions .= ')';
3124
3125         $author_query = '';
3126         if (!empty($authors)) {
3127                 $post_authors = preg_split('/[\s,]+/',$authors);
3128
3129                 if ( ! empty( $post_authors ) ) {
3130                         foreach ( $post_authors as $post_author ) {
3131                                 //Do we have an author id or an author login?
3132                                 if ( 0 == intval($post_author) ) {
3133                                         $post_author = get_userdatabylogin($post_author);
3134                                         if ( empty($post_author) )
3135                                                 continue;
3136                                         if ( empty($post_author->ID) )
3137                                                 continue;
3138                                         $post_author = $post_author->ID;
3139                                 }
3140
3141                                 if ( '' == $author_query )
3142                                         $author_query = $wpdb->prepare(' post_author = %d ', $post_author);
3143                                 else
3144                                         $author_query .= $wpdb->prepare(' OR post_author = %d ', $post_author);
3145                         }
3146                         if ( '' != $author_query )
3147                                 $author_query = " AND ($author_query)";
3148                 }
3149         }
3150
3151         $join = '';
3152         $where = "$exclusions $inclusions ";
3153         if ( ! empty( $meta_key ) || ! empty( $meta_value ) ) {
3154                 $join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
3155
3156                 // meta_key and meta_value might be slashed
3157                 $meta_key = stripslashes($meta_key);
3158                 $meta_value = stripslashes($meta_value);
3159                 if ( ! empty( $meta_key ) )
3160                         $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_key = %s", $meta_key);
3161                 if ( ! empty( $meta_value ) )
3162                         $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_value = %s", $meta_value);
3163
3164         }
3165
3166         if ( $parent >= 0 )
3167                 $where .= $wpdb->prepare(' AND post_parent = %d ', $parent);
3168
3169         $where_post_type = $wpdb->prepare( "post_type = '%s' AND post_status = '%s'", $post_type, $post_status );
3170
3171         $query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
3172         $query .= $author_query;
3173         $query .= " ORDER BY " . $sort_column . " " . $sort_order ;
3174
3175         if ( !empty($number) )
3176                 $query .= ' LIMIT ' . $offset . ',' . $number;
3177
3178         $pages = $wpdb->get_results($query);
3179
3180         if ( empty($pages) ) {
3181                 $pages = apply_filters('get_pages', array(), $r);
3182                 return $pages;
3183         }
3184
3185         // Sanitize before caching so it'll only get done once
3186         $num_pages = count($pages);
3187         for ($i = 0; $i < $num_pages; $i++) {
3188                 $pages[$i] = sanitize_post($pages[$i], 'raw');
3189         }
3190
3191         // Update cache.
3192         update_page_cache($pages);
3193
3194         if ( $child_of || $hierarchical )
3195                 $pages = & get_page_children($child_of, $pages);
3196
3197         if ( !empty($exclude_tree) ) {
3198                 $exclude = (int) $exclude_tree;
3199                 $children = get_page_children($exclude, $pages);
3200                 $excludes = array();
3201                 foreach ( $children as $child )
3202                         $excludes[] = $child->ID;
3203                 $excludes[] = $exclude;
3204                 $num_pages = count($pages);
3205                 for ( $i = 0; $i < $num_pages; $i++ ) {
3206                         if ( in_array($pages[$i]->ID, $excludes) )
3207                                 unset($pages[$i]);
3208                 }
3209         }
3210
3211         $cache[ $key ] = $pages;
3212         wp_cache_set( 'get_pages', $cache, 'posts' );
3213
3214         $pages = apply_filters('get_pages', $pages, $r);
3215
3216         return $pages;
3217 }
3218
3219 //
3220 // Attachment functions
3221 //
3222
3223 /**
3224  * Check if the attachment URI is local one and is really an attachment.
3225  *
3226  * @since 2.0.0
3227  *
3228  * @param string $url URL to check
3229  * @return bool True on success, false on failure.
3230  */
3231 function is_local_attachment($url) {
3232         if (strpos($url, home_url()) === false)
3233                 return false;
3234         if (strpos($url, home_url('/?attachment_id=')) !== false)
3235                 return true;
3236         if ( $id = url_to_postid($url) ) {
3237                 $post = & get_post($id);
3238                 if ( 'attachment' == $post->post_type )
3239                         return true;
3240         }
3241         return false;
3242 }
3243
3244 /**
3245  * Insert an attachment.
3246  *
3247  * If you set the 'ID' in the $object parameter, it will mean that you are
3248  * updating and attempt to update the attachment. You can also set the
3249  * attachment name or title by setting the key 'post_name' or 'post_title'.
3250  *
3251  * You can set the dates for the attachment manually by setting the 'post_date'
3252  * and 'post_date_gmt' keys' values.
3253  *
3254  * By default, the comments will use the default settings for whether the
3255  * comments are allowed. You can close them manually or keep them open by
3256  * setting the value for the 'comment_status' key.
3257  *
3258  * The $object parameter can have the following:
3259  *     'post_status'   - Default is 'draft'. Can not be overridden, set the same as parent post.
3260  *     'post_type'     - Default is 'post', will be set to attachment. Can not override.
3261  *     'post_author'   - Default is current user ID. The ID of the user, who added the attachment.
3262  *     'ping_status'   - Default is the value in default ping status option. Whether the attachment
3263  *                       can accept pings.
3264  *     'post_parent'   - Default is 0. Can use $parent parameter or set this for the post it belongs
3265  *                       to, if any.
3266  *     'menu_order'    - Default is 0. The order it is displayed.
3267  *     'to_ping'       - Whether to ping.
3268  *     'pinged'        - Default is empty string.
3269  *     'post_password' - Default is empty string. The password to access the attachment.
3270  *     'guid'          - Global Unique ID for referencing the attachment.
3271  *     'post_content_filtered' - Attachment post content filtered.
3272  *     'post_excerpt'  - Attachment excerpt.
3273  *
3274  * @since 2.0.0
3275  * @uses $wpdb
3276  * @uses $user_ID
3277  * @uses do_action() Calls 'edit_attachment' on $post_ID if this is an update.
3278  * @uses do_action() Calls 'add_attachment' on $post_ID if this is not an update.
3279  *
3280  * @param string|array $object Arguments to override defaults.
3281  * @param string $file Optional filename.
3282  * @param int $post_parent Parent post ID.
3283  * @return int Attachment ID.
3284  */
3285 function wp_insert_attachment($object, $file = false, $parent = 0) {
3286         global $wpdb, $user_ID;
3287
3288         $defaults = array('post_status' => 'draft', 'post_type' => 'post', 'post_author' => $user_ID,
3289                 'ping_status' => get_option('default_ping_status'), 'post_parent' => 0,
3290                 'menu_order' => 0, 'to_ping' =>  '', 'pinged' => '', 'post_password' => '',
3291                 'guid' => '', 'post_content_filtered' => '', 'post_excerpt' => '', 'import_id' => 0);
3292
3293         $object = wp_parse_args($object, $defaults);
3294         if ( !empty($parent) )
3295                 $object['post_parent'] = $parent;
3296
3297         $object = sanitize_post($object, 'db');
3298
3299         // export array as variables
3300         extract($object, EXTR_SKIP);
3301
3302         if ( empty($post_author) )
3303                 $post_author = $user_ID;
3304
3305         $post_type = 'attachment';
3306         $post_status = 'inherit';
3307
3308         // Make sure we set a valid category.
3309         if ( !isset($post_category) || 0 == count($post_category) || !is_array($post_category) ) {
3310                 // 'post' requires at least one category.
3311                 if ( 'post' == $post_type )
3312                         $post_category = array( get_option('default_category') );
3313                 else
3314                         $post_category = array();
3315         }
3316
3317         // Are we updating or creating?
3318         if ( !empty($ID) ) {
3319                 $update = true;
3320                 $post_ID = (int) $ID;
3321         } else {
3322                 $update = false;
3323                 $post_ID = 0;
3324         }
3325
3326         // Create a valid post name.
3327         if ( empty($post_name) )
3328                 $post_name = sanitize_title($post_title);
3329         else
3330                 $post_name = sanitize_title($post_name);
3331
3332         // expected_slashed ($post_name)
3333         $post_name = wp_unique_post_slug($post_name, $post_ID, $post_status, $post_type, $post_parent);
3334
3335         if ( empty($post_date) )
3336                 $post_date = current_time('mysql');
3337         if ( empty($post_date_gmt) )
3338                 $post_date_gmt = current_time('mysql', 1);
3339
3340         if ( empty($post_modified) )
3341                 $post_modified = $post_date;
3342         if ( empty($post_modified_gmt) )
3343                 $post_modified_gmt = $post_date_gmt;
3344
3345         if ( empty($comment_status) ) {
3346                 if ( $update )
3347                         $comment_status = 'closed';
3348                 else
3349                         $comment_status = get_option('default_comment_status');
3350         }
3351         if ( empty($ping_status) )
3352                 $ping_status = get_option('default_ping_status');
3353
3354         if ( isset($to_ping) )
3355                 $to_ping = preg_replace('|\s+|', "\n", $to_ping);
3356         else
3357                 $to_ping = '';
3358
3359         if ( isset($post_parent) )
3360                 $post_parent = (int) $post_parent;
3361         else
3362                 $post_parent = 0;
3363
3364         if ( isset($menu_order) )
3365                 $menu_order = (int) $menu_order;
3366         else
3367                 $menu_order = 0;
3368
3369         if ( !isset($post_password) )
3370                 $post_password = '';
3371
3372         if ( ! isset($pinged) )
3373                 $pinged = '';
3374
3375         // expected_slashed (everything!)
3376         $data = compact( array( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_content_filtered', 'post_title', 'post_excerpt', 'post_status', 'post_type', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_parent', 'menu_order', 'post_mime_type', 'guid' ) );
3377         $data = stripslashes_deep( $data );
3378
3379         if ( $update ) {
3380                 $wpdb->update( $wpdb->posts, $data, array( 'ID' => $post_ID ) );
3381         } else {
3382                 // If there is a suggested ID, use it if not already present
3383                 if ( !empty($import_id) ) {
3384                         $import_id = (int) $import_id;
3385                         if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
3386                                 $data['ID'] = $import_id;
3387                         }
3388                 }
3389
3390                 $wpdb->insert( $wpdb->posts, $data );
3391                 $post_ID = (int) $wpdb->insert_id;
3392         }
3393
3394         if ( empty($post_name) ) {
3395                 $post_name = sanitize_title($post_title, $post_ID);
3396                 $wpdb->update( $wpdb->posts, compact("post_name"), array( 'ID' => $post_ID ) );
3397         }
3398
3399         wp_set_post_categories($post_ID, $post_category);
3400
3401         if ( $file )
3402                 update_attached_file( $post_ID, $file );
3403
3404         clean_post_cache($post_ID);
3405
3406         if ( isset($post_parent) && $post_parent < 0 )
3407                 add_post_meta($post_ID, '_wp_attachment_temp_parent', $post_parent, true);
3408
3409         if ( $update) {
3410                 do_action('edit_attachment', $post_ID);
3411         } else {
3412                 do_action('add_attachment', $post_ID);
3413         }
3414
3415         return $post_ID;
3416 }
3417
3418 /**
3419  * Trashes or deletes an attachment.
3420  *
3421  * When an attachment is permanently deleted, the file will also be removed.
3422  * Deletion removes all post meta fields, taxonomy, comments, etc. associated
3423  * with the attachment (except the main post).
3424  *
3425  * The attachment is moved to the trash instead of permanently deleted unless trash
3426  * for media is disabled, item is already in the trash, or $force_delete is true.
3427  *
3428  * @since 2.0.0
3429  * @uses $wpdb
3430  * @uses do_action() Calls 'delete_attachment' hook on Attachment ID.
3431  *
3432  * @param int $postid Attachment ID.
3433  * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
3434  * @return mixed False on failure. Post data on success.
3435  */
3436 function wp_delete_attachment( $post_id, $force_delete = false ) {
3437         global $wpdb;
3438
3439         if ( !$post = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id) ) )
3440                 return $post;
3441
3442         if ( 'attachment' != $post->post_type )
3443                 return false;
3444
3445         if ( !$force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' != $post->post_status )
3446                 return wp_trash_post( $post_id );
3447
3448         delete_post_meta($post_id, '_wp_trash_meta_status');
3449         delete_post_meta($post_id, '_wp_trash_meta_time');
3450
3451         $meta = wp_get_attachment_metadata( $post_id );
3452         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
3453         $file = get_attached_file( $post_id );
3454
3455         if ( is_multisite() )
3456                 delete_transient( 'dirsize_cache' );
3457
3458         do_action('delete_attachment', $post_id);
3459
3460         wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
3461         wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
3462
3463         $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id' AND meta_value = %d", $post_id ));
3464
3465         $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ));
3466         if ( ! empty( $comment_ids ) ) {
3467                 do_action( 'delete_comment', $comment_ids );
3468                 foreach ( $comment_ids as $comment_id )
3469                         wp_delete_comment( $comment_id, true );
3470                 do_action( 'deleted_comment', $comment_ids );
3471         }
3472
3473         $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ));
3474         if ( !empty($post_meta_ids) ) {
3475                 do_action( 'delete_postmeta', $post_meta_ids );
3476                 $in_post_meta_ids = "'" . implode("', '", $post_meta_ids) . "'";
3477                 $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_id IN($in_post_meta_ids)" );
3478                 do_action( 'deleted_postmeta', $post_meta_ids );
3479         }
3480
3481         do_action( 'delete_post', $post_id );
3482         $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->posts WHERE ID = %d", $post_id ));
3483         do_action( 'deleted_post', $post_id );
3484
3485         $uploadpath = wp_upload_dir();
3486
3487         if ( ! empty($meta['thumb']) ) {
3488                 // Don't delete the thumb if another attachment uses it
3489                 if (! $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s AND post_id <> %d", '%' . $meta['thumb'] . '%', $post_id)) ) {
3490                         $thumbfile = str_replace(basename($file), $meta['thumb'], $file);
3491                         $thumbfile = apply_filters('wp_delete_file', $thumbfile);
3492                         @ unlink( path_join($uploadpath['basedir'], $thumbfile) );
3493                 }
3494         }
3495
3496         // remove intermediate and backup images if there are any
3497         foreach ( get_intermediate_image_sizes() as $size ) {
3498                 if ( $intermediate = image_get_intermediate_size($post_id, $size) ) {
3499                         $intermediate_file = apply_filters('wp_delete_file', $intermediate['path']);
3500                         @ unlink( path_join($uploadpath['basedir'], $intermediate_file) );
3501                 }
3502         }
3503
3504         if ( is_array($backup_sizes) ) {
3505                 foreach ( $backup_sizes as $size ) {
3506                         $del_file = path_join( dirname($meta['file']), $size['file'] );
3507                         $del_file = apply_filters('wp_delete_file', $del_file);
3508             @ unlink( path_join($uploadpath['basedir'], $del_file) );
3509                 }
3510         }
3511
3512         $file = apply_filters('wp_delete_file', $file);
3513
3514         if ( ! empty($file) )
3515                 @ unlink($file);
3516
3517         clean_post_cache($post_id);
3518
3519         return $post;
3520 }
3521
3522 /**
3523  * Retrieve attachment meta field for attachment ID.
3524  *
3525  * @since 2.1.0
3526  *
3527  * @param int $post_id Attachment ID
3528  * @param bool $unfiltered Optional, default is false. If true, filters are not run.
3529  * @return string|bool Attachment meta field. False on failure.
3530  */
3531 function wp_get_attachment_metadata( $post_id = 0, $unfiltered = false ) {
3532         $post_id = (int) $post_id;
3533         if ( !$post =& get_post( $post_id ) )
3534                 return false;
3535
3536         $data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
3537
3538         if ( $unfiltered )
3539                 return $data;
3540
3541         return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
3542 }
3543
3544 /**
3545  * Update metadata for an attachment.
3546  *
3547  * @since 2.1.0
3548  *
3549  * @param int $post_id Attachment ID.
3550  * @param array $data Attachment data.
3551  * @return int
3552  */
3553 function wp_update_attachment_metadata( $post_id, $data ) {
3554         $post_id = (int) $post_id;
3555         if ( !$post =& get_post( $post_id ) )
3556                 return false;
3557
3558         $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID );
3559
3560         return update_post_meta( $post->ID, '_wp_attachment_metadata', $data);
3561 }
3562
3563 /**
3564  * Retrieve the URL for an attachment.
3565  *
3566  * @since 2.1.0
3567  *
3568  * @param int $post_id Attachment ID.
3569  * @return string
3570  */
3571 function wp_get_attachment_url( $post_id = 0 ) {
3572         $post_id = (int) $post_id;
3573         if ( !$post =& get_post( $post_id ) )
3574                 return false;
3575
3576         $url = '';
3577         if ( $file = get_post_meta( $post->ID, '_wp_attached_file', true) ) { //Get attached file
3578                 if ( ($uploads = wp_upload_dir()) && false === $uploads['error'] ) { //Get upload directory
3579                         if ( 0 === strpos($file, $uploads['basedir']) ) //Check that the upload base exists in the file location
3580                                 $url = str_replace($uploads['basedir'], $uploads['baseurl'], $file); //replace file location with url location
3581             elseif ( false !== strpos($file, 'wp-content/uploads') )
3582                 $url = $uploads['baseurl'] . substr( $file, strpos($file, 'wp-content/uploads') + 18 );
3583             else
3584                 $url = $uploads['baseurl'] . "/$file"; //Its a newly uploaded file, therefor $file is relative to the basedir.
3585                 }
3586         }
3587
3588         if ( empty($url) ) //If any of the above options failed, Fallback on the GUID as used pre-2.7, not recomended to rely upon this.
3589                 $url = get_the_guid( $post->ID );
3590
3591         if ( 'attachment' != $post->post_type || empty($url) )
3592                 return false;
3593
3594         return apply_filters( 'wp_get_attachment_url', $url, $post->ID );
3595 }
3596
3597 /**
3598  * Retrieve thumbnail for an attachment.
3599  *
3600  * @since 2.1.0
3601  *
3602  * @param int $post_id Attachment ID.
3603  * @return mixed False on failure. Thumbnail file path on success.
3604  */
3605 function wp_get_attachment_thumb_file( $post_id = 0 ) {
3606         $post_id = (int) $post_id;
3607         if ( !$post =& get_post( $post_id ) )
3608                 return false;
3609         if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )
3610                 return false;
3611
3612         $file = get_attached_file( $post->ID );
3613
3614         if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) )
3615                 return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
3616         return false;
3617 }
3618
3619 /**
3620  * Retrieve URL for an attachment thumbnail.
3621  *
3622  * @since 2.1.0
3623  *
3624  * @param int $post_id Attachment ID
3625  * @return string|bool False on failure. Thumbnail URL on success.
3626  */
3627 function wp_get_attachment_thumb_url( $post_id = 0 ) {
3628         $post_id = (int) $post_id;
3629         if ( !$post =& get_post( $post_id ) )
3630                 return false;
3631         if ( !$url = wp_get_attachment_url( $post->ID ) )
3632                 return false;
3633
3634         $sized = image_downsize( $post_id, 'thumbnail' );
3635         if ( $sized )
3636                 return $sized[0];
3637
3638         if ( !$thumb = wp_get_attachment_thumb_file( $post->ID ) )
3639                 return false;
3640
3641         $url = str_replace(basename($url), basename($thumb), $url);
3642
3643         return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
3644 }
3645
3646 /**
3647  * Check if the attachment is an image.
3648  *
3649  * @since 2.1.0
3650  *
3651  * @param int $post_id Attachment ID
3652  * @return bool
3653  */
3654 function wp_attachment_is_image( $post_id = 0 ) {
3655         $post_id = (int) $post_id;
3656         if ( !$post =& get_post( $post_id ) )
3657                 return false;
3658
3659         if ( !$file = get_attached_file( $post->ID ) )
3660                 return false;
3661
3662         $ext = preg_match('/\.([^.]+)$/', $file, $matches) ? strtolower($matches[1]) : false;
3663
3664         $image_exts = array('jpg', 'jpeg', 'gif', 'png');
3665
3666         if ( 'image/' == substr($post->post_mime_type, 0, 6) || $ext && 'import' == $post->post_mime_type && in_array($ext, $image_exts) )
3667                 return true;
3668         return false;
3669 }
3670
3671 /**
3672  * Retrieve the icon for a MIME type.
3673  *
3674  * @since 2.1.0
3675  *
3676  * @param string $mime MIME type
3677  * @return string|bool
3678  */
3679 function wp_mime_type_icon( $mime = 0 ) {
3680         if ( !is_numeric($mime) )
3681                 $icon = wp_cache_get("mime_type_icon_$mime");
3682         if ( empty($icon) ) {
3683                 $post_id = 0;
3684                 $post_mimes = array();
3685                 if ( is_numeric($mime) ) {
3686                         $mime = (int) $mime;
3687                         if ( $post =& get_post( $mime ) ) {
3688                                 $post_id = (int) $post->ID;
3689                                 $ext = preg_replace('/^.+?\.([^.]+)$/', '$1', $post->guid);
3690                                 if ( !empty($ext) ) {
3691                                         $post_mimes[] = $ext;
3692                                         if ( $ext_type = wp_ext2type( $ext ) )
3693                                                 $post_mimes[] = $ext_type;
3694                                 }
3695                                 $mime = $post->post_mime_type;
3696                         } else {
3697                                 $mime = 0;
3698                         }
3699                 } else {
3700                         $post_mimes[] = $mime;
3701                 }
3702
3703                 $icon_files = wp_cache_get('icon_files');
3704
3705                 if ( !is_array($icon_files) ) {
3706                         $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/crystal' );
3707                         $icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url('images/crystal') );
3708                         $dirs = apply_filters( 'icon_dirs', array($icon_dir => $icon_dir_uri) );
3709                         $icon_files = array();
3710                         while ( $dirs ) {
3711                                 $dir = array_shift($keys = array_keys($dirs));
3712                                 $uri = array_shift($dirs);
3713                                 if ( $dh = opendir($dir) ) {
3714                                         while ( false !== $file = readdir($dh) ) {
3715                                                 $file = basename($file);
3716                                                 if ( substr($file, 0, 1) == '.' )
3717                                                         continue;
3718                                                 if ( !in_array(strtolower(substr($file, -4)), array('.png', '.gif', '.jpg') ) ) {
3719                                                         if ( is_dir("$dir/$file") )
3720                                                                 $dirs["$dir/$file"] = "$uri/$file";
3721                                                         continue;
3722                                                 }
3723                                                 $icon_files["$dir/$file"] = "$uri/$file";
3724                                         }
3725                                         closedir($dh);
3726                                 }
3727                         }
3728                         wp_cache_set('icon_files', $icon_files, 600);
3729                 }
3730
3731                 // Icon basename - extension = MIME wildcard
3732                 foreach ( $icon_files as $file => $uri )
3733                         $types[ preg_replace('/^([^.]*).*$/', '$1', basename($file)) ] =& $icon_files[$file];
3734
3735                 if ( ! empty($mime) ) {
3736                         $post_mimes[] = substr($mime, 0, strpos($mime, '/'));
3737                         $post_mimes[] = substr($mime, strpos($mime, '/') + 1);
3738                         $post_mimes[] = str_replace('/', '_', $mime);
3739                 }
3740
3741                 $matches = wp_match_mime_types(array_keys($types), $post_mimes);
3742                 $matches['default'] = array('default');
3743
3744                 foreach ( $matches as $match => $wilds ) {
3745                         if ( isset($types[$wilds[0]])) {
3746                                 $icon = $types[$wilds[0]];
3747                                 if ( !is_numeric($mime) )
3748                                         wp_cache_set("mime_type_icon_$mime", $icon);
3749                                 break;
3750                         }
3751                 }
3752         }
3753
3754         return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id ); // Last arg is 0 if function pass mime type.
3755 }
3756
3757 /**
3758  * Checked for changed slugs for published posts and save old slug.
3759  *
3760  * The function is used along with form POST data. It checks for the wp-old-slug
3761  * POST field. Will only be concerned with published posts and the slug actually
3762  * changing.
3763  *
3764  * If the slug was changed and not already part of the old slugs then it will be
3765  * added to the post meta field ('_wp_old_slug') for storing old slugs for that
3766  * post.
3767  *
3768  * The most logically usage of this function is redirecting changed posts, so
3769  * that those that linked to an changed post will be redirected to the new post.
3770  *
3771  * @since 2.1.0
3772  *
3773  * @param int $post_id Post ID.
3774  * @return int Same as $post_id
3775  */
3776 function wp_check_for_changed_slugs($post_id, $post, $post_before) {
3777         // dont bother if it hasnt changed
3778         if ( $post->post_name == $post_before->post_name )
3779                 return;
3780
3781         // we're only concerned with published posts
3782         if ( $post->post_status != 'publish' || $post->post_type != 'post' )
3783                 return;
3784
3785         $old_slugs = (array) get_post_meta($post_id, '_wp_old_slug');
3786
3787         // if we haven't added this old slug before, add it now
3788         if ( !in_array($post_before->post_name, $old_slugs) )
3789                 add_post_meta($post_id, '_wp_old_slug', $post_before->post_name);
3790
3791         // if the new slug was used previously, delete it from the list
3792         if ( in_array($post->post_name, $old_slugs) )
3793                 delete_post_meta($post_id, '_wp_old_slug', $post->post_name);
3794 }
3795
3796 /**
3797  * Retrieve the private post SQL based on capability.
3798  *
3799  * This function provides a standardized way to appropriately select on the
3800  * post_status of posts/pages. The function will return a piece of SQL code that
3801  * can be added to a WHERE clause; this SQL is constructed to allow all
3802  * published posts, and all private posts to which the user has access.
3803  *
3804  * It also allows plugins that define their own post type to control the cap by
3805  * using the hook 'pub_priv_sql_capability'. The plugin is expected to return
3806  * the capability the user must have to read the private post type.
3807  *
3808  * @since 2.2.0
3809  *
3810  * @uses $user_ID
3811  * @uses apply_filters() Call 'pub_priv_sql_capability' filter for plugins with different post types.
3812  *
3813  * @param string $post_type currently only supports 'post' or 'page'.
3814  * @return string SQL code that can be added to a where clause.
3815  */
3816 function get_private_posts_cap_sql($post_type) {
3817         return get_posts_by_author_sql($post_type, FALSE);
3818 }
3819
3820 /**
3821  * Retrieve the post SQL based on capability, author, and type.
3822  *
3823  * See above for full description.
3824  *
3825  * @since 3.0.0
3826  * @param string $post_type currently only supports 'post' or 'page'.
3827  * @param bool $full Optional.  Returns a full WHERE statement instead of just an 'andalso' term.
3828  * @param int $post_author Optional.  Query posts having a single author ID.
3829  * @return string SQL WHERE code that can be added to a query.
3830  */
3831 function get_posts_by_author_sql($post_type, $full = TRUE, $post_author = NULL) {
3832         global $user_ID, $wpdb;
3833
3834         // Private posts
3835         if ($post_type == 'post') {
3836                 $cap = 'read_private_posts';
3837         // Private pages
3838         } elseif ($post_type == 'page') {
3839                 $cap = 'read_private_pages';
3840         // Dunno what it is, maybe plugins have their own post type?
3841         } else {
3842                 $cap = '';
3843                 $cap = apply_filters('pub_priv_sql_capability', $cap);
3844
3845                 if (empty($cap)) {
3846                         // We don't know what it is, filters don't change anything,
3847                         // so set the SQL up to return nothing.
3848                         return ' 1 = 0 ';
3849                 }
3850         }
3851
3852         if ($full) {
3853                 if (is_null($post_author)) {
3854                         $sql = $wpdb->prepare('WHERE post_type = %s AND ', $post_type);
3855                 } else {
3856                         $sql = $wpdb->prepare('WHERE post_author = %d AND post_type = %s AND ', $post_author, $post_type);
3857                 }
3858         } else {
3859                 $sql = '';
3860         }
3861
3862         $sql .= "(post_status = 'publish'";
3863
3864         if (current_user_can($cap)) {
3865                 // Does the user have the capability to view private posts? Guess so.
3866                 $sql .= " OR post_status = 'private'";
3867         } elseif (is_user_logged_in()) {
3868                 // Users can view their own private posts.
3869                 $id = (int) $user_ID;
3870                 if (is_null($post_author) || !$full) {
3871                         $sql .= " OR post_status = 'private' AND post_author = $id";
3872                 } elseif ($id == (int)$post_author) {
3873                         $sql .= " OR post_status = 'private'";
3874                 } // else none
3875         } // else none
3876
3877         $sql .= ')';
3878
3879         return $sql;
3880 }
3881
3882 /**
3883  * Retrieve the date that the last post was published.
3884  *
3885  * The server timezone is the default and is the difference between GMT and
3886  * server time. The 'blog' value is the date when the last post was posted. The
3887  * 'gmt' is when the last post was posted in GMT formatted date.
3888  *
3889  * @since 0.71
3890  *
3891  * @uses $wpdb
3892  * @uses $blog_id
3893  * @uses apply_filters() Calls 'get_lastpostdate' filter
3894  *
3895  * @global mixed $cache_lastpostdate Stores the last post date
3896  * @global mixed $pagenow The current page being viewed
3897  *
3898  * @param string $timezone The location to get the time. Can be 'gmt', 'blog', or 'server'.
3899  * @return string The date of the last post.
3900  */
3901 function get_lastpostdate($timezone = 'server') {
3902         global $cache_lastpostdate, $wpdb, $blog_id;
3903         $add_seconds_server = date('Z');
3904         if ( !isset($cache_lastpostdate[$blog_id][$timezone]) ) {
3905                 switch(strtolower($timezone)) {
3906                         case 'gmt':
3907                                 $lastpostdate = $wpdb->get_var("SELECT post_date_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_date_gmt DESC LIMIT 1");
3908                                 break;
3909                         case 'blog':
3910                                 $lastpostdate = $wpdb->get_var("SELECT post_date FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_date_gmt DESC LIMIT 1");
3911                                 break;
3912                         case 'server':
3913                                 $lastpostdate = $wpdb->get_var("SELECT DATE_ADD(post_date_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_date_gmt DESC LIMIT 1");
3914                                 break;
3915                 }
3916                 $cache_lastpostdate[$blog_id][$timezone] = $lastpostdate;
3917         } else {
3918                 $lastpostdate = $cache_lastpostdate[$blog_id][$timezone];
3919         }
3920         return apply_filters( 'get_lastpostdate', $lastpostdate, $timezone );
3921 }
3922
3923 /**
3924  * Retrieve last post modified date depending on timezone.
3925  *
3926  * The server timezone is the default and is the difference between GMT and
3927  * server time. The 'blog' value is just when the last post was modified. The
3928  * 'gmt' is when the last post was modified in GMT time.
3929  *
3930  * @since 1.2.0
3931  * @uses $wpdb
3932  * @uses $blog_id
3933  * @uses apply_filters() Calls 'get_lastpostmodified' filter
3934  *
3935  * @param string $timezone The location to get the time. Can be 'gmt', 'blog', or 'server'.
3936  * @return string The date the post was last modified.
3937  */
3938 function get_lastpostmodified($timezone = 'server') {
3939         global $wpdb;
3940
3941         $add_seconds_server = date('Z');
3942         $timezone = strtolower( $timezone );
3943
3944         $lastpostmodified = wp_cache_get( "lastpostmodified:$timezone", 'timeinfo' );
3945         if ( $lastpostmodified )
3946                 return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
3947
3948         switch ( strtolower($timezone) ) {
3949                 case 'gmt':
3950                         $lastpostmodified = $wpdb->get_var("SELECT post_modified_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_modified_gmt DESC LIMIT 1");
3951                         break;
3952                 case 'blog':
3953                         $lastpostmodified = $wpdb->get_var("SELECT post_modified FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_modified_gmt DESC LIMIT 1");
3954                         break;
3955                 case 'server':
3956                         $lastpostmodified = $wpdb->get_var("SELECT DATE_ADD(post_modified_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post' ORDER BY post_modified_gmt DESC LIMIT 1");
3957                         break;
3958         }
3959
3960         $lastpostdate = get_lastpostdate($timezone);
3961         if ( $lastpostdate > $lastpostmodified )
3962                 $lastpostmodified = $lastpostdate;
3963
3964         if ( $lastpostmodified )
3965                 wp_cache_set( "lastpostmodified:$timezone", $lastpostmodified, 'timeinfo' );
3966
3967         return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
3968 }
3969
3970 /**
3971  * Updates posts in cache.
3972  *
3973  * @usedby update_page_cache() Aliased by this function.
3974  *
3975  * @package WordPress
3976  * @subpackage Cache
3977  * @since 1.5.1
3978  *
3979  * @param array $posts Array of post objects
3980  */
3981 function update_post_cache(&$posts) {
3982         if ( !$posts )
3983                 return;
3984
3985         foreach ( $posts as $post )
3986                 wp_cache_add($post->ID, $post, 'posts');
3987 }
3988
3989 /**
3990  * Will clean the post in the cache.
3991  *
3992  * Cleaning means delete from the cache of the post. Will call to clean the term
3993  * object cache associated with the post ID.
3994  *
3995  * clean_post_cache() will call itself recursively for each child post.
3996  *
3997  * This function not run if $_wp_suspend_cache_invalidation is not empty. See
3998  * wp_suspend_cache_invalidation().
3999  *
4000  * @package WordPress
4001  * @subpackage Cache
4002  * @since 2.0.0
4003  *
4004  * @uses do_action() Calls 'clean_post_cache' on $id before adding children (if any).
4005  *
4006  * @param int $id The Post ID in the cache to clean
4007  */
4008 function clean_post_cache($id) {
4009         global $_wp_suspend_cache_invalidation, $wpdb;
4010
4011         if ( !empty($_wp_suspend_cache_invalidation) )
4012                 return;
4013
4014         $id = (int) $id;
4015
4016         wp_cache_delete($id, 'posts');
4017         wp_cache_delete($id, 'post_meta');
4018
4019         clean_object_term_cache($id, 'post');
4020
4021         wp_cache_delete( 'wp_get_archives', 'general' );
4022
4023         do_action('clean_post_cache', $id);
4024
4025         if ( $children = $wpdb->get_col( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_parent = %d", $id) ) ) {
4026                 foreach( $children as $cid )
4027                         clean_post_cache( $cid );
4028         }
4029
4030         if ( is_multisite() )
4031                 wp_cache_delete( $wpdb->blogid . '-' . $id, 'global-posts' );
4032 }
4033
4034 /**
4035  * Alias of update_post_cache().
4036  *
4037  * @see update_post_cache() Posts and pages are the same, alias is intentional
4038  *
4039  * @package WordPress
4040  * @subpackage Cache
4041  * @since 1.5.1
4042  *
4043  * @param array $pages list of page objects
4044  */
4045 function update_page_cache(&$pages) {
4046         update_post_cache($pages);
4047 }
4048
4049 /**
4050  * Will clean the page in the cache.
4051  *
4052  * Clean (read: delete) page from cache that matches $id. Will also clean cache
4053  * associated with 'all_page_ids' and 'get_pages'.
4054  *
4055  * @package WordPress
4056  * @subpackage Cache
4057  * @since 2.0.0
4058  *
4059  * @uses do_action() Will call the 'clean_page_cache' hook action.
4060  *
4061  * @param int $id Page ID to clean
4062  */
4063 function clean_page_cache($id) {
4064         clean_post_cache($id);
4065
4066         wp_cache_delete( 'all_page_ids', 'posts' );
4067         wp_cache_delete( 'get_pages', 'posts' );
4068
4069         do_action('clean_page_cache', $id);
4070 }
4071
4072 /**
4073  * Call major cache updating functions for list of Post objects.
4074  *
4075  * @package WordPress
4076  * @subpackage Cache
4077  * @since 1.5.0
4078  *
4079  * @uses $wpdb
4080  * @uses update_post_cache()
4081  * @uses update_object_term_cache()
4082  * @uses update_postmeta_cache()
4083  *
4084  * @param array $posts Array of Post objects
4085  * @param string $post_type The post type of the posts in $posts. Default is 'post'.
4086  * @param bool $update_term_cache Whether to update the term cache. Default is true.
4087  * @param bool $update_meta_cache Whether to update the meta cache. Default is true.
4088  */
4089 function update_post_caches(&$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true) {
4090         // No point in doing all this work if we didn't match any posts.
4091         if ( !$posts )
4092                 return;
4093
4094         update_post_cache($posts);
4095
4096         $post_ids = array();
4097         foreach ( $posts as $post )
4098                 $post_ids[] = $post->ID;
4099
4100         if ( empty($post_type) )
4101                 $post_type = 'post';
4102
4103         if ( !is_array($post_type) && 'any' != $post_type && $update_term_cache )
4104                 update_object_term_cache($post_ids, $post_type);
4105
4106         if ( $update_meta_cache )
4107                 update_postmeta_cache($post_ids);
4108 }
4109
4110 /**
4111  * Updates metadata cache for list of post IDs.
4112  *
4113  * Performs SQL query to retrieve the metadata for the post IDs and updates the
4114  * metadata cache for the posts. Therefore, the functions, which call this
4115  * function, do not need to perform SQL queries on their own.
4116  *
4117  * @package WordPress
4118  * @subpackage Cache
4119  * @since 2.1.0
4120  *
4121  * @uses $wpdb
4122  *
4123  * @param array $post_ids List of post IDs.
4124  * @return bool|array Returns false if there is nothing to update or an array of metadata.
4125  */
4126 function update_postmeta_cache($post_ids) {
4127         return update_meta_cache('post', $post_ids);
4128 }
4129
4130 /**
4131  * Will clean the attachment in the cache.
4132  *
4133  * Cleaning means delete from the cache. Optionaly will clean the term
4134  * object cache associated with the attachment ID.
4135  *
4136  * This function will not run if $_wp_suspend_cache_invalidation is not empty. See
4137  * wp_suspend_cache_invalidation().
4138  *
4139  * @package WordPress
4140  * @subpackage Cache
4141  * @since 3.0.0
4142  *
4143  * @uses do_action() Calls 'clean_attachment_cache' on $id.
4144  *
4145  * @param int $id The attachment ID in the cache to clean
4146  * @param bool $clean_terms optional. Whether to clean terms cache
4147  */
4148 function clean_attachment_cache($id, $clean_terms = false) {
4149         global $_wp_suspend_cache_invalidation;
4150
4151         if ( !empty($_wp_suspend_cache_invalidation) )
4152                 return;
4153
4154         $id = (int) $id;
4155
4156         wp_cache_delete($id, 'posts');
4157         wp_cache_delete($id, 'post_meta');
4158
4159         if ( $clean_terms )
4160                 clean_object_term_cache($id, 'attachment');
4161
4162         do_action('clean_attachment_cache', $id);
4163 }
4164
4165 //
4166 // Hooks
4167 //
4168
4169 /**
4170  * Hook for managing future post transitions to published.
4171  *
4172  * @since 2.3.0
4173  * @access private
4174  * @uses $wpdb
4175  * @uses do_action() Calls 'private_to_published' on post ID if this is a 'private_to_published' call.
4176  * @uses wp_clear_scheduled_hook() with 'publish_future_post' and post ID.
4177  *
4178  * @param string $new_status New post status
4179  * @param string $old_status Previous post status
4180  * @param object $post Object type containing the post information
4181  */
4182 function _transition_post_status($new_status, $old_status, $post) {
4183         global $wpdb;
4184
4185         if ( $old_status != 'publish' && $new_status == 'publish' ) {
4186                 // Reset GUID if transitioning to publish and it is empty
4187                 if ( '' == get_the_guid($post->ID) )
4188                         $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
4189                 do_action('private_to_published', $post->ID);  // Deprecated, use private_to_publish
4190         }
4191
4192         // If published posts changed clear the lastpostmodified cache
4193         if ( 'publish' == $new_status || 'publish' == $old_status) {
4194                 wp_cache_delete( 'lastpostmodified:server', 'timeinfo' );
4195                 wp_cache_delete( 'lastpostmodified:gmt',    'timeinfo' );
4196                 wp_cache_delete( 'lastpostmodified:blog',   'timeinfo' );
4197         }
4198
4199         // Always clears the hook in case the post status bounced from future to draft.
4200         wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) );
4201 }
4202
4203 /**
4204  * Hook used to schedule publication for a post marked for the future.
4205  *
4206  * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
4207  *
4208  * @since 2.3.0
4209  * @access private
4210  *
4211  * @param int $deprecated Not used. Can be set to null. Never implemented.
4212  *   Not marked as deprecated with _deprecated_argument() as it conflicts with
4213  *   wp_transition_post_status() and the default filter for _future_post_hook().
4214  * @param object $post Object type containing the post information
4215  */
4216 function _future_post_hook( $deprecated = '', $post ) {
4217         wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
4218         wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT') , 'publish_future_post', array( $post->ID ) );
4219 }
4220
4221 /**
4222  * Hook to schedule pings and enclosures when a post is published.
4223  *
4224  * @since 2.3.0
4225  * @access private
4226  * @uses $wpdb
4227  * @uses XMLRPC_REQUEST and APP_REQUEST constants.
4228  * @uses do_action() Calls 'xmlprc_publish_post' on post ID if XMLRPC_REQUEST is defined.
4229  * @uses do_action() Calls 'app_publish_post' on post ID if APP_REQUEST is defined.
4230  *
4231  * @param int $post_id The ID in the database table of the post being published
4232  */
4233 function _publish_post_hook($post_id) {
4234         global $wpdb;
4235
4236         if ( defined('XMLRPC_REQUEST') )
4237                 do_action('xmlrpc_publish_post', $post_id);
4238         if ( defined('APP_REQUEST') )
4239                 do_action('app_publish_post', $post_id);
4240
4241         if ( defined('WP_IMPORTING') )
4242                 return;
4243
4244         $data = array( 'post_id' => $post_id, 'meta_value' => '1' );
4245         if ( get_option('default_pingback_flag') ) {
4246                 $wpdb->insert( $wpdb->postmeta, $data + array( 'meta_key' => '_pingme' ) );
4247                 do_action( 'added_postmeta', $wpdb->insert_id, $post_id, '_pingme', 1 );
4248         }
4249         $wpdb->insert( $wpdb->postmeta, $data + array( 'meta_key' => '_encloseme' ) );
4250         do_action( 'added_postmeta', $wpdb->insert_id, $post_id, '_encloseme', 1 );
4251
4252         wp_schedule_single_event(time(), 'do_pings');
4253 }
4254
4255 /**
4256  * Hook used to prevent page/post cache and rewrite rules from staying dirty.
4257  *
4258  * Does two things. If the post is a page and has a template then it will
4259  * update/add that template to the meta. For both pages and posts, it will clean
4260  * the post cache to make sure that the cache updates to the changes done
4261  * recently. For pages, the rewrite rules of WordPress are flushed to allow for
4262  * any changes.
4263  *
4264  * The $post parameter, only uses 'post_type' property and 'page_template'
4265  * property.
4266  *
4267  * @since 2.3.0
4268  * @access private
4269  * @uses $wp_rewrite Flushes Rewrite Rules.
4270  *
4271  * @param int $post_id The ID in the database table for the $post
4272  * @param object $post Object type containing the post information
4273  */
4274 function _save_post_hook($post_id, $post) {
4275         if ( $post->post_type == 'page' ) {
4276                 clean_page_cache($post_id);
4277                 // Avoid flushing rules for every post during import.
4278                 if ( !defined('WP_IMPORTING') ) {
4279                         global $wp_rewrite;
4280                         $wp_rewrite->flush_rules(false);
4281                 }
4282         } else {
4283                 clean_post_cache($post_id);
4284         }
4285 }
4286
4287 /**
4288  * Retrieve post ancestors and append to post ancestors property.
4289  *
4290  * Will only retrieve ancestors once, if property is already set, then nothing
4291  * will be done. If there is not a parent post, or post ID and post parent ID
4292  * are the same then nothing will be done.
4293  *
4294  * The parameter is passed by reference, so nothing needs to be returned. The
4295  * property will be updated and can be referenced after the function is
4296  * complete. The post parent will be an ancestor and the parent of the post
4297  * parent will be an ancestor. There will only be two ancestors at the most.
4298  *
4299  * @since unknown
4300  * @access private
4301  * @uses $wpdb
4302  *
4303  * @param object $_post Post data.
4304  * @return null When nothing needs to be done.
4305  */
4306 function _get_post_ancestors(&$_post) {
4307         global $wpdb;
4308
4309         if ( isset($_post->ancestors) )
4310                 return;
4311
4312         $_post->ancestors = array();
4313
4314         if ( empty($_post->post_parent) || $_post->ID == $_post->post_parent )
4315                 return;
4316
4317         $id = $_post->ancestors[] = $_post->post_parent;
4318         while ( $ancestor = $wpdb->get_var( $wpdb->prepare("SELECT `post_parent` FROM $wpdb->posts WHERE ID = %d LIMIT 1", $id) ) ) {
4319                 if ( $id == $ancestor )
4320                         break;
4321                 $id = $_post->ancestors[] = $ancestor;
4322         }
4323 }
4324
4325 /**
4326  * Determines which fields of posts are to be saved in revisions.
4327  *
4328  * Does two things. If passed a post *array*, it will return a post array ready
4329  * to be insterted into the posts table as a post revision. Otherwise, returns
4330  * an array whose keys are the post fields to be saved for post revisions.
4331  *
4332  * @package WordPress
4333  * @subpackage Post_Revisions
4334  * @since 2.6.0
4335  * @access private
4336  * @uses apply_filters() Calls '_wp_post_revision_fields' on 'title', 'content' and 'excerpt' fields.
4337  *
4338  * @param array $post Optional a post array to be processed for insertion as a post revision.
4339  * @param bool $autosave optional Is the revision an autosave?
4340  * @return array Post array ready to be inserted as a post revision or array of fields that can be versioned.
4341  */
4342 function _wp_post_revision_fields( $post = null, $autosave = false ) {
4343         static $fields = false;
4344
4345         if ( !$fields ) {
4346                 // Allow these to be versioned
4347                 $fields = array(
4348                         'post_title' => __( 'Title' ),
4349                         'post_content' => __( 'Content' ),
4350                         'post_excerpt' => __( 'Excerpt' ),
4351                 );
4352
4353                 // Runs only once
4354                 $fields = apply_filters( '_wp_post_revision_fields', $fields );
4355
4356                 // WP uses these internally either in versioning or elsewhere - they cannot be versioned
4357                 foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 'post_author' ) as $protect )
4358                         unset( $fields[$protect] );
4359         }
4360
4361         if ( !is_array($post) )
4362                 return $fields;
4363
4364         $return = array();
4365         foreach ( array_intersect( array_keys( $post ), array_keys( $fields ) ) as $field )
4366                 $return[$field] = $post[$field];
4367
4368         $return['post_parent']   = $post['ID'];
4369         $return['post_status']   = 'inherit';
4370         $return['post_type']     = 'revision';
4371         $return['post_name']     = $autosave ? "$post[ID]-autosave" : "$post[ID]-revision";
4372         $return['post_date']     = isset($post['post_modified']) ? $post['post_modified'] : '';
4373         $return['post_date_gmt'] = isset($post['post_modified_gmt']) ? $post['post_modified_gmt'] : '';
4374
4375         return $return;
4376 }
4377
4378 /**
4379  * Saves an already existing post as a post revision.
4380  *
4381  * Typically used immediately prior to post updates.
4382  *
4383  * @package WordPress
4384  * @subpackage Post_Revisions
4385  * @since 2.6.0
4386  *
4387  * @uses _wp_put_post_revision()
4388  *
4389  * @param int $post_id The ID of the post to save as a revision.
4390  * @return mixed Null or 0 if error, new revision ID, if success.
4391  */
4392 function wp_save_post_revision( $post_id ) {
4393         // We do autosaves manually with wp_create_post_autosave()
4394         if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
4395                 return;
4396
4397         // WP_POST_REVISIONS = 0, false
4398         if ( ! WP_POST_REVISIONS )
4399                 return;
4400
4401         if ( !$post = get_post( $post_id, ARRAY_A ) )
4402                 return;
4403
4404         if ( !post_type_supports($post['post_type'], 'revisions') )
4405                 return;
4406
4407         $return = _wp_put_post_revision( $post );
4408
4409         // WP_POST_REVISIONS = true (default), -1
4410         if ( !is_numeric( WP_POST_REVISIONS ) || WP_POST_REVISIONS < 0 )
4411                 return $return;
4412
4413         // all revisions and (possibly) one autosave
4414         $revisions = wp_get_post_revisions( $post_id, array( 'order' => 'ASC' ) );
4415
4416         // WP_POST_REVISIONS = (int) (# of autosaves to save)
4417         $delete = count($revisions) - WP_POST_REVISIONS;
4418
4419         if ( $delete < 1 )
4420                 return $return;
4421
4422         $revisions = array_slice( $revisions, 0, $delete );
4423
4424         for ( $i = 0; isset($revisions[$i]); $i++ ) {
4425                 if ( false !== strpos( $revisions[$i]->post_name, 'autosave' ) )
4426                         continue;
4427                 wp_delete_post_revision( $revisions[$i]->ID );
4428         }
4429
4430         return $return;
4431 }
4432
4433 /**
4434  * Retrieve the autosaved data of the specified post.
4435  *
4436  * Returns a post object containing the information that was autosaved for the
4437  * specified post.
4438  *
4439  * @package WordPress
4440  * @subpackage Post_Revisions
4441  * @since 2.6.0
4442  *
4443  * @param int $post_id The post ID.
4444  * @return object|bool The autosaved data or false on failure or when no autosave exists.
4445  */
4446 function wp_get_post_autosave( $post_id ) {
4447
4448         if ( !$post = get_post( $post_id ) )
4449                 return false;
4450
4451         $q = array(
4452                 'name' => "{$post->ID}-autosave",
4453                 'post_parent' => $post->ID,
4454                 'post_type' => 'revision',
4455                 'post_status' => 'inherit'
4456         );
4457
4458         // Use WP_Query so that the result gets cached
4459         $autosave_query = new WP_Query;
4460
4461         add_action( 'parse_query', '_wp_get_post_autosave_hack' );
4462         $autosave = $autosave_query->query( $q );
4463         remove_action( 'parse_query', '_wp_get_post_autosave_hack' );
4464
4465         if ( $autosave && is_array($autosave) && is_object($autosave[0]) )
4466                 return $autosave[0];
4467
4468         return false;
4469 }
4470
4471 /**
4472  * Internally used to hack WP_Query into submission.
4473  *
4474  * @package WordPress
4475  * @subpackage Post_Revisions
4476  * @since 2.6.0
4477  *
4478  * @param object $query WP_Query object
4479  */
4480 function _wp_get_post_autosave_hack( $query ) {
4481         $query->is_single = false;
4482 }
4483
4484 /**
4485  * Determines if the specified post is a revision.
4486  *
4487  * @package WordPress
4488  * @subpackage Post_Revisions
4489  * @since 2.6.0
4490  *
4491  * @param int|object $post Post ID or post object.
4492  * @return bool|int False if not a revision, ID of revision's parent otherwise.
4493  */
4494 function wp_is_post_revision( $post ) {
4495         if ( !$post = wp_get_post_revision( $post ) )
4496                 return false;
4497         return (int) $post->post_parent;
4498 }
4499
4500 /**
4501  * Determines if the specified post is an autosave.
4502  *
4503  * @package WordPress
4504  * @subpackage Post_Revisions
4505  * @since 2.6.0
4506  *
4507  * @param int|object $post Post ID or post object.
4508  * @return bool|int False if not a revision, ID of autosave's parent otherwise
4509  */
4510 function wp_is_post_autosave( $post ) {
4511         if ( !$post = wp_get_post_revision( $post ) )
4512                 return false;
4513         if ( "{$post->post_parent}-autosave" !== $post->post_name )
4514                 return false;
4515         return (int) $post->post_parent;
4516 }
4517
4518 /**
4519  * Inserts post data into the posts table as a post revision.
4520  *
4521  * @package WordPress
4522  * @subpackage Post_Revisions
4523  * @since 2.6.0
4524  *
4525  * @uses wp_insert_post()
4526  *
4527  * @param int|object|array $post Post ID, post object OR post array.
4528  * @param bool $autosave Optional. Is the revision an autosave?
4529  * @return mixed Null or 0 if error, new revision ID if success.
4530  */
4531 function _wp_put_post_revision( $post = null, $autosave = false ) {
4532         if ( is_object($post) )
4533                 $post = get_object_vars( $post );
4534         elseif ( !is_array($post) )
4535                 $post = get_post($post, ARRAY_A);
4536         if ( !$post || empty($post['ID']) )
4537                 return;
4538
4539         if ( isset($post['post_type']) && 'revision' == $post['post_type'] )
4540                 return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) );
4541
4542         $post = _wp_post_revision_fields( $post, $autosave );
4543         $post = add_magic_quotes($post); //since data is from db
4544
4545         $revision_id = wp_insert_post( $post );
4546         if ( is_wp_error($revision_id) )
4547                 return $revision_id;
4548
4549         if ( $revision_id )
4550                 do_action( '_wp_put_post_revision', $revision_id );
4551         return $revision_id;
4552 }
4553
4554 /**
4555  * Gets a post revision.
4556  *
4557  * @package WordPress
4558  * @subpackage Post_Revisions
4559  * @since 2.6.0
4560  *
4561  * @uses get_post()
4562  *
4563  * @param int|object $post Post ID or post object
4564  * @param string $output Optional. OBJECT, ARRAY_A, or ARRAY_N.
4565  * @param string $filter Optional sanitation filter.  @see sanitize_post()
4566  * @return mixed Null if error or post object if success
4567  */
4568 function &wp_get_post_revision(&$post, $output = OBJECT, $filter = 'raw') {
4569         $null = null;
4570         if ( !$revision = get_post( $post, OBJECT, $filter ) )
4571                 return $revision;
4572         if ( 'revision' !== $revision->post_type )
4573                 return $null;
4574
4575         if ( $output == OBJECT ) {
4576                 return $revision;
4577         } elseif ( $output == ARRAY_A ) {
4578                 $_revision = get_object_vars($revision);
4579                 return $_revision;
4580         } elseif ( $output == ARRAY_N ) {
4581                 $_revision = array_values(get_object_vars($revision));
4582                 return $_revision;
4583         }
4584
4585         return $revision;
4586 }
4587
4588 /**
4589  * Restores a post to the specified revision.
4590  *
4591  * Can restore a past revision using all fields of the post revision, or only selected fields.
4592  *
4593  * @package WordPress
4594  * @subpackage Post_Revisions
4595  * @since 2.6.0
4596  *
4597  * @uses wp_get_post_revision()
4598  * @uses wp_update_post()
4599  * @uses do_action() Calls 'wp_restore_post_revision' on post ID and revision ID if wp_update_post()
4600  *  is successful.
4601  *
4602  * @param int|object $revision_id Revision ID or revision object.
4603  * @param array $fields Optional. What fields to restore from. Defaults to all.
4604  * @return mixed Null if error, false if no fields to restore, (int) post ID if success.
4605  */
4606 function wp_restore_post_revision( $revision_id, $fields = null ) {
4607         if ( !$revision = wp_get_post_revision( $revision_id, ARRAY_A ) )
4608                 return $revision;
4609
4610         if ( !is_array( $fields ) )
4611                 $fields = array_keys( _wp_post_revision_fields() );
4612
4613         $update = array();
4614         foreach( array_intersect( array_keys( $revision ), $fields ) as $field )
4615                 $update[$field] = $revision[$field];
4616
4617         if ( !$update )
4618                 return false;
4619
4620         $update['ID'] = $revision['post_parent'];
4621
4622         $update = add_magic_quotes( $update ); //since data is from db
4623
4624         $post_id = wp_update_post( $update );
4625         if ( is_wp_error( $post_id ) )
4626                 return $post_id;
4627
4628         if ( $post_id )
4629                 do_action( 'wp_restore_post_revision', $post_id, $revision['ID'] );
4630
4631         return $post_id;
4632 }
4633
4634 /**
4635  * Deletes a revision.
4636  *
4637  * Deletes the row from the posts table corresponding to the specified revision.
4638  *
4639  * @package WordPress
4640  * @subpackage Post_Revisions
4641  * @since 2.6.0
4642  *
4643  * @uses wp_get_post_revision()
4644  * @uses wp_delete_post()
4645  *
4646  * @param int|object $revision_id Revision ID or revision object.
4647  * @param array $fields Optional. What fields to restore from.  Defaults to all.
4648  * @return mixed Null if error, false if no fields to restore, (int) post ID if success.
4649  */
4650 function wp_delete_post_revision( $revision_id ) {
4651         if ( !$revision = wp_get_post_revision( $revision_id ) )
4652                 return $revision;
4653
4654         $delete = wp_delete_post( $revision->ID );
4655         if ( is_wp_error( $delete ) )
4656                 return $delete;
4657
4658         if ( $delete )
4659                 do_action( 'wp_delete_post_revision', $revision->ID, $revision );
4660
4661         return $delete;
4662 }
4663
4664 /**
4665  * Returns all revisions of specified post.
4666  *
4667  * @package WordPress
4668  * @subpackage Post_Revisions
4669  * @since 2.6.0
4670  *
4671  * @uses get_children()
4672  *
4673  * @param int|object $post_id Post ID or post object
4674  * @return array empty if no revisions
4675  */
4676 function wp_get_post_revisions( $post_id = 0, $args = null ) {
4677         if ( ! WP_POST_REVISIONS )
4678                 return array();
4679         if ( ( !$post = get_post( $post_id ) ) || empty( $post->ID ) )
4680                 return array();
4681
4682         $defaults = array( 'order' => 'DESC', 'orderby' => 'date' );
4683         $args = wp_parse_args( $args, $defaults );
4684         $args = array_merge( $args, array( 'post_parent' => $post->ID, 'post_type' => 'revision', 'post_status' => 'inherit' ) );
4685
4686         if ( !$revisions = get_children( $args ) )
4687                 return array();
4688         return $revisions;
4689 }
4690
4691 function _set_preview($post) {
4692
4693         if ( ! is_object($post) )
4694                 return $post;
4695
4696         $preview = wp_get_post_autosave($post->ID);
4697
4698         if ( ! is_object($preview) )
4699                 return $post;
4700
4701         $preview = sanitize_post($preview);
4702
4703         $post->post_content = $preview->post_content;
4704         $post->post_title = $preview->post_title;
4705         $post->post_excerpt = $preview->post_excerpt;
4706
4707         return $post;
4708 }
4709
4710 function _show_post_preview() {
4711
4712         if ( isset($_GET['preview_id']) && isset($_GET['preview_nonce']) ) {
4713                 $id = (int) $_GET['preview_id'];
4714
4715                 if ( false == wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . $id ) )
4716                         wp_die( __('You do not have permission to preview drafts.') );
4717
4718                 add_filter('the_preview', '_set_preview');
4719         }
4720 }