[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

/wp-includes/ -> post.php (source)

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


Generated: Sat May 26 08:20:01 2012 Cross-referenced by PHPXref 0.7