]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/post.php
Wordpress 3.0.4-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         if ( 
2081                 ( OBJECT == $mode && empty( $post->ID ) ) ||
2082                 ( OBJECT != $mode && empty( $post['ID'] ) )
2083         )
2084                 return ( OBJECT == $mode ? null : array() );
2085
2086         // Set categories and tags
2087         if ( $mode == OBJECT ) {
2088                 $post->post_category = array();
2089                 if ( is_object_in_taxonomy($post->post_type, 'category') )
2090                         $post->post_category = wp_get_post_categories($postid);
2091                 $post->tags_input = array();
2092                 if ( is_object_in_taxonomy($post->post_type, 'post_tag') )
2093                         $post->tags_input = wp_get_post_tags($postid, array('fields' => 'names'));
2094         } else {
2095                 $post['post_category'] = array();
2096                 if ( is_object_in_taxonomy($post['post_type'], 'category') )
2097                         $post['post_category'] = wp_get_post_categories($postid);
2098                 $post['tags_input'] = array();
2099                 if ( is_object_in_taxonomy($post['post_type'], 'post_tag') )
2100                         $post['tags_input'] = wp_get_post_tags($postid, array('fields' => 'names'));
2101         }
2102
2103         return $post;
2104 }
2105
2106 /**
2107  * Insert a post.
2108  *
2109  * If the $postarr parameter has 'ID' set to a value, then post will be updated.
2110  *
2111  * You can set the post date manually, but setting the values for 'post_date'
2112  * and 'post_date_gmt' keys. You can close the comments or open the comments by
2113  * setting the value for 'comment_status' key.
2114  *
2115  * The defaults for the parameter $postarr are:
2116  *     'post_status'   - Default is 'draft'.
2117  *     'post_type'     - Default is 'post'.
2118  *     'post_author'   - Default is current user ID ($user_ID). The ID of the user who added the post.
2119  *     'ping_status'   - Default is the value in 'default_ping_status' option.
2120  *                       Whether the attachment can accept pings.
2121  *     'post_parent'   - Default is 0. Set this for the post it belongs to, if any.
2122  *     'menu_order'    - Default is 0. The order it is displayed.
2123  *     'to_ping'       - Whether to ping.
2124  *     'pinged'        - Default is empty string.
2125  *     'post_password' - Default is empty string. The password to access the attachment.
2126  *     'guid'          - Global Unique ID for referencing the attachment.
2127  *     'post_content_filtered' - Post content filtered.
2128  *     'post_excerpt'  - Post excerpt.
2129  *
2130  * @since 1.0.0
2131  * @link http://core.trac.wordpress.org/ticket/9084 Bug report on 'wp_insert_post_data' filter.
2132  * @uses $wpdb
2133  * @uses $wp_rewrite
2134  * @uses $user_ID
2135  *
2136  * @uses do_action() Calls 'pre_post_update' on post ID if this is an update.
2137  * @uses do_action() Calls 'edit_post' action on post ID and post data if this is an update.
2138  * @uses do_action() Calls 'save_post' and 'wp_insert_post' on post id and post data just before
2139  *                   returning.
2140  *
2141  * @uses apply_filters() Calls 'wp_insert_post_data' passing $data, $postarr prior to database
2142  *                       update or insert.
2143  * @uses wp_transition_post_status()
2144  *
2145  * @param array $postarr Optional. Overrides defaults.
2146  * @param bool $wp_error Optional. Allow return of WP_Error on failure.
2147  * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
2148  */
2149 function wp_insert_post($postarr = array(), $wp_error = false) {
2150         global $wpdb, $wp_rewrite, $user_ID;
2151
2152         $defaults = array('post_status' => 'draft', 'post_type' => 'post', 'post_author' => $user_ID,
2153                 'ping_status' => get_option('default_ping_status'), 'post_parent' => 0,
2154                 'menu_order' => 0, 'to_ping' =>  '', 'pinged' => '', 'post_password' => '',
2155                 'guid' => '', 'post_content_filtered' => '', 'post_excerpt' => '', 'import_id' => 0,
2156                 'post_content' => '', 'post_title' => '');
2157
2158         $postarr = wp_parse_args($postarr, $defaults);
2159         $postarr = sanitize_post($postarr, 'db');
2160
2161         // export array as variables
2162         extract($postarr, EXTR_SKIP);
2163
2164         // Are we updating or creating?
2165         $update = false;
2166         if ( !empty($ID) ) {
2167                 $update = true;
2168                 $previous_status = get_post_field('post_status', $ID);
2169         } else {
2170                 $previous_status = 'new';
2171         }
2172
2173         if ( ('' == $post_content) && ('' == $post_title) && ('' == $post_excerpt) && ('attachment' != $post_type) ) {
2174                 if ( $wp_error )
2175                         return new WP_Error('empty_content', __('Content, title, and excerpt are empty.'));
2176                 else
2177                         return 0;
2178         }
2179
2180         if ( empty($post_type) )
2181                 $post_type = 'post';
2182
2183         if ( empty($post_status) )
2184                 $post_status = 'draft';
2185
2186         if ( !empty($post_category) )
2187                 $post_category = array_filter($post_category); // Filter out empty terms
2188
2189         // Make sure we set a valid category.
2190         if ( empty($post_category) || 0 == count($post_category) || !is_array($post_category) ) {
2191                 // 'post' requires at least one category.
2192                 if ( 'post' == $post_type && 'auto-draft' != $post_status )
2193                         $post_category = array( get_option('default_category') );
2194                 else
2195                         $post_category = array();
2196         }
2197
2198         if ( empty($post_author) )
2199                 $post_author = $user_ID;
2200
2201         $post_ID = 0;
2202
2203         // Get the post ID and GUID
2204         if ( $update ) {
2205                 $post_ID = (int) $ID;
2206                 $guid = get_post_field( 'guid', $post_ID );
2207                 $post_before = get_post($post_ID);
2208         }
2209
2210         // Don't allow contributors to set to set the post slug for pending review posts
2211         if ( 'pending' == $post_status && !current_user_can( 'publish_posts' ) )
2212                 $post_name = '';
2213
2214         // Create a valid post name.  Drafts and pending posts are allowed to have an empty
2215         // post name.
2216         if ( empty($post_name) ) {
2217                 if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2218                         $post_name = sanitize_title($post_title);
2219                 else
2220                         $post_name = '';
2221         } else {
2222                 $post_name = sanitize_title($post_name);
2223         }
2224
2225         // 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
2226         if ( empty($post_date) || '0000-00-00 00:00:00' == $post_date )
2227                 $post_date = current_time('mysql');
2228
2229         if ( empty($post_date_gmt) || '0000-00-00 00:00:00' == $post_date_gmt ) {
2230                 if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2231                         $post_date_gmt = get_gmt_from_date($post_date);
2232                 else
2233                         $post_date_gmt = '0000-00-00 00:00:00';
2234         }
2235
2236         if ( $update || '0000-00-00 00:00:00' == $post_date ) {
2237                 $post_modified     = current_time( 'mysql' );
2238                 $post_modified_gmt = current_time( 'mysql', 1 );
2239         } else {
2240                 $post_modified     = $post_date;
2241                 $post_modified_gmt = $post_date_gmt;
2242         }
2243
2244         if ( 'publish' == $post_status ) {
2245                 $now = gmdate('Y-m-d H:i:59');
2246                 if ( mysql2date('U', $post_date_gmt, false) > mysql2date('U', $now, false) )
2247                         $post_status = 'future';
2248         } elseif( 'future' == $post_status ) {
2249                 $now = gmdate('Y-m-d H:i:59');
2250                 if ( mysql2date('U', $post_date_gmt, false) <= mysql2date('U', $now, false) )
2251                         $post_status = 'publish';
2252         }
2253
2254         if ( empty($comment_status) ) {
2255                 if ( $update )
2256                         $comment_status = 'closed';
2257                 else
2258                         $comment_status = get_option('default_comment_status');
2259         }
2260         if ( empty($ping_status) )
2261                 $ping_status = get_option('default_ping_status');
2262
2263         if ( isset($to_ping) )
2264                 $to_ping = preg_replace('|\s+|', "\n", $to_ping);
2265         else
2266                 $to_ping = '';
2267
2268         if ( ! isset($pinged) )
2269                 $pinged = '';
2270
2271         if ( isset($post_parent) )
2272                 $post_parent = (int) $post_parent;
2273         else
2274                 $post_parent = 0;
2275
2276         if ( !empty($post_ID) ) {
2277                 if ( $post_parent == $post_ID ) {
2278                         // Post can't be its own parent
2279                         $post_parent = 0;
2280                 } elseif ( !empty($post_parent) ) {
2281                         $parent_post = get_post($post_parent);
2282                         // Check for circular dependency
2283                         if ( isset( $parent_post->post_parent ) && $parent_post->post_parent == $post_ID )
2284                                 $post_parent = 0;
2285                 }
2286         }
2287
2288         if ( isset($menu_order) )
2289                 $menu_order = (int) $menu_order;
2290         else
2291                 $menu_order = 0;
2292
2293         if ( !isset($post_password) || 'private' == $post_status )
2294                 $post_password = '';
2295
2296         $post_name = wp_unique_post_slug($post_name, $post_ID, $post_status, $post_type, $post_parent);
2297
2298         // expected_slashed (everything!)
2299         $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' ) );
2300         $data = apply_filters('wp_insert_post_data', $data, $postarr);
2301         $data = stripslashes_deep( $data );
2302         $where = array( 'ID' => $post_ID );
2303
2304         if ( $update ) {
2305                 do_action( 'pre_post_update', $post_ID );
2306                 if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
2307                         if ( $wp_error )
2308                                 return new WP_Error('db_update_error', __('Could not update post in the database'), $wpdb->last_error);
2309                         else
2310                                 return 0;
2311                 }
2312         } else {
2313                 if ( isset($post_mime_type) )
2314                         $data['post_mime_type'] = stripslashes( $post_mime_type ); // This isn't in the update
2315                 // If there is a suggested ID, use it if not already present
2316                 if ( !empty($import_id) ) {
2317                         $import_id = (int) $import_id;
2318                         if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
2319                                 $data['ID'] = $import_id;
2320                         }
2321                 }
2322                 if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
2323                         if ( $wp_error )
2324                                 return new WP_Error('db_insert_error', __('Could not insert post into the database'), $wpdb->last_error);
2325                         else
2326                                 return 0;
2327                 }
2328                 $post_ID = (int) $wpdb->insert_id;
2329
2330                 // use the newly generated $post_ID
2331                 $where = array( 'ID' => $post_ID );
2332         }
2333
2334         if ( empty($data['post_name']) && !in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
2335                 $data['post_name'] = sanitize_title($data['post_title'], $post_ID);
2336                 $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
2337         }
2338
2339         if ( is_object_in_taxonomy($post_type, 'category') )
2340                 wp_set_post_categories( $post_ID, $post_category );
2341
2342         if ( isset( $tags_input ) && is_object_in_taxonomy($post_type, 'post_tag') )
2343                 wp_set_post_tags( $post_ID, $tags_input );
2344
2345         // new-style support for all custom taxonomies
2346         if ( !empty($tax_input) ) {
2347                 foreach ( $tax_input as $taxonomy => $tags ) {
2348                         $taxonomy_obj = get_taxonomy($taxonomy);
2349                         if ( is_array($tags) ) // array = hierarchical, string = non-hierarchical.
2350                                 $tags = array_filter($tags);
2351                         if ( current_user_can($taxonomy_obj->cap->assign_terms) )
2352                                 wp_set_post_terms( $post_ID, $tags, $taxonomy );
2353                 }
2354         }
2355
2356         $current_guid = get_post_field( 'guid', $post_ID );
2357
2358         if ( 'page' == $data['post_type'] )
2359                 clean_page_cache($post_ID);
2360         else
2361                 clean_post_cache($post_ID);
2362
2363         // Set GUID
2364         if ( !$update && '' == $current_guid )
2365                 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
2366
2367         $post = get_post($post_ID);
2368
2369         if ( !empty($page_template) && 'page' == $data['post_type'] ) {
2370                 $post->page_template = $page_template;
2371                 $page_templates = get_page_templates();
2372                 if ( 'default' != $page_template && !in_array($page_template, $page_templates) ) {
2373                         if ( $wp_error )
2374                                 return new WP_Error('invalid_page_template', __('The page template is invalid.'));
2375                         else
2376                                 return 0;
2377                 }
2378                 update_post_meta($post_ID, '_wp_page_template',  $page_template);
2379         }
2380
2381         wp_transition_post_status($data['post_status'], $previous_status, $post);
2382
2383         if ( $update ) {
2384                 do_action('edit_post', $post_ID, $post);
2385                 $post_after = get_post($post_ID);
2386                 do_action( 'post_updated', $post_ID, $post_after, $post_before);
2387         }
2388
2389         do_action('save_post', $post_ID, $post);
2390         do_action('wp_insert_post', $post_ID, $post);
2391
2392         return $post_ID;
2393 }
2394
2395 /**
2396  * Update a post with new post data.
2397  *
2398  * The date does not have to be set for drafts. You can set the date and it will
2399  * not be overridden.
2400  *
2401  * @since 1.0.0
2402  *
2403  * @param array|object $postarr Post data. Arrays are expected to be escaped, objects are not.
2404  * @return int 0 on failure, Post ID on success.
2405  */
2406 function wp_update_post($postarr = array()) {
2407         if ( is_object($postarr) ) {
2408                 // non-escaped post was passed
2409                 $postarr = get_object_vars($postarr);
2410                 $postarr = add_magic_quotes($postarr);
2411         }
2412
2413         // First, get all of the original fields
2414         $post = wp_get_single_post($postarr['ID'], ARRAY_A);
2415
2416         // Escape data pulled from DB.
2417         $post = add_magic_quotes($post);
2418
2419         // Passed post category list overwrites existing category list if not empty.
2420         if ( isset($postarr['post_category']) && is_array($postarr['post_category'])
2421                          && 0 != count($postarr['post_category']) )
2422                 $post_cats = $postarr['post_category'];
2423         else
2424                 $post_cats = $post['post_category'];
2425
2426         // Drafts shouldn't be assigned a date unless explicitly done so by the user
2427         if ( isset( $post['post_status'] ) && in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) && empty($postarr['edit_date']) &&
2428                          ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
2429                 $clear_date = true;
2430         else
2431                 $clear_date = false;
2432
2433         // Merge old and new fields with new fields overwriting old ones.
2434         $postarr = array_merge($post, $postarr);
2435         $postarr['post_category'] = $post_cats;
2436         if ( $clear_date ) {
2437                 $postarr['post_date'] = current_time('mysql');
2438                 $postarr['post_date_gmt'] = '';
2439         }
2440
2441         if ($postarr['post_type'] == 'attachment')
2442                 return wp_insert_attachment($postarr);
2443
2444         return wp_insert_post($postarr);
2445 }
2446
2447 /**
2448  * Publish a post by transitioning the post status.
2449  *
2450  * @since 2.1.0
2451  * @uses $wpdb
2452  * @uses do_action() Calls 'edit_post', 'save_post', and 'wp_insert_post' on post_id and post data.
2453  *
2454  * @param int $post_id Post ID.
2455  * @return null
2456  */
2457 function wp_publish_post($post_id) {
2458         global $wpdb;
2459
2460         $post = get_post($post_id);
2461
2462         if ( empty($post) )
2463                 return;
2464
2465         if ( 'publish' == $post->post_status )
2466                 return;
2467
2468         $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post_id ) );
2469
2470         $old_status = $post->post_status;
2471         $post->post_status = 'publish';
2472         wp_transition_post_status('publish', $old_status, $post);
2473
2474         // Update counts for the post's terms.
2475         foreach ( (array) get_object_taxonomies('post') as $taxonomy ) {
2476                 $tt_ids = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'tt_ids'));
2477                 wp_update_term_count($tt_ids, $taxonomy);
2478         }
2479
2480         do_action('edit_post', $post_id, $post);
2481         do_action('save_post', $post_id, $post);
2482         do_action('wp_insert_post', $post_id, $post);
2483 }
2484
2485 /**
2486  * Publish future post and make sure post ID has future post status.
2487  *
2488  * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
2489  * from publishing drafts, etc.
2490  *
2491  * @since 2.5.0
2492  *
2493  * @param int $post_id Post ID.
2494  * @return null Nothing is returned. Which can mean that no action is required or post was published.
2495  */
2496 function check_and_publish_future_post($post_id) {
2497
2498         $post = get_post($post_id);
2499
2500         if ( empty($post) )
2501                 return;
2502
2503         if ( 'future' != $post->post_status )
2504                 return;
2505
2506         $time = strtotime( $post->post_date_gmt . ' GMT' );
2507
2508         if ( $time > time() ) { // Uh oh, someone jumped the gun!
2509                 wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
2510                 wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
2511                 return;
2512         }
2513
2514         return wp_publish_post($post_id);
2515 }
2516
2517
2518 /**
2519  * Computes a unique slug for the post, when given the desired slug and some post details.
2520  *
2521  * @global wpdb $wpdb
2522  * @global WP_Rewrite $wp_rewrite
2523  * @param string $slug the desired slug (post_name)
2524  * @param integer $post_ID
2525  * @param string $post_status no uniqueness checks are made if the post is still draft or pending
2526  * @param string $post_type
2527  * @param integer $post_parent
2528  * @return string unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
2529  */
2530 function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
2531         if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) )
2532                 return $slug;
2533
2534         global $wpdb, $wp_rewrite;
2535
2536         $feeds = $wp_rewrite->feeds;
2537         if ( ! is_array( $feeds ) )
2538                 $feeds = array();
2539
2540         $hierarchical_post_types = get_post_types( array('hierarchical' => true) );
2541         if ( 'attachment' == $post_type ) {
2542                 // Attachment slugs must be unique across all types.
2543                 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
2544                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
2545
2546                 if ( $post_name_check || in_array( $slug, $feeds ) ) {
2547                         $suffix = 2;
2548                         do {
2549                                 $alt_post_name = substr ($slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2550                                 $post_name_check = $wpdb->get_var( $wpdb->prepare($check_sql, $alt_post_name, $post_ID ) );
2551                                 $suffix++;
2552                         } while ( $post_name_check );
2553                         $slug = $alt_post_name;
2554                 }
2555         } elseif ( in_array( $post_type, $hierarchical_post_types ) ) {
2556                 // Page slugs must be unique within their own trees. Pages are in a separate
2557                 // namespace than posts so page slugs are allowed to overlap post slugs.
2558                 $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";
2559                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID, $post_parent ) );
2560
2561                 if ( $post_name_check || in_array( $slug, $feeds ) || preg_match( '@^(page)?\d+$@', $slug ) ) {
2562                         $suffix = 2;
2563                         do {
2564                                 $alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2565                                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID, $post_parent ) );
2566                                 $suffix++;
2567                         } while ( $post_name_check );
2568                         $slug = $alt_post_name;
2569                 }
2570         } else {
2571                 // Post slugs must be unique across all posts.
2572                 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
2573                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
2574
2575                 if ( $post_name_check || in_array( $slug, $feeds ) ) {
2576                         $suffix = 2;
2577                         do {
2578                                 $alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
2579                                 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
2580                                 $suffix++;
2581                         } while ( $post_name_check );
2582                         $slug = $alt_post_name;
2583                 }
2584         }
2585
2586         return $slug;
2587 }
2588
2589 /**
2590  * Adds tags to a post.
2591  *
2592  * @uses wp_set_post_tags() Same first two parameters, but the last parameter is always set to true.
2593  *
2594  * @package WordPress
2595  * @subpackage Post
2596  * @since 2.3.0
2597  *
2598  * @param int $post_id Post ID
2599  * @param string $tags The tags to set for the post, separated by commas.
2600  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2601  */
2602 function wp_add_post_tags($post_id = 0, $tags = '') {
2603         return wp_set_post_tags($post_id, $tags, true);
2604 }
2605
2606
2607 /**
2608  * Set the tags for a post.
2609  *
2610  * @since 2.3.0
2611  * @uses wp_set_object_terms() Sets the tags for the post.
2612  *
2613  * @param int $post_id Post ID.
2614  * @param string $tags The tags to set for the post, separated by commas.
2615  * @param bool $append If true, don't delete existing tags, just add on. If false, replace the tags with the new tags.
2616  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2617  */
2618 function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
2619         return wp_set_post_terms( $post_id, $tags, 'post_tag', $append);
2620 }
2621
2622 /**
2623  * Set the terms for a post.
2624  *
2625  * @since 2.8.0
2626  * @uses wp_set_object_terms() Sets the tags for the post.
2627  *
2628  * @param int $post_id Post ID.
2629  * @param string $tags The tags to set for the post, separated by commas.
2630  * @param bool $append If true, don't delete existing tags, just add on. If false, replace the tags with the new tags.
2631  * @return bool|null Will return false if $post_id is not an integer or is 0. Will return null otherwise
2632  */
2633 function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
2634         $post_id = (int) $post_id;
2635
2636         if ( !$post_id )
2637                 return false;
2638
2639         if ( empty($tags) )
2640                 $tags = array();
2641
2642         $tags = is_array($tags) ? $tags : explode( ',', trim($tags, " \n\t\r\0\x0B,") );
2643
2644         // Hierarchical taxonomies must always pass IDs rather than names so that children with the same
2645         // names but different parents aren't confused.
2646         if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2647                 $tags = array_map( 'intval', $tags );
2648                 $tags = array_unique( $tags );
2649         }
2650
2651         wp_set_object_terms($post_id, $tags, $taxonomy, $append);
2652 }
2653
2654 /**
2655  * Set categories for a post.
2656  *
2657  * If the post categories parameter is not set, then the default category is
2658  * going used.
2659  *
2660  * @since 2.1.0
2661  *
2662  * @param int $post_ID Post ID.
2663  * @param array $post_categories Optional. List of categories.
2664  * @return bool|mixed
2665  */
2666 function wp_set_post_categories($post_ID = 0, $post_categories = array()) {
2667         $post_ID = (int) $post_ID;
2668         $post_type = get_post_type( $post_ID );
2669         $post_status = get_post_status( $post_ID );
2670         // If $post_categories isn't already an array, make it one:
2671         if ( !is_array($post_categories) || empty($post_categories) ) {
2672                 if ( 'post' == $post_type && 'auto-draft' != $post_status )
2673                         $post_categories = array( get_option('default_category') );
2674                 else
2675                         $post_categories = array();
2676         } else if ( 1 == count($post_categories) && '' == reset($post_categories) ) {
2677                 return true;
2678         }
2679
2680         if ( !empty($post_categories) ) {
2681                 $post_categories = array_map('intval', $post_categories);
2682                 $post_categories = array_unique($post_categories);
2683         }
2684
2685         return wp_set_object_terms($post_ID, $post_categories, 'category');
2686 }
2687
2688 /**
2689  * Transition the post status of a post.
2690  *
2691  * Calls hooks to transition post status.
2692  *
2693  * The first is 'transition_post_status' with new status, old status, and post data.
2694  *
2695  * The next action called is 'OLDSTATUS_to_NEWSTATUS' the 'NEWSTATUS' is the
2696  * $new_status parameter and the 'OLDSTATUS' is $old_status parameter; it has the
2697  * post data.
2698  *
2699  * The final action is named 'NEWSTATUS_POSTTYPE', 'NEWSTATUS' is from the $new_status
2700  * parameter and POSTTYPE is post_type post data.
2701  *
2702  * @since 2.3.0
2703  * @link http://codex.wordpress.org/Post_Status_Transitions
2704  *
2705  * @uses do_action() Calls 'transition_post_status' on $new_status, $old_status and
2706  *  $post if there is a status change.
2707  * @uses do_action() Calls '${old_status}_to_$new_status' on $post if there is a status change.
2708  * @uses do_action() Calls '${new_status}_$post->post_type' on post ID and $post.
2709  *
2710  * @param string $new_status Transition to this post status.
2711  * @param string $old_status Previous post status.
2712  * @param object $post Post data.
2713  */
2714 function wp_transition_post_status($new_status, $old_status, $post) {
2715         do_action('transition_post_status', $new_status, $old_status, $post);
2716         do_action("${old_status}_to_$new_status", $post);
2717         do_action("${new_status}_$post->post_type", $post->ID, $post);
2718 }
2719
2720 //
2721 // Trackback and ping functions
2722 //
2723
2724 /**
2725  * Add a URL to those already pung.
2726  *
2727  * @since 1.5.0
2728  * @uses $wpdb
2729  *
2730  * @param int $post_id Post ID.
2731  * @param string $uri Ping URI.
2732  * @return int How many rows were updated.
2733  */
2734 function add_ping($post_id, $uri) {
2735         global $wpdb;
2736         $pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
2737         $pung = trim($pung);
2738         $pung = preg_split('/\s/', $pung);
2739         $pung[] = $uri;
2740         $new = implode("\n", $pung);
2741         $new = apply_filters('add_ping', $new);
2742         // expected_slashed ($new)
2743         $new = stripslashes($new);
2744         return $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post_id ) );
2745 }
2746
2747 /**
2748  * Retrieve enclosures already enclosed for a post.
2749  *
2750  * @since 1.5.0
2751  * @uses $wpdb
2752  *
2753  * @param int $post_id Post ID.
2754  * @return array List of enclosures
2755  */
2756 function get_enclosed($post_id) {
2757         $custom_fields = get_post_custom( $post_id );
2758         $pung = array();
2759         if ( !is_array( $custom_fields ) )
2760                 return $pung;
2761
2762         foreach ( $custom_fields as $key => $val ) {
2763                 if ( 'enclosure' != $key || !is_array( $val ) )
2764                         continue;
2765                 foreach( $val as $enc ) {
2766                         $enclosure = split( "\n", $enc );
2767                         $pung[] = trim( $enclosure[ 0 ] );
2768                 }
2769         }
2770         $pung = apply_filters('get_enclosed', $pung);
2771         return $pung;
2772 }
2773
2774 /**
2775  * Retrieve URLs already pinged for a post.
2776  *
2777  * @since 1.5.0
2778  * @uses $wpdb
2779  *
2780  * @param int $post_id Post ID.
2781  * @return array
2782  */
2783 function get_pung($post_id) {
2784         global $wpdb;
2785         $pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
2786         $pung = trim($pung);
2787         $pung = preg_split('/\s/', $pung);
2788         $pung = apply_filters('get_pung', $pung);
2789         return $pung;
2790 }
2791
2792 /**
2793  * Retrieve URLs that need to be pinged.
2794  *
2795  * @since 1.5.0
2796  * @uses $wpdb
2797  *
2798  * @param int $post_id Post ID
2799  * @return array
2800  */
2801 function get_to_ping($post_id) {
2802         global $wpdb;
2803         $to_ping = $wpdb->get_var( $wpdb->prepare( "SELECT to_ping FROM $wpdb->posts WHERE ID = %d", $post_id ));
2804         $to_ping = trim($to_ping);
2805         $to_ping = preg_split('/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY);
2806         $to_ping = apply_filters('get_to_ping',  $to_ping);
2807         return $to_ping;
2808 }
2809
2810 /**
2811  * Do trackbacks for a list of URLs.
2812  *
2813  * @since 1.0.0
2814  *
2815  * @param string $tb_list Comma separated list of URLs
2816  * @param int $post_id Post ID
2817  */
2818 function trackback_url_list($tb_list, $post_id) {
2819         if ( ! empty( $tb_list ) ) {
2820                 // get post data
2821                 $postdata = wp_get_single_post($post_id, ARRAY_A);
2822
2823                 // import postdata as variables
2824                 extract($postdata, EXTR_SKIP);
2825
2826                 // form an excerpt
2827                 $excerpt = strip_tags($post_excerpt ? $post_excerpt : $post_content);
2828
2829                 if (strlen($excerpt) > 255) {
2830                         $excerpt = substr($excerpt,0,252) . '...';
2831                 }
2832
2833                 $trackback_urls = explode(',', $tb_list);
2834                 foreach( (array) $trackback_urls as $tb_url) {
2835                         $tb_url = trim($tb_url);
2836                         trackback($tb_url, stripslashes($post_title), $excerpt, $post_id);
2837                 }
2838         }
2839 }
2840
2841 //
2842 // Page functions
2843 //
2844
2845 /**
2846  * Get a list of page IDs.
2847  *
2848  * @since 2.0.0
2849  * @uses $wpdb
2850  *
2851  * @return array List of page IDs.
2852  */
2853 function get_all_page_ids() {
2854         global $wpdb;
2855
2856         if ( ! $page_ids = wp_cache_get('all_page_ids', 'posts') ) {
2857                 $page_ids = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'page'");
2858                 wp_cache_add('all_page_ids', $page_ids, 'posts');
2859         }
2860
2861         return $page_ids;
2862 }
2863
2864 /**
2865  * Retrieves page data given a page ID or page object.
2866  *
2867  * @since 1.5.1
2868  *
2869  * @param mixed $page Page object or page ID. Passed by reference.
2870  * @param string $output What to output. OBJECT, ARRAY_A, or ARRAY_N.
2871  * @param string $filter How the return value should be filtered.
2872  * @return mixed Page data.
2873  */
2874 function &get_page(&$page, $output = OBJECT, $filter = 'raw') {
2875         $p = get_post($page, $output, $filter);
2876         return $p;
2877 }
2878
2879 /**
2880  * Retrieves a page given its path.
2881  *
2882  * @since 2.1.0
2883  * @uses $wpdb
2884  *
2885  * @param string $page_path Page path
2886  * @param string $output Optional. Output type. OBJECT, ARRAY_N, or ARRAY_A. Default OBJECT.
2887  * @param string $post_type Optional. Post type. Default page.
2888  * @return mixed Null when complete.
2889  */
2890 function get_page_by_path($page_path, $output = OBJECT, $post_type = 'page') {
2891         global $wpdb;
2892         $page_path = rawurlencode(urldecode($page_path));
2893         $page_path = str_replace('%2F', '/', $page_path);
2894         $page_path = str_replace('%20', ' ', $page_path);
2895         $page_paths = '/' . trim($page_path, '/');
2896         $leaf_path  = sanitize_title(basename($page_paths));
2897         $page_paths = explode('/', $page_paths);
2898         $full_path = '';
2899         foreach ( (array) $page_paths as $pathdir )
2900                 $full_path .= ( $pathdir != '' ? '/' : '' ) . sanitize_title($pathdir);
2901
2902         $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 ));
2903
2904         if ( empty($pages) )
2905                 return null;
2906
2907         foreach ( $pages as $page ) {
2908                 $path = '/' . $leaf_path;
2909                 $curpage = $page;
2910                 while ( $curpage->post_parent != 0 ) {
2911                         $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 ));
2912                         $path = '/' . $curpage->post_name . $path;
2913                 }
2914
2915                 if ( $path == $full_path )
2916                         return get_page($page->ID, $output, $post_type);
2917         }
2918
2919         return null;
2920 }
2921
2922 /**
2923  * Retrieve a page given its title.
2924  *
2925  * @since 2.1.0
2926  * @uses $wpdb
2927  *
2928  * @param string $page_title Page title
2929  * @param string $output Optional. Output type. OBJECT, ARRAY_N, or ARRAY_A. Default OBJECT.
2930  * @param string $post_type Optional. Post type. Default page.
2931  * @return mixed
2932  */
2933 function get_page_by_title($page_title, $output = OBJECT, $post_type = 'page' ) {
2934         global $wpdb;
2935         $page = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type= %s", $page_title, $post_type ) );
2936         if ( $page )
2937                 return get_page($page, $output);
2938
2939         return null;
2940 }
2941
2942 /**
2943  * Retrieve child pages from list of pages matching page ID.
2944  *
2945  * Matches against the pages parameter against the page ID. Also matches all
2946  * children for the same to retrieve all children of a page. Does not make any
2947  * SQL queries to get the children.
2948  *
2949  * @since 1.5.1
2950  *
2951  * @param int $page_id Page ID.
2952  * @param array $pages List of pages' objects.
2953  * @return array
2954  */
2955 function &get_page_children($page_id, $pages) {
2956         $page_list = array();
2957         foreach ( (array) $pages as $page ) {
2958                 if ( $page->post_parent == $page_id ) {
2959                         $page_list[] = $page;
2960                         if ( $children = get_page_children($page->ID, $pages) )
2961                                 $page_list = array_merge($page_list, $children);
2962                 }
2963         }
2964         return $page_list;
2965 }
2966
2967 /**
2968  * Order the pages with children under parents in a flat list.
2969  *
2970  * It uses auxiliary structure to hold parent-children relationships and
2971  * runs in O(N) complexity
2972  *
2973  * @since 2.0.0
2974  *
2975  * @param array $posts Posts array.
2976  * @param int $parent Parent page ID.
2977  * @return array A list arranged by hierarchy. Children immediately follow their parents.
2978  */
2979 function &get_page_hierarchy( &$pages, $page_id = 0 ) {
2980
2981         if ( empty( $pages ) ) {
2982                 $result = array();
2983                 return $result;
2984         }
2985
2986         $children = array();
2987         foreach ( (array) $pages as $p ) {
2988
2989                 $parent_id = intval( $p->post_parent );
2990                 $children[ $parent_id ][] = $p;
2991          }
2992
2993          $result = array();
2994          _page_traverse_name( $page_id, $children, $result );
2995
2996         return $result;
2997 }
2998
2999 /**
3000  * function to traverse and return all the nested children post names of a root page.
3001  * $children contains parent-chilren relations
3002  *
3003  */
3004 function _page_traverse_name( $page_id, &$children, &$result ){
3005
3006         if ( isset( $children[ $page_id ] ) ){
3007
3008                 foreach( (array)$children[ $page_id ] as $child ) {
3009
3010                         $result[ $child->ID ] = $child->post_name;
3011                         _page_traverse_name( $child->ID, $children, $result );
3012                 }
3013         }
3014 }
3015
3016 /**
3017  * Builds URI for a page.
3018  *
3019  * Sub pages will be in the "directory" under the parent page post name.
3020  *
3021  * @since 1.5.0
3022  *
3023  * @param mixed $page Page object or page ID.
3024  * @return string Page URI.
3025  */
3026 function get_page_uri($page) {
3027         if ( ! is_object($page) )
3028                 $page = get_page($page);
3029         $uri = $page->post_name;
3030
3031         // A page cannot be it's own parent.
3032         if ( $page->post_parent == $page->ID )
3033                 return $uri;
3034
3035         while ($page->post_parent != 0) {
3036                 $page = get_page($page->post_parent);
3037                 $uri = $page->post_name . "/" . $uri;
3038         }
3039
3040         return $uri;
3041 }
3042
3043 /**
3044  * Retrieve a list of pages.
3045  *
3046  * The defaults that can be overridden are the following: 'child_of',
3047  * 'sort_order', 'sort_column', 'post_title', 'hierarchical', 'exclude',
3048  * 'include', 'meta_key', 'meta_value','authors', 'number', and 'offset'.
3049  *
3050  * @since 1.5.0
3051  * @uses $wpdb
3052  *
3053  * @param mixed $args Optional. Array or string of options that overrides defaults.
3054  * @return array List of pages matching defaults or $args
3055  */
3056 function &get_pages($args = '') {
3057         global $wpdb;
3058
3059         $defaults = array(
3060                 'child_of' => 0, 'sort_order' => 'ASC',
3061                 'sort_column' => 'post_title', 'hierarchical' => 1,
3062                 'exclude' => array(), 'include' => array(),
3063                 'meta_key' => '', 'meta_value' => '',
3064                 'authors' => '', 'parent' => -1, 'exclude_tree' => '',
3065                 'number' => '', 'offset' => 0,
3066                 'post_type' => 'page', 'post_status' => 'publish',
3067         );
3068
3069         $r = wp_parse_args( $args, $defaults );
3070         extract( $r, EXTR_SKIP );
3071         $number = (int) $number;
3072         $offset = (int) $offset;
3073
3074         // Make sure the post type is hierarchical
3075         $hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
3076         if ( !in_array( $post_type, $hierarchical_post_types ) )
3077                 return false;
3078
3079         // Make sure we have a valid post status
3080         if ( !in_array($post_status, get_post_stati()) )
3081                 return false;
3082
3083         $cache = array();
3084         $key = md5( serialize( compact(array_keys($defaults)) ) );
3085         if ( $cache = wp_cache_get( 'get_pages', 'posts' ) ) {
3086                 if ( is_array($cache) && isset( $cache[ $key ] ) ) {
3087                         $pages = apply_filters('get_pages', $cache[ $key ], $r );
3088                         return $pages;
3089                 }
3090         }
3091
3092         if ( !is_array($cache) )
3093                 $cache = array();
3094
3095         $inclusions = '';
3096         if ( !empty($include) ) {
3097                 $child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
3098                 $parent = -1;
3099                 $exclude = '';
3100                 $meta_key = '';
3101                 $meta_value = '';
3102                 $hierarchical = false;
3103                 $incpages = wp_parse_id_list( $include );
3104                 if ( ! empty( $incpages ) ) {
3105                         foreach ( $incpages as $incpage ) {
3106                                 if (empty($inclusions))
3107                                         $inclusions = $wpdb->prepare(' AND ( ID = %d ', $incpage);
3108                                 else
3109                                         $inclusions .= $wpdb->prepare(' OR ID = %d ', $incpage);
3110                         }
3111                 }
3112         }
3113         if (!empty($inclusions))
3114                 $inclusions .= ')';
3115
3116         $exclusions = '';
3117         if ( !empty($exclude) ) {
3118                 $expages = wp_parse_id_list( $exclude );
3119                 if ( ! empty( $expages ) ) {
3120                         foreach ( $expages as $expage ) {
3121                                 if (empty($exclusions))
3122                                         $exclusions = $wpdb->prepare(' AND ( ID <> %d ', $expage);
3123                                 else
3124                                         $exclusions .= $wpdb->prepare(' AND ID <> %d ', $expage);
3125                         }
3126                 }
3127         }
3128         if (!empty($exclusions))
3129                 $exclusions .= ')';
3130
3131         $author_query = '';
3132         if (!empty($authors)) {
3133                 $post_authors = preg_split('/[\s,]+/',$authors);
3134
3135                 if ( ! empty( $post_authors ) ) {
3136                         foreach ( $post_authors as $post_author ) {
3137                                 //Do we have an author id or an author login?
3138                                 if ( 0 == intval($post_author) ) {
3139                                         $post_author = get_userdatabylogin($post_author);
3140                                         if ( empty($post_author) )
3141                                                 continue;
3142                                         if ( empty($post_author->ID) )
3143                                                 continue;
3144                                         $post_author = $post_author->ID;
3145                                 }
3146
3147                                 if ( '' == $author_query )
3148                                         $author_query = $wpdb->prepare(' post_author = %d ', $post_author);
3149                                 else
3150                                         $author_query .= $wpdb->prepare(' OR post_author = %d ', $post_author);
3151                         }
3152                         if ( '' != $author_query )
3153                                 $author_query = " AND ($author_query)";
3154                 }
3155         }
3156
3157         $join = '';
3158         $where = "$exclusions $inclusions ";
3159         if ( ! empty( $meta_key ) || ! empty( $meta_value ) ) {
3160                 $join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
3161
3162                 // meta_key and meta_value might be slashed
3163                 $meta_key = stripslashes($meta_key);
3164                 $meta_value = stripslashes($meta_value);
3165                 if ( ! empty( $meta_key ) )
3166                         $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_key = %s", $meta_key);
3167                 if ( ! empty( $meta_value ) )
3168                         $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_value = %s", $meta_value);
3169
3170         }
3171
3172         if ( $parent >= 0 )
3173                 $where .= $wpdb->prepare(' AND post_parent = %d ', $parent);
3174
3175         $where_post_type = $wpdb->prepare( "post_type = '%s' AND post_status = '%s'", $post_type, $post_status );
3176
3177         $query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
3178         $query .= $author_query;
3179         $query .= " ORDER BY " . $sort_column . " " . $sort_order ;
3180
3181         if ( !empty($number) )
3182                 $query .= ' LIMIT ' . $offset . ',' . $number;
3183
3184         $pages = $wpdb->get_results($query);
3185
3186         if ( empty($pages) ) {
3187                 $pages = apply_filters('get_pages', array(), $r);
3188                 return $pages;
3189         }
3190
3191         // Sanitize before caching so it'll only get done once
3192         $num_pages = count($pages);
3193         for ($i = 0; $i < $num_pages; $i++) {
3194                 $pages[$i] = sanitize_post($pages[$i], 'raw');
3195         }
3196
3197         // Update cache.
3198         update_page_cache($pages);
3199
3200         if ( $child_of || $hierarchical )
3201                 $pages = & get_page_children($child_of, $pages);
3202
3203         if ( !empty($exclude_tree) ) {
3204                 $exclude = (int) $exclude_tree;
3205                 $children = get_page_children($exclude, $pages);
3206                 $excludes = array();
3207                 foreach ( $children as $child )
3208                         $excludes[] = $child->ID;
3209                 $excludes[] = $exclude;
3210                 $num_pages = count($pages);
3211                 for ( $i = 0; $i < $num_pages; $i++ ) {
3212                         if ( in_array($pages[$i]->ID, $excludes) )
3213                                 unset($pages[$i]);
3214                 }
3215         }
3216
3217         $cache[ $key ] = $pages;
3218         wp_cache_set( 'get_pages', $cache, 'posts' );
3219
3220         $pages = apply_filters('get_pages', $pages, $r);
3221
3222         return $pages;
3223 }
3224
3225 //
3226 // Attachment functions
3227 //
3228
3229 /**
3230  * Check if the attachment URI is local one and is really an attachment.
3231  *
3232  * @since 2.0.0
3233  *
3234  * @param string $url URL to check
3235  * @return bool True on success, false on failure.
3236  */
3237 function is_local_attachment($url) {
3238         if (strpos($url, home_url()) === false)
3239                 return false;
3240         if (strpos($url, home_url('/?attachment_id=')) !== false)
3241                 return true;
3242         if ( $id = url_to_postid($url) ) {
3243                 $post = & get_post($id);
3244                 if ( 'attachment' == $post->post_type )
3245                         return true;
3246         }
3247         return false;
3248 }
3249
3250 /**
3251  * Insert an attachment.
3252  *
3253  * If you set the 'ID' in the $object parameter, it will mean that you are
3254  * updating and attempt to update the attachment. You can also set the
3255  * attachment name or title by setting the key 'post_name' or 'post_title'.
3256  *
3257  * You can set the dates for the attachment manually by setting the 'post_date'
3258  * and 'post_date_gmt' keys' values.
3259  *
3260  * By default, the comments will use the default settings for whether the
3261  * comments are allowed. You can close them manually or keep them open by
3262  * setting the value for the 'comment_status' key.
3263  *
3264  * The $object parameter can have the following:
3265  *     'post_status'   - Default is 'draft'. Can not be overridden, set the same as parent post.
3266  *     'post_type'     - Default is 'post', will be set to attachment. Can not override.
3267  *     'post_author'   - Default is current user ID. The ID of the user, who added the attachment.
3268  *     'ping_status'   - Default is the value in default ping status option. Whether the attachment
3269  *                       can accept pings.
3270  *     'post_parent'   - Default is 0. Can use $parent parameter or set this for the post it belongs
3271  *                       to, if any.
3272  *     'menu_order'    - Default is 0. The order it is displayed.
3273  *     'to_ping'       - Whether to ping.
3274  *     'pinged'        - Default is empty string.
3275  *     'post_password' - Default is empty string. The password to access the attachment.
3276  *     'guid'          - Global Unique ID for referencing the attachment.
3277  *     'post_content_filtered' - Attachment post content filtered.
3278  *     'post_excerpt'  - Attachment excerpt.
3279  *
3280  * @since 2.0.0
3281  * @uses $wpdb
3282  * @uses $user_ID
3283  * @uses do_action() Calls 'edit_attachment' on $post_ID if this is an update.
3284  * @uses do_action() Calls 'add_attachment' on $post_ID if this is not an update.
3285  *
3286  * @param string|array $object Arguments to override defaults.
3287  * @param string $file Optional filename.
3288  * @param int $post_parent Parent post ID.
3289  * @return int Attachment ID.
3290  */
3291 function wp_insert_attachment($object, $file = false, $parent = 0) {
3292         global $wpdb, $user_ID;
3293
3294         $defaults = array('post_status' => 'draft', 'post_type' => 'post', 'post_author' => $user_ID,
3295                 'ping_status' => get_option('default_ping_status'), 'post_parent' => 0,
3296                 'menu_order' => 0, 'to_ping' =>  '', 'pinged' => '', 'post_password' => '',
3297                 'guid' => '', 'post_content_filtered' => '', 'post_excerpt' => '', 'import_id' => 0);
3298
3299         $object = wp_parse_args($object, $defaults);
3300         if ( !empty($parent) )
3301                 $object['post_parent'] = $parent;
3302
3303         $object = sanitize_post($object, 'db');
3304
3305         // export array as variables
3306         extract($object, EXTR_SKIP);
3307
3308         if ( empty($post_author) )
3309                 $post_author = $user_ID;
3310
3311         $post_type = 'attachment';
3312         $post_status = 'inherit';
3313
3314         // Make sure we set a valid category.
3315         if ( !isset($post_category) || 0 == count($post_category) || !is_array($post_category) ) {
3316                 // 'post' requires at least one category.
3317                 if ( 'post' == $post_type )
3318                         $post_category = array( get_option('default_category') );
3319                 else
3320                         $post_category = array();
3321         }
3322
3323         // Are we updating or creating?
3324         if ( !empty($ID) ) {
3325                 $update = true;
3326                 $post_ID = (int) $ID;
3327         } else {
3328                 $update = false;
3329                 $post_ID = 0;
3330         }
3331
3332         // Create a valid post name.
3333         if ( empty($post_name) )
3334                 $post_name = sanitize_title($post_title);
3335         else
3336                 $post_name = sanitize_title($post_name);
3337
3338         // expected_slashed ($post_name)
3339         $post_name = wp_unique_post_slug($post_name, $post_ID, $post_status, $post_type, $post_parent);
3340
3341         if ( empty($post_date) )
3342                 $post_date = current_time('mysql');
3343         if ( empty($post_date_gmt) )
3344                 $post_date_gmt = current_time('mysql', 1);
3345
3346         if ( empty($post_modified) )
3347                 $post_modified = $post_date;
3348         if ( empty($post_modified_gmt) )
3349                 $post_modified_gmt = $post_date_gmt;
3350
3351         if ( empty($comment_status) ) {
3352                 if ( $update )
3353                         $comment_status = 'closed';
3354                 else
3355                         $comment_status = get_option('default_comment_status');
3356         }
3357         if ( empty($ping_status) )
3358                 $ping_status = get_option('default_ping_status');
3359
3360         if ( isset($to_ping) )
3361                 $to_ping = preg_replace('|\s+|', "\n", $to_ping);
3362         else
3363                 $to_ping = '';
3364
3365         if ( isset($post_parent) )
3366                 $post_parent = (int) $post_parent;
3367         else
3368                 $post_parent = 0;
3369
3370         if ( isset($menu_order) )
3371                 $menu_order = (int) $menu_order;
3372         else
3373                 $menu_order = 0;
3374
3375         if ( !isset($post_password) )
3376                 $post_password = '';
3377
3378         if ( ! isset($pinged) )
3379                 $pinged = '';
3380
3381         // expected_slashed (everything!)
3382         $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' ) );
3383         $data = stripslashes_deep( $data );
3384
3385         if ( $update ) {
3386                 $wpdb->update( $wpdb->posts, $data, array( 'ID' => $post_ID ) );
3387         } else {
3388                 // If there is a suggested ID, use it if not already present
3389                 if ( !empty($import_id) ) {
3390                         $import_id = (int) $import_id;
3391                         if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
3392                                 $data['ID'] = $import_id;
3393                         }
3394                 }
3395
3396                 $wpdb->insert( $wpdb->posts, $data );
3397                 $post_ID = (int) $wpdb->insert_id;
3398         }
3399
3400         if ( empty($post_name) ) {
3401                 $post_name = sanitize_title($post_title, $post_ID);
3402                 $wpdb->update( $wpdb->posts, compact("post_name"), array( 'ID' => $post_ID ) );
3403         }
3404
3405         wp_set_post_categories($post_ID, $post_category);
3406
3407         if ( $file )
3408                 update_attached_file( $post_ID, $file );
3409
3410         clean_post_cache($post_ID);
3411
3412         if ( isset($post_parent) && $post_parent < 0 )
3413                 add_post_meta($post_ID, '_wp_attachment_temp_parent', $post_parent, true);
3414
3415         if ( $update) {
3416                 do_action('edit_attachment', $post_ID);
3417         } else {
3418                 do_action('add_attachment', $post_ID);
3419         }
3420
3421         return $post_ID;
3422 }
3423
3424 /**
3425  * Trashes or deletes an attachment.
3426  *
3427  * When an attachment is permanently deleted, the file will also be removed.
3428  * Deletion removes all post meta fields, taxonomy, comments, etc. associated
3429  * with the attachment (except the main post).
3430  *
3431  * The attachment is moved to the trash instead of permanently deleted unless trash
3432  * for media is disabled, item is already in the trash, or $force_delete is true.
3433  *
3434  * @since 2.0.0
3435  * @uses $wpdb
3436  * @uses do_action() Calls 'delete_attachment' hook on Attachment ID.
3437  *
3438  * @param int $postid Attachment ID.
3439  * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
3440  * @return mixed False on failure. Post data on success.
3441  */
3442 function wp_delete_attachment( $post_id, $force_delete = false ) {
3443         global $wpdb;
3444
3445         if ( !$post = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id) ) )
3446                 return $post;
3447
3448         if ( 'attachment' != $post->post_type )
3449                 return false;
3450
3451         if ( !$force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' != $post->post_status )
3452                 return wp_trash_post( $post_id );
3453
3454         delete_post_meta($post_id, '_wp_trash_meta_status');
3455         delete_post_meta($post_id, '_wp_trash_meta_time');
3456
3457         $meta = wp_get_attachment_metadata( $post_id );
3458         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
3459         $file = get_attached_file( $post_id );
3460
3461         if ( is_multisite() )
3462                 delete_transient( 'dirsize_cache' );
3463
3464         do_action('delete_attachment', $post_id);
3465
3466         wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
3467         wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
3468
3469         $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id' AND meta_value = %d", $post_id ));
3470
3471         $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ));
3472         if ( ! empty( $comment_ids ) ) {
3473                 do_action( 'delete_comment', $comment_ids );
3474                 foreach ( $comment_ids as $comment_id )
3475                         wp_delete_comment( $comment_id, true );
3476                 do_action( 'deleted_comment', $comment_ids );
3477         }
3478
3479         $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ));
3480         if ( !empty($post_meta_ids) ) {
3481                 do_action( 'delete_postmeta', $post_meta_ids );
3482                 $in_post_meta_ids = "'" . implode("', '", $post_meta_ids) . "'";
3483                 $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_id IN($in_post_meta_ids)" );
3484                 do_action( 'deleted_postmeta', $post_meta_ids );
3485         }
3486
3487         do_action( 'delete_post', $post_id );
3488         $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->posts WHERE ID = %d", $post_id ));
3489         do_action( 'deleted_post', $post_id );
3490
3491         $uploadpath = wp_upload_dir();
3492
3493         if ( ! empty($meta['thumb']) ) {
3494                 // Don't delete the thumb if another attachment uses it
3495                 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)) ) {
3496                         $thumbfile = str_replace(basename($file), $meta['thumb'], $file);
3497                         $thumbfile = apply_filters('wp_delete_file', $thumbfile);
3498                         @ unlink( path_join($uploadpath['basedir'], $thumbfile) );
3499                 }
3500         }
3501
3502         // remove intermediate and backup images if there are any
3503         foreach ( get_intermediate_image_sizes() as $size ) {
3504                 if ( $intermediate = image_get_intermediate_size($post_id, $size) ) {
3505                         $intermediate_file = apply_filters('wp_delete_file', $intermediate['path']);
3506                         @ unlink( path_join($uploadpath['basedir'], $intermediate_file) );
3507                 }
3508         }
3509
3510         if ( is_array($backup_sizes) ) {
3511                 foreach ( $backup_sizes as $size ) {
3512                         $del_file = path_join( dirname($meta['file']), $size['file'] );
3513                         $del_file = apply_filters('wp_delete_file', $del_file);
3514             @ unlink( path_join($uploadpath['basedir'], $del_file) );
3515                 }
3516         }
3517
3518         $file = apply_filters('wp_delete_file', $file);
3519
3520         if ( ! empty($file) )
3521                 @ unlink($file);
3522
3523         clean_post_cache($post_id);
3524
3525         return $post;
3526 }
3527
3528 /**
3529  * Retrieve attachment meta field for attachment ID.
3530  *
3531  * @since 2.1.0
3532  *
3533  * @param int $post_id Attachment ID
3534  * @param bool $unfiltered Optional, default is false. If true, filters are not run.
3535  * @return string|bool Attachment meta field. False on failure.
3536  */
3537 function wp_get_attachment_metadata( $post_id = 0, $unfiltered = false ) {
3538         $post_id = (int) $post_id;
3539         if ( !$post =& get_post( $post_id ) )
3540                 return false;
3541
3542         $data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
3543
3544         if ( $unfiltered )
3545                 return $data;
3546
3547         return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
3548 }
3549
3550 /**
3551  * Update metadata for an attachment.
3552  *
3553  * @since 2.1.0
3554  *
3555  * @param int $post_id Attachment ID.
3556  * @param array $data Attachment data.
3557  * @return int
3558  */
3559 function wp_update_attachment_metadata( $post_id, $data ) {
3560         $post_id = (int) $post_id;
3561         if ( !$post =& get_post( $post_id ) )
3562                 return false;
3563
3564         $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID );
3565
3566         return update_post_meta( $post->ID, '_wp_attachment_metadata', $data);
3567 }
3568
3569 /**
3570  * Retrieve the URL for an attachment.
3571  *
3572  * @since 2.1.0
3573  *
3574  * @param int $post_id Attachment ID.
3575  * @return string
3576  */
3577 function wp_get_attachment_url( $post_id = 0 ) {
3578         $post_id = (int) $post_id;
3579         if ( !$post =& get_post( $post_id ) )
3580                 return false;
3581
3582         $url = '';
3583         if ( $file = get_post_meta( $post->ID, '_wp_attached_file', true) ) { //Get attached file
3584                 if ( ($uploads = wp_upload_dir()) && false === $uploads['error'] ) { //Get upload directory
3585                         if ( 0 === strpos($file, $uploads['basedir']) ) //Check that the upload base exists in the file location
3586                                 $url = str_replace($uploads['basedir'], $uploads['baseurl'], $file); //replace file location with url location
3587             elseif ( false !== strpos($file, 'wp-content/uploads') )
3588                 $url = $uploads['baseurl'] . substr( $file, strpos($file, 'wp-content/uploads') + 18 );
3589             else
3590                 $url = $uploads['baseurl'] . "/$file"; //Its a newly uploaded file, therefor $file is relative to the basedir.
3591                 }
3592         }
3593
3594         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.
3595                 $url = get_the_guid( $post->ID );
3596
3597         if ( 'attachment' != $post->post_type || empty($url) )
3598                 return false;
3599
3600         return apply_filters( 'wp_get_attachment_url', $url, $post->ID );
3601 }
3602
3603 /**
3604  * Retrieve thumbnail for an attachment.
3605  *
3606  * @since 2.1.0
3607  *
3608  * @param int $post_id Attachment ID.
3609  * @return mixed False on failure. Thumbnail file path on success.
3610  */
3611 function wp_get_attachment_thumb_file( $post_id = 0 ) {
3612         $post_id = (int) $post_id;
3613         if ( !$post =& get_post( $post_id ) )
3614                 return false;
3615         if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )
3616                 return false;
3617
3618         $file = get_attached_file( $post->ID );
3619
3620         if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) )
3621                 return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
3622         return false;
3623 }
3624
3625 /**
3626  * Retrieve URL for an attachment thumbnail.
3627  *
3628  * @since 2.1.0
3629  *
3630  * @param int $post_id Attachment ID
3631  * @return string|bool False on failure. Thumbnail URL on success.
3632  */
3633 function wp_get_attachment_thumb_url( $post_id = 0 ) {
3634         $post_id = (int) $post_id;
3635         if ( !$post =& get_post( $post_id ) )
3636                 return false;
3637         if ( !$url = wp_get_attachment_url( $post->ID ) )
3638                 return false;
3639
3640         $sized = image_downsize( $post_id, 'thumbnail' );
3641         if ( $sized )
3642                 return $sized[0];
3643
3644         if ( !$thumb = wp_get_attachment_thumb_file( $post->ID ) )
3645                 return false;
3646
3647         $url = str_replace(basename($url), basename($thumb), $url);
3648
3649         return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
3650 }
3651
3652 /**
3653  * Check if the attachment is an image.
3654  *
3655  * @since 2.1.0
3656  *
3657  * @param int $post_id Attachment ID
3658  * @return bool
3659  */
3660 function wp_attachment_is_image( $post_id = 0 ) {
3661         $post_id = (int) $post_id;
3662         if ( !$post =& get_post( $post_id ) )
3663                 return false;
3664
3665         if ( !$file = get_attached_file( $post->ID ) )
3666                 return false;
3667
3668         $ext = preg_match('/\.([^.]+)$/', $file, $matches) ? strtolower($matches[1]) : false;
3669
3670         $image_exts = array('jpg', 'jpeg', 'gif', 'png');
3671
3672         if ( 'image/' == substr($post->post_mime_type, 0, 6) || $ext && 'import' == $post->post_mime_type && in_array($ext, $image_exts) )
3673                 return true;
3674         return false;
3675 }
3676
3677 /**
3678  * Retrieve the icon for a MIME type.
3679  *
3680  * @since 2.1.0
3681  *
3682  * @param string $mime MIME type
3683  * @return string|bool
3684  */
3685 function wp_mime_type_icon( $mime = 0 ) {
3686         if ( !is_numeric($mime) )
3687                 $icon = wp_cache_get("mime_type_icon_$mime");
3688         if ( empty($icon) ) {
3689                 $post_id = 0;
3690                 $post_mimes = array();
3691                 if ( is_numeric($mime) ) {
3692                         $mime = (int) $mime;
3693                         if ( $post =& get_post( $mime ) ) {
3694                                 $post_id = (int) $post->ID;
3695                                 $ext = preg_replace('/^.+?\.([^.]+)$/', '$1', $post->guid);
3696                                 if ( !empty($ext) ) {
3697                                         $post_mimes[] = $ext;
3698                                         if ( $ext_type = wp_ext2type( $ext ) )
3699                                                 $post_mimes[] = $ext_type;
3700                                 }
3701                                 $mime = $post->post_mime_type;
3702                         } else {
3703                                 $mime = 0;
3704                         }
3705                 } else {
3706                         $post_mimes[] = $mime;
3707                 }
3708
3709                 $icon_files = wp_cache_get('icon_files');
3710
3711                 if ( !is_array($icon_files) ) {
3712                         $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/crystal' );
3713                         $icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url('images/crystal') );
3714                         $dirs = apply_filters( 'icon_dirs', array($icon_dir => $icon_dir_uri) );
3715                         $icon_files = array();
3716                         while ( $dirs ) {
3717                                 $dir = array_shift($keys = array_keys($dirs));
3718                                 $uri = array_shift($dirs);
3719                                 if ( $dh = opendir($dir) ) {
3720                                         while ( false !== $file = readdir($dh) ) {
3721                                                 $file = basename($file);
3722                                                 if ( substr($file, 0, 1) == '.' )
3723                                                         continue;
3724                                                 if ( !in_array(strtolower(substr($file, -4)), array('.png', '.gif', '.jpg') ) ) {
3725                                                         if ( is_dir("$dir/$file") )
3726                                                                 $dirs["$dir/$file"] = "$uri/$file";
3727                                                         continue;
3728                                                 }
3729                                                 $icon_files["$dir/$file"] = "$uri/$file";
3730                                         }
3731                                         closedir($dh);
3732                                 }
3733                         }
3734                         wp_cache_set('icon_files', $icon_files, 600);
3735                 }
3736
3737                 // Icon basename - extension = MIME wildcard
3738                 foreach ( $icon_files as $file => $uri )
3739                         $types[ preg_replace('/^([^.]*).*$/', '$1', basename($file)) ] =& $icon_files[$file];
3740
3741                 if ( ! empty($mime) ) {
3742                         $post_mimes[] = substr($mime, 0, strpos($mime, '/'));
3743                         $post_mimes[] = substr($mime, strpos($mime, '/') + 1);
3744                         $post_mimes[] = str_replace('/', '_', $mime);
3745                 }
3746
3747                 $matches = wp_match_mime_types(array_keys($types), $post_mimes);
3748                 $matches['default'] = array('default');
3749
3750                 foreach ( $matches as $match => $wilds ) {
3751                         if ( isset($types[$wilds[0]])) {
3752                                 $icon = $types[$wilds[0]];
3753                                 if ( !is_numeric($mime) )
3754                                         wp_cache_set("mime_type_icon_$mime", $icon);
3755                                 break;
3756                         }
3757                 }
3758         }
3759
3760         return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id ); // Last arg is 0 if function pass mime type.
3761 }
3762
3763 /**
3764  * Checked for changed slugs for published posts and save old slug.
3765  *
3766  * The function is used along with form POST data. It checks for the wp-old-slug
3767  * POST field. Will only be concerned with published posts and the slug actually
3768  * changing.
3769  *
3770  * If the slug was changed and not already part of the old slugs then it will be
3771  * added to the post meta field ('_wp_old_slug') for storing old slugs for that
3772  * post.
3773  *
3774  * The most logically usage of this function is redirecting changed posts, so
3775  * that those that linked to an changed post will be redirected to the new post.
3776  *
3777  * @since 2.1.0
3778  *
3779  * @param int $post_id Post ID.
3780  * @return int Same as $post_id
3781  */
3782 function wp_check_for_changed_slugs($post_id, $post, $post_before) {
3783         // dont bother if it hasnt changed
3784         if ( $post->post_name == $post_before->post_name )
3785                 return;
3786
3787         // we're only concerned with published posts
3788         if ( $post->post_status != 'publish' || $post->post_type != 'post' )
3789                 return;
3790
3791         $old_slugs = (array) get_post_meta($post_id, '_wp_old_slug');
3792
3793         // if we haven't added this old slug before, add it now
3794         if ( !in_array($post_before->post_name, $old_slugs) )
3795                 add_post_meta($post_id, '_wp_old_slug', $post_before->post_name);
3796
3797         // if the new slug was used previously, delete it from the list
3798         if ( in_array($post->post_name, $old_slugs) )
3799                 delete_post_meta($post_id, '_wp_old_slug', $post->post_name);
3800 }
3801
3802 /**
3803  * Retrieve the private post SQL based on capability.
3804  *
3805  * This function provides a standardized way to appropriately select on the
3806  * post_status of posts/pages. The function will return a piece of SQL code that
3807  * can be added to a WHERE clause; this SQL is constructed to allow all
3808  * published posts, and all private posts to which the user has access.
3809  *
3810  * It also allows plugins that define their own post type to control the cap by
3811  * using the hook 'pub_priv_sql_capability'. The plugin is expected to return
3812  * the capability the user must have to read the private post type.
3813  *
3814  * @since 2.2.0
3815  *
3816  * @uses $user_ID
3817  * @uses apply_filters() Call 'pub_priv_sql_capability' filter for plugins with different post types.
3818  *
3819  * @param string $post_type currently only supports 'post' or 'page'.
3820  * @return string SQL code that can be added to a where clause.
3821  */
3822 function get_private_posts_cap_sql($post_type) {
3823         return get_posts_by_author_sql($post_type, FALSE);
3824 }
3825
3826 /**
3827  * Retrieve the post SQL based on capability, author, and type.
3828  *
3829  * See above for full description.
3830  *
3831  * @since 3.0.0
3832  * @param string $post_type currently only supports 'post' or 'page'.
3833  * @param bool $full Optional.  Returns a full WHERE statement instead of just an 'andalso' term.
3834  * @param int $post_author Optional.  Query posts having a single author ID.
3835  * @return string SQL WHERE code that can be added to a query.
3836  */
3837 function get_posts_by_author_sql($post_type, $full = TRUE, $post_author = NULL) {
3838         global $user_ID, $wpdb;
3839
3840         // Private posts
3841         if ($post_type == 'post') {
3842                 $cap = 'read_private_posts';
3843         // Private pages
3844         } elseif ($post_type == 'page') {
3845                 $cap = 'read_private_pages';
3846         // Dunno what it is, maybe plugins have their own post type?
3847         } else {
3848                 $cap = '';
3849                 $cap = apply_filters('pub_priv_sql_capability', $cap);
3850
3851                 if (empty($cap)) {
3852                         // We don't know what it is, filters don't change anything,
3853                         // so set the SQL up to return nothing.
3854                         return ' 1 = 0 ';
3855                 }
3856         }
3857
3858         if ($full) {
3859                 if (is_null($post_author)) {
3860                         $sql = $wpdb->prepare('WHERE post_type = %s AND ', $post_type);
3861                 } else {
3862                         $sql = $wpdb->prepare('WHERE post_author = %d AND post_type = %s AND ', $post_author, $post_type);
3863                 }
3864         } else {
3865                 $sql = '';
3866         }
3867
3868         $sql .= "(post_status = 'publish'";
3869
3870         if (current_user_can($cap)) {
3871                 // Does the user have the capability to view private posts? Guess so.
3872                 $sql .= " OR post_status = 'private'";
3873         } elseif (is_user_logged_in()) {
3874                 // Users can view their own private posts.
3875                 $id = (int) $user_ID;
3876                 if (is_null($post_author) || !$full) {
3877                         $sql .= " OR post_status = 'private' AND post_author = $id";
3878                 } elseif ($id == (int)$post_author) {
3879                         $sql .= " OR post_status = 'private'";
3880                 } // else none
3881         } // else none
3882
3883         $sql .= ')';
3884
3885         return $sql;
3886 }
3887
3888 /**
3889  * Retrieve the date that the last post was published.
3890  *
3891  * The server timezone is the default and is the difference between GMT and
3892  * server time. The 'blog' value is the date when the last post was posted. The
3893  * 'gmt' is when the last post was posted in GMT formatted date.
3894  *
3895  * @since 0.71
3896  *
3897  * @uses $wpdb
3898  * @uses $blog_id
3899  * @uses apply_filters() Calls 'get_lastpostdate' filter
3900  *
3901  * @global mixed $cache_lastpostdate Stores the last post date
3902  * @global mixed $pagenow The current page being viewed
3903  *
3904  * @param string $timezone The location to get the time. Can be 'gmt', 'blog', or 'server'.
3905  * @return string The date of the last post.
3906  */
3907 function get_lastpostdate($timezone = 'server') {
3908         global $cache_lastpostdate, $wpdb, $blog_id;
3909         $add_seconds_server = date('Z');
3910         if ( !isset($cache_lastpostdate[$blog_id][$timezone]) ) {
3911                 switch(strtolower($timezone)) {
3912                         case 'gmt':
3913                                 $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");
3914                                 break;
3915                         case 'blog':
3916                                 $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");
3917                                 break;
3918                         case 'server':
3919                                 $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");
3920                                 break;
3921                 }
3922                 $cache_lastpostdate[$blog_id][$timezone] = $lastpostdate;
3923         } else {
3924                 $lastpostdate = $cache_lastpostdate[$blog_id][$timezone];
3925         }
3926         return apply_filters( 'get_lastpostdate', $lastpostdate, $timezone );
3927 }
3928
3929 /**
3930  * Retrieve last post modified date depending on timezone.
3931  *
3932  * The server timezone is the default and is the difference between GMT and
3933  * server time. The 'blog' value is just when the last post was modified. The
3934  * 'gmt' is when the last post was modified in GMT time.
3935  *
3936  * @since 1.2.0
3937  * @uses $wpdb
3938  * @uses $blog_id
3939  * @uses apply_filters() Calls 'get_lastpostmodified' filter
3940  *
3941  * @param string $timezone The location to get the time. Can be 'gmt', 'blog', or 'server'.
3942  * @return string The date the post was last modified.
3943  */
3944 function get_lastpostmodified($timezone = 'server') {
3945         global $wpdb;
3946
3947         $add_seconds_server = date('Z');
3948         $timezone = strtolower( $timezone );
3949
3950         $lastpostmodified = wp_cache_get( "lastpostmodified:$timezone", 'timeinfo' );
3951         if ( $lastpostmodified )
3952                 return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
3953
3954         switch ( strtolower($timezone) ) {
3955                 case 'gmt':
3956                         $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");
3957                         break;
3958                 case 'blog':
3959                         $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");
3960                         break;
3961                 case 'server':
3962                         $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");
3963                         break;
3964         }
3965
3966         $lastpostdate = get_lastpostdate($timezone);
3967         if ( $lastpostdate > $lastpostmodified )
3968                 $lastpostmodified = $lastpostdate;
3969
3970         if ( $lastpostmodified )
3971                 wp_cache_set( "lastpostmodified:$timezone", $lastpostmodified, 'timeinfo' );
3972
3973         return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
3974 }
3975
3976 /**
3977  * Updates posts in cache.
3978  *
3979  * @usedby update_page_cache() Aliased by this function.
3980  *
3981  * @package WordPress
3982  * @subpackage Cache
3983  * @since 1.5.1
3984  *
3985  * @param array $posts Array of post objects
3986  */
3987 function update_post_cache(&$posts) {
3988         if ( !$posts )
3989                 return;
3990
3991         foreach ( $posts as $post )
3992                 wp_cache_add($post->ID, $post, 'posts');
3993 }
3994
3995 /**
3996  * Will clean the post in the cache.
3997  *
3998  * Cleaning means delete from the cache of the post. Will call to clean the term
3999  * object cache associated with the post ID.
4000  *
4001  * clean_post_cache() will call itself recursively for each child post.
4002  *
4003  * This function not run if $_wp_suspend_cache_invalidation is not empty. See
4004  * wp_suspend_cache_invalidation().
4005  *
4006  * @package WordPress
4007  * @subpackage Cache
4008  * @since 2.0.0
4009  *
4010  * @uses do_action() Calls 'clean_post_cache' on $id before adding children (if any).
4011  *
4012  * @param int $id The Post ID in the cache to clean
4013  */
4014 function clean_post_cache($id) {
4015         global $_wp_suspend_cache_invalidation, $wpdb;
4016
4017         if ( !empty($_wp_suspend_cache_invalidation) )
4018                 return;
4019
4020         $id = (int) $id;
4021
4022         wp_cache_delete($id, 'posts');
4023         wp_cache_delete($id, 'post_meta');
4024
4025         clean_object_term_cache($id, 'post');
4026
4027         wp_cache_delete( 'wp_get_archives', 'general' );
4028
4029         do_action('clean_post_cache', $id);
4030
4031         if ( $children = $wpdb->get_col( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_parent = %d", $id) ) ) {
4032                 foreach( $children as $cid )
4033                         clean_post_cache( $cid );
4034         }
4035
4036         if ( is_multisite() )
4037                 wp_cache_delete( $wpdb->blogid . '-' . $id, 'global-posts' );
4038 }
4039
4040 /**
4041  * Alias of update_post_cache().
4042  *
4043  * @see update_post_cache() Posts and pages are the same, alias is intentional
4044  *
4045  * @package WordPress
4046  * @subpackage Cache
4047  * @since 1.5.1
4048  *
4049  * @param array $pages list of page objects
4050  */
4051 function update_page_cache(&$pages) {
4052         update_post_cache($pages);
4053 }
4054
4055 /**
4056  * Will clean the page in the cache.
4057  *
4058  * Clean (read: delete) page from cache that matches $id. Will also clean cache
4059  * associated with 'all_page_ids' and 'get_pages'.
4060  *
4061  * @package WordPress
4062  * @subpackage Cache
4063  * @since 2.0.0
4064  *
4065  * @uses do_action() Will call the 'clean_page_cache' hook action.
4066  *
4067  * @param int $id Page ID to clean
4068  */
4069 function clean_page_cache($id) {
4070         clean_post_cache($id);
4071
4072         wp_cache_delete( 'all_page_ids', 'posts' );
4073         wp_cache_delete( 'get_pages', 'posts' );
4074
4075         do_action('clean_page_cache', $id);
4076 }
4077
4078 /**
4079  * Call major cache updating functions for list of Post objects.
4080  *
4081  * @package WordPress
4082  * @subpackage Cache
4083  * @since 1.5.0
4084  *
4085  * @uses $wpdb
4086  * @uses update_post_cache()
4087  * @uses update_object_term_cache()
4088  * @uses update_postmeta_cache()
4089  *
4090  * @param array $posts Array of Post objects
4091  * @param string $post_type The post type of the posts in $posts. Default is 'post'.
4092  * @param bool $update_term_cache Whether to update the term cache. Default is true.
4093  * @param bool $update_meta_cache Whether to update the meta cache. Default is true.
4094  */
4095 function update_post_caches(&$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true) {
4096         // No point in doing all this work if we didn't match any posts.
4097         if ( !$posts )
4098                 return;
4099
4100         update_post_cache($posts);
4101
4102         $post_ids = array();
4103         foreach ( $posts as $post )
4104                 $post_ids[] = $post->ID;
4105
4106         if ( empty($post_type) )
4107                 $post_type = 'post';
4108
4109         if ( !is_array($post_type) && 'any' != $post_type && $update_term_cache )
4110                 update_object_term_cache($post_ids, $post_type);
4111
4112         if ( $update_meta_cache )
4113                 update_postmeta_cache($post_ids);
4114 }
4115
4116 /**
4117  * Updates metadata cache for list of post IDs.
4118  *
4119  * Performs SQL query to retrieve the metadata for the post IDs and updates the
4120  * metadata cache for the posts. Therefore, the functions, which call this
4121  * function, do not need to perform SQL queries on their own.
4122  *
4123  * @package WordPress
4124  * @subpackage Cache
4125  * @since 2.1.0
4126  *
4127  * @uses $wpdb
4128  *
4129  * @param array $post_ids List of post IDs.
4130  * @return bool|array Returns false if there is nothing to update or an array of metadata.
4131  */
4132 function update_postmeta_cache($post_ids) {
4133         return update_meta_cache('post', $post_ids);
4134 }
4135
4136 /**
4137  * Will clean the attachment in the cache.
4138  *
4139  * Cleaning means delete from the cache. Optionaly will clean the term
4140  * object cache associated with the attachment ID.
4141  *
4142  * This function will not run if $_wp_suspend_cache_invalidation is not empty. See
4143  * wp_suspend_cache_invalidation().
4144  *
4145  * @package WordPress
4146  * @subpackage Cache
4147  * @since 3.0.0
4148  *
4149  * @uses do_action() Calls 'clean_attachment_cache' on $id.
4150  *
4151  * @param int $id The attachment ID in the cache to clean
4152  * @param bool $clean_terms optional. Whether to clean terms cache
4153  */
4154 function clean_attachment_cache($id, $clean_terms = false) {
4155         global $_wp_suspend_cache_invalidation;
4156
4157         if ( !empty($_wp_suspend_cache_invalidation) )
4158                 return;
4159
4160         $id = (int) $id;
4161
4162         wp_cache_delete($id, 'posts');
4163         wp_cache_delete($id, 'post_meta');
4164
4165         if ( $clean_terms )
4166                 clean_object_term_cache($id, 'attachment');
4167
4168         do_action('clean_attachment_cache', $id);
4169 }
4170
4171 //
4172 // Hooks
4173 //
4174
4175 /**
4176  * Hook for managing future post transitions to published.
4177  *
4178  * @since 2.3.0
4179  * @access private
4180  * @uses $wpdb
4181  * @uses do_action() Calls 'private_to_published' on post ID if this is a 'private_to_published' call.
4182  * @uses wp_clear_scheduled_hook() with 'publish_future_post' and post ID.
4183  *
4184  * @param string $new_status New post status
4185  * @param string $old_status Previous post status
4186  * @param object $post Object type containing the post information
4187  */
4188 function _transition_post_status($new_status, $old_status, $post) {
4189         global $wpdb;
4190
4191         if ( $old_status != 'publish' && $new_status == 'publish' ) {
4192                 // Reset GUID if transitioning to publish and it is empty
4193                 if ( '' == get_the_guid($post->ID) )
4194                         $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
4195                 do_action('private_to_published', $post->ID);  // Deprecated, use private_to_publish
4196         }
4197
4198         // If published posts changed clear the lastpostmodified cache
4199         if ( 'publish' == $new_status || 'publish' == $old_status) {
4200                 wp_cache_delete( 'lastpostmodified:server', 'timeinfo' );
4201                 wp_cache_delete( 'lastpostmodified:gmt',    'timeinfo' );
4202                 wp_cache_delete( 'lastpostmodified:blog',   'timeinfo' );
4203         }
4204
4205         // Always clears the hook in case the post status bounced from future to draft.
4206         wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) );
4207 }
4208
4209 /**
4210  * Hook used to schedule publication for a post marked for the future.
4211  *
4212  * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
4213  *
4214  * @since 2.3.0
4215  * @access private
4216  *
4217  * @param int $deprecated Not used. Can be set to null. Never implemented.
4218  *   Not marked as deprecated with _deprecated_argument() as it conflicts with
4219  *   wp_transition_post_status() and the default filter for _future_post_hook().
4220  * @param object $post Object type containing the post information
4221  */
4222 function _future_post_hook( $deprecated = '', $post ) {
4223         wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
4224         wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT') , 'publish_future_post', array( $post->ID ) );
4225 }
4226
4227 /**
4228  * Hook to schedule pings and enclosures when a post is published.
4229  *
4230  * @since 2.3.0
4231  * @access private
4232  * @uses $wpdb
4233  * @uses XMLRPC_REQUEST and APP_REQUEST constants.
4234  * @uses do_action() Calls 'xmlprc_publish_post' on post ID if XMLRPC_REQUEST is defined.
4235  * @uses do_action() Calls 'app_publish_post' on post ID if APP_REQUEST is defined.
4236  *
4237  * @param int $post_id The ID in the database table of the post being published
4238  */
4239 function _publish_post_hook($post_id) {
4240         global $wpdb;
4241
4242         if ( defined('XMLRPC_REQUEST') )
4243                 do_action('xmlrpc_publish_post', $post_id);
4244         if ( defined('APP_REQUEST') )
4245                 do_action('app_publish_post', $post_id);
4246
4247         if ( defined('WP_IMPORTING') )
4248                 return;
4249
4250         $data = array( 'post_id' => $post_id, 'meta_value' => '1' );
4251         if ( get_option('default_pingback_flag') ) {
4252                 $wpdb->insert( $wpdb->postmeta, $data + array( 'meta_key' => '_pingme' ) );
4253                 do_action( 'added_postmeta', $wpdb->insert_id, $post_id, '_pingme', 1 );
4254         }
4255         $wpdb->insert( $wpdb->postmeta, $data + array( 'meta_key' => '_encloseme' ) );
4256         do_action( 'added_postmeta', $wpdb->insert_id, $post_id, '_encloseme', 1 );
4257
4258         wp_schedule_single_event(time(), 'do_pings');
4259 }
4260
4261 /**
4262  * Hook used to prevent page/post cache and rewrite rules from staying dirty.
4263  *
4264  * Does two things. If the post is a page and has a template then it will
4265  * update/add that template to the meta. For both pages and posts, it will clean
4266  * the post cache to make sure that the cache updates to the changes done
4267  * recently. For pages, the rewrite rules of WordPress are flushed to allow for
4268  * any changes.
4269  *
4270  * The $post parameter, only uses 'post_type' property and 'page_template'
4271  * property.
4272  *
4273  * @since 2.3.0
4274  * @access private
4275  * @uses $wp_rewrite Flushes Rewrite Rules.
4276  *
4277  * @param int $post_id The ID in the database table for the $post
4278  * @param object $post Object type containing the post information
4279  */
4280 function _save_post_hook($post_id, $post) {
4281         if ( $post->post_type == 'page' ) {
4282                 clean_page_cache($post_id);
4283                 // Avoid flushing rules for every post during import.
4284                 if ( !defined('WP_IMPORTING') ) {
4285                         global $wp_rewrite;
4286                         $wp_rewrite->flush_rules(false);
4287                 }
4288         } else {
4289                 clean_post_cache($post_id);
4290         }
4291 }
4292
4293 /**
4294  * Retrieve post ancestors and append to post ancestors property.
4295  *
4296  * Will only retrieve ancestors once, if property is already set, then nothing
4297  * will be done. If there is not a parent post, or post ID and post parent ID
4298  * are the same then nothing will be done.
4299  *
4300  * The parameter is passed by reference, so nothing needs to be returned. The
4301  * property will be updated and can be referenced after the function is
4302  * complete. The post parent will be an ancestor and the parent of the post
4303  * parent will be an ancestor. There will only be two ancestors at the most.
4304  *
4305  * @since unknown
4306  * @access private
4307  * @uses $wpdb
4308  *
4309  * @param object $_post Post data.
4310  * @return null When nothing needs to be done.
4311  */
4312 function _get_post_ancestors(&$_post) {
4313         global $wpdb;
4314
4315         if ( isset($_post->ancestors) )
4316                 return;
4317
4318         $_post->ancestors = array();
4319
4320         if ( empty($_post->post_parent) || $_post->ID == $_post->post_parent )
4321                 return;
4322
4323         $id = $_post->ancestors[] = $_post->post_parent;
4324         while ( $ancestor = $wpdb->get_var( $wpdb->prepare("SELECT `post_parent` FROM $wpdb->posts WHERE ID = %d LIMIT 1", $id) ) ) {
4325                 if ( $id == $ancestor )
4326                         break;
4327                 $id = $_post->ancestors[] = $ancestor;
4328         }
4329 }
4330
4331 /**
4332  * Determines which fields of posts are to be saved in revisions.
4333  *
4334  * Does two things. If passed a post *array*, it will return a post array ready
4335  * to be insterted into the posts table as a post revision. Otherwise, returns
4336  * an array whose keys are the post fields to be saved for post revisions.
4337  *
4338  * @package WordPress
4339  * @subpackage Post_Revisions
4340  * @since 2.6.0
4341  * @access private
4342  * @uses apply_filters() Calls '_wp_post_revision_fields' on 'title', 'content' and 'excerpt' fields.
4343  *
4344  * @param array $post Optional a post array to be processed for insertion as a post revision.
4345  * @param bool $autosave optional Is the revision an autosave?
4346  * @return array Post array ready to be inserted as a post revision or array of fields that can be versioned.
4347  */
4348 function _wp_post_revision_fields( $post = null, $autosave = false ) {
4349         static $fields = false;
4350
4351         if ( !$fields ) {
4352                 // Allow these to be versioned
4353                 $fields = array(
4354                         'post_title' => __( 'Title' ),
4355                         'post_content' => __( 'Content' ),
4356                         'post_excerpt' => __( 'Excerpt' ),
4357                 );
4358
4359                 // Runs only once
4360                 $fields = apply_filters( '_wp_post_revision_fields', $fields );
4361
4362                 // WP uses these internally either in versioning or elsewhere - they cannot be versioned
4363                 foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 'post_author' ) as $protect )
4364                         unset( $fields[$protect] );
4365         }
4366
4367         if ( !is_array($post) )
4368                 return $fields;
4369
4370         $return = array();
4371         foreach ( array_intersect( array_keys( $post ), array_keys( $fields ) ) as $field )
4372                 $return[$field] = $post[$field];
4373
4374         $return['post_parent']   = $post['ID'];
4375         $return['post_status']   = 'inherit';
4376         $return['post_type']     = 'revision';
4377         $return['post_name']     = $autosave ? "$post[ID]-autosave" : "$post[ID]-revision";
4378         $return['post_date']     = isset($post['post_modified']) ? $post['post_modified'] : '';
4379         $return['post_date_gmt'] = isset($post['post_modified_gmt']) ? $post['post_modified_gmt'] : '';
4380
4381         return $return;
4382 }
4383
4384 /**
4385  * Saves an already existing post as a post revision.
4386  *
4387  * Typically used immediately prior to post updates.
4388  *
4389  * @package WordPress
4390  * @subpackage Post_Revisions
4391  * @since 2.6.0
4392  *
4393  * @uses _wp_put_post_revision()
4394  *
4395  * @param int $post_id The ID of the post to save as a revision.
4396  * @return mixed Null or 0 if error, new revision ID, if success.
4397  */
4398 function wp_save_post_revision( $post_id ) {
4399         // We do autosaves manually with wp_create_post_autosave()
4400         if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
4401                 return;
4402
4403         // WP_POST_REVISIONS = 0, false
4404         if ( ! WP_POST_REVISIONS )
4405                 return;
4406
4407         if ( !$post = get_post( $post_id, ARRAY_A ) )
4408                 return;
4409
4410         if ( !post_type_supports($post['post_type'], 'revisions') )
4411                 return;
4412
4413         $return = _wp_put_post_revision( $post );
4414
4415         // WP_POST_REVISIONS = true (default), -1
4416         if ( !is_numeric( WP_POST_REVISIONS ) || WP_POST_REVISIONS < 0 )
4417                 return $return;
4418
4419         // all revisions and (possibly) one autosave
4420         $revisions = wp_get_post_revisions( $post_id, array( 'order' => 'ASC' ) );
4421
4422         // WP_POST_REVISIONS = (int) (# of autosaves to save)
4423         $delete = count($revisions) - WP_POST_REVISIONS;
4424
4425         if ( $delete < 1 )
4426                 return $return;
4427
4428         $revisions = array_slice( $revisions, 0, $delete );
4429
4430         for ( $i = 0; isset($revisions[$i]); $i++ ) {
4431                 if ( false !== strpos( $revisions[$i]->post_name, 'autosave' ) )
4432                         continue;
4433                 wp_delete_post_revision( $revisions[$i]->ID );
4434         }
4435
4436         return $return;
4437 }
4438
4439 /**
4440  * Retrieve the autosaved data of the specified post.
4441  *
4442  * Returns a post object containing the information that was autosaved for the
4443  * specified post.
4444  *
4445  * @package WordPress
4446  * @subpackage Post_Revisions
4447  * @since 2.6.0
4448  *
4449  * @param int $post_id The post ID.
4450  * @return object|bool The autosaved data or false on failure or when no autosave exists.
4451  */
4452 function wp_get_post_autosave( $post_id ) {
4453
4454         if ( !$post = get_post( $post_id ) )
4455                 return false;
4456
4457         $q = array(
4458                 'name' => "{$post->ID}-autosave",
4459                 'post_parent' => $post->ID,
4460                 'post_type' => 'revision',
4461                 'post_status' => 'inherit'
4462         );
4463
4464         // Use WP_Query so that the result gets cached
4465         $autosave_query = new WP_Query;
4466
4467         add_action( 'parse_query', '_wp_get_post_autosave_hack' );
4468         $autosave = $autosave_query->query( $q );
4469         remove_action( 'parse_query', '_wp_get_post_autosave_hack' );
4470
4471         if ( $autosave && is_array($autosave) && is_object($autosave[0]) )
4472                 return $autosave[0];
4473
4474         return false;
4475 }
4476
4477 /**
4478  * Internally used to hack WP_Query into submission.
4479  *
4480  * @package WordPress
4481  * @subpackage Post_Revisions
4482  * @since 2.6.0
4483  *
4484  * @param object $query WP_Query object
4485  */
4486 function _wp_get_post_autosave_hack( $query ) {
4487         $query->is_single = false;
4488 }
4489
4490 /**
4491  * Determines if the specified post is a revision.
4492  *
4493  * @package WordPress
4494  * @subpackage Post_Revisions
4495  * @since 2.6.0
4496  *
4497  * @param int|object $post Post ID or post object.
4498  * @return bool|int False if not a revision, ID of revision's parent otherwise.
4499  */
4500 function wp_is_post_revision( $post ) {
4501         if ( !$post = wp_get_post_revision( $post ) )
4502                 return false;
4503         return (int) $post->post_parent;
4504 }
4505
4506 /**
4507  * Determines if the specified post is an autosave.
4508  *
4509  * @package WordPress
4510  * @subpackage Post_Revisions
4511  * @since 2.6.0
4512  *
4513  * @param int|object $post Post ID or post object.
4514  * @return bool|int False if not a revision, ID of autosave's parent otherwise
4515  */
4516 function wp_is_post_autosave( $post ) {
4517         if ( !$post = wp_get_post_revision( $post ) )
4518                 return false;
4519         if ( "{$post->post_parent}-autosave" !== $post->post_name )
4520                 return false;
4521         return (int) $post->post_parent;
4522 }
4523
4524 /**
4525  * Inserts post data into the posts table as a post revision.
4526  *
4527  * @package WordPress
4528  * @subpackage Post_Revisions
4529  * @since 2.6.0
4530  *
4531  * @uses wp_insert_post()
4532  *
4533  * @param int|object|array $post Post ID, post object OR post array.
4534  * @param bool $autosave Optional. Is the revision an autosave?
4535  * @return mixed Null or 0 if error, new revision ID if success.
4536  */
4537 function _wp_put_post_revision( $post = null, $autosave = false ) {
4538         if ( is_object($post) )
4539                 $post = get_object_vars( $post );
4540         elseif ( !is_array($post) )
4541                 $post = get_post($post, ARRAY_A);
4542         if ( !$post || empty($post['ID']) )
4543                 return;
4544
4545         if ( isset($post['post_type']) && 'revision' == $post['post_type'] )
4546                 return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) );
4547
4548         $post = _wp_post_revision_fields( $post, $autosave );
4549         $post = add_magic_quotes($post); //since data is from db
4550
4551         $revision_id = wp_insert_post( $post );
4552         if ( is_wp_error($revision_id) )
4553                 return $revision_id;
4554
4555         if ( $revision_id )
4556                 do_action( '_wp_put_post_revision', $revision_id );
4557         return $revision_id;
4558 }
4559
4560 /**
4561  * Gets a post revision.
4562  *
4563  * @package WordPress
4564  * @subpackage Post_Revisions
4565  * @since 2.6.0
4566  *
4567  * @uses get_post()
4568  *
4569  * @param int|object $post Post ID or post object
4570  * @param string $output Optional. OBJECT, ARRAY_A, or ARRAY_N.
4571  * @param string $filter Optional sanitation filter.  @see sanitize_post()
4572  * @return mixed Null if error or post object if success
4573  */
4574 function &wp_get_post_revision(&$post, $output = OBJECT, $filter = 'raw') {
4575         $null = null;
4576         if ( !$revision = get_post( $post, OBJECT, $filter ) )
4577                 return $revision;
4578         if ( 'revision' !== $revision->post_type )
4579                 return $null;
4580
4581         if ( $output == OBJECT ) {
4582                 return $revision;
4583         } elseif ( $output == ARRAY_A ) {
4584                 $_revision = get_object_vars($revision);
4585                 return $_revision;
4586         } elseif ( $output == ARRAY_N ) {
4587                 $_revision = array_values(get_object_vars($revision));
4588                 return $_revision;
4589         }
4590
4591         return $revision;
4592 }
4593
4594 /**
4595  * Restores a post to the specified revision.
4596  *
4597  * Can restore a past revision using all fields of the post revision, or only selected fields.
4598  *
4599  * @package WordPress
4600  * @subpackage Post_Revisions
4601  * @since 2.6.0
4602  *
4603  * @uses wp_get_post_revision()
4604  * @uses wp_update_post()
4605  * @uses do_action() Calls 'wp_restore_post_revision' on post ID and revision ID if wp_update_post()
4606  *  is successful.
4607  *
4608  * @param int|object $revision_id Revision ID or revision object.
4609  * @param array $fields Optional. What fields to restore from. Defaults to all.
4610  * @return mixed Null if error, false if no fields to restore, (int) post ID if success.
4611  */
4612 function wp_restore_post_revision( $revision_id, $fields = null ) {
4613         if ( !$revision = wp_get_post_revision( $revision_id, ARRAY_A ) )
4614                 return $revision;
4615
4616         if ( !is_array( $fields ) )
4617                 $fields = array_keys( _wp_post_revision_fields() );
4618
4619         $update = array();
4620         foreach( array_intersect( array_keys( $revision ), $fields ) as $field )
4621                 $update[$field] = $revision[$field];
4622
4623         if ( !$update )
4624                 return false;
4625
4626         $update['ID'] = $revision['post_parent'];
4627
4628         $update = add_magic_quotes( $update ); //since data is from db
4629
4630         $post_id = wp_update_post( $update );
4631         if ( is_wp_error( $post_id ) )
4632                 return $post_id;
4633
4634         if ( $post_id )
4635                 do_action( 'wp_restore_post_revision', $post_id, $revision['ID'] );
4636
4637         return $post_id;
4638 }
4639
4640 /**
4641  * Deletes a revision.
4642  *
4643  * Deletes the row from the posts table corresponding to the specified revision.
4644  *
4645  * @package WordPress
4646  * @subpackage Post_Revisions
4647  * @since 2.6.0
4648  *
4649  * @uses wp_get_post_revision()
4650  * @uses wp_delete_post()
4651  *
4652  * @param int|object $revision_id Revision ID or revision object.
4653  * @param array $fields Optional. What fields to restore from.  Defaults to all.
4654  * @return mixed Null if error, false if no fields to restore, (int) post ID if success.
4655  */
4656 function wp_delete_post_revision( $revision_id ) {
4657         if ( !$revision = wp_get_post_revision( $revision_id ) )
4658                 return $revision;
4659
4660         $delete = wp_delete_post( $revision->ID );
4661         if ( is_wp_error( $delete ) )
4662                 return $delete;
4663
4664         if ( $delete )
4665                 do_action( 'wp_delete_post_revision', $revision->ID, $revision );
4666
4667         return $delete;
4668 }
4669
4670 /**
4671  * Returns all revisions of specified post.
4672  *
4673  * @package WordPress
4674  * @subpackage Post_Revisions
4675  * @since 2.6.0
4676  *
4677  * @uses get_children()
4678  *
4679  * @param int|object $post_id Post ID or post object
4680  * @return array empty if no revisions
4681  */
4682 function wp_get_post_revisions( $post_id = 0, $args = null ) {
4683         if ( ! WP_POST_REVISIONS )
4684                 return array();
4685         if ( ( !$post = get_post( $post_id ) ) || empty( $post->ID ) )
4686                 return array();
4687
4688         $defaults = array( 'order' => 'DESC', 'orderby' => 'date' );
4689         $args = wp_parse_args( $args, $defaults );
4690         $args = array_merge( $args, array( 'post_parent' => $post->ID, 'post_type' => 'revision', 'post_status' => 'inherit' ) );
4691
4692         if ( !$revisions = get_children( $args ) )
4693                 return array();
4694         return $revisions;
4695 }
4696
4697 function _set_preview($post) {
4698
4699         if ( ! is_object($post) )
4700                 return $post;
4701
4702         $preview = wp_get_post_autosave($post->ID);
4703
4704         if ( ! is_object($preview) )
4705                 return $post;
4706
4707         $preview = sanitize_post($preview);
4708
4709         $post->post_content = $preview->post_content;
4710         $post->post_title = $preview->post_title;
4711         $post->post_excerpt = $preview->post_excerpt;
4712
4713         return $post;
4714 }
4715
4716 function _show_post_preview() {
4717
4718         if ( isset($_GET['preview_id']) && isset($_GET['preview_nonce']) ) {
4719                 $id = (int) $_GET['preview_id'];
4720
4721                 if ( false == wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . $id ) )
4722                         wp_die( __('You do not have permission to preview drafts.') );
4723
4724                 add_filter('the_preview', '_set_preview');
4725         }
4726 }