WordPress 4.1
[autoinstalls/wordpress.git] / wp-includes / comment.php
1 <?php
2 /**
3  * Manages WordPress comments
4  *
5  * @package WordPress
6  * @subpackage Comment
7  */
8
9 /**
10  * Check whether a comment passes internal checks to be allowed to add.
11  *
12  * If manual comment moderation is set in the administration, then all checks,
13  * regardless of their type and whitelist, will fail and the function will
14  * return false.
15  *
16  * If the number of links exceeds the amount in the administration, then the
17  * check fails. If any of the parameter contents match the blacklist of words,
18  * then the check fails.
19  *
20  * If the comment author was approved before, then the comment is automatically
21  * whitelisted.
22  *
23  * If all checks pass, the function will return true.
24  *
25  * @since 1.2.0
26  *
27  * @global wpdb $wpdb WordPress database abstraction object.
28  *
29  * @param string $author       Comment author name.
30  * @param string $email        Comment author email.
31  * @param string $url          Comment author URL.
32  * @param string $comment      Content of the comment.
33  * @param string $user_ip      Comment author IP address.
34  * @param string $user_agent   Comment author User-Agent.
35  * @param string $comment_type Comment type, either user-submitted comment,
36  *                                     trackback, or pingback.
37  * @return bool If all checks pass, true, otherwise false.
38  */
39 function check_comment($author, $email, $url, $comment, $user_ip, $user_agent, $comment_type) {
40         global $wpdb;
41
42         // If manual moderation is enabled, skip all checks and return false.
43         if ( 1 == get_option('comment_moderation') )
44                 return false;
45
46         /** This filter is documented in wp-includes/comment-template.php */
47         $comment = apply_filters( 'comment_text', $comment );
48
49         // Check for the number of external links if a max allowed number is set.
50         if ( $max_links = get_option( 'comment_max_links' ) ) {
51                 $num_links = preg_match_all( '/<a [^>]*href/i', $comment, $out );
52
53                 /**
54                  * Filter the maximum number of links allowed in a comment.
55                  *
56                  * @since 3.0.0
57                  *
58                  * @param int    $num_links The number of links allowed.
59                  * @param string $url       Comment author's URL. Included in allowed links total.
60                  */
61                 $num_links = apply_filters( 'comment_max_links_url', $num_links, $url );
62
63                 /*
64                  * If the number of links in the comment exceeds the allowed amount,
65                  * fail the check by returning false.
66                  */
67                 if ( $num_links >= $max_links )
68                         return false;
69         }
70
71         $mod_keys = trim(get_option('moderation_keys'));
72
73         // If moderation 'keys' (keywords) are set, process them.
74         if ( !empty($mod_keys) ) {
75                 $words = explode("\n", $mod_keys );
76
77                 foreach ( (array) $words as $word) {
78                         $word = trim($word);
79
80                         // Skip empty lines.
81                         if ( empty($word) )
82                                 continue;
83
84                         /*
85                          * Do some escaping magic so that '#' (number of) characters in the spam
86                          * words don't break things:
87                          */
88                         $word = preg_quote($word, '#');
89
90                         /*
91                          * Check the comment fields for moderation keywords. If any are found,
92                          * fail the check for the given field by returning false.
93                          */
94                         $pattern = "#$word#i";
95                         if ( preg_match($pattern, $author) ) return false;
96                         if ( preg_match($pattern, $email) ) return false;
97                         if ( preg_match($pattern, $url) ) return false;
98                         if ( preg_match($pattern, $comment) ) return false;
99                         if ( preg_match($pattern, $user_ip) ) return false;
100                         if ( preg_match($pattern, $user_agent) ) return false;
101                 }
102         }
103
104         /*
105          * Check if the option to approve comments by previously-approved authors is enabled.
106          *
107          * If it is enabled, check whether the comment author has a previously-approved comment,
108          * as well as whether there are any moderation keywords (if set) present in the author
109          * email address. If both checks pass, return true. Otherwise, return false.
110          */
111         if ( 1 == get_option('comment_whitelist')) {
112                 if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
113                         // expected_slashed ($author, $email)
114                         $ok_to_comment = $wpdb->get_var("SELECT comment_approved FROM $wpdb->comments WHERE comment_author = '$author' AND comment_author_email = '$email' and comment_approved = '1' LIMIT 1");
115                         if ( ( 1 == $ok_to_comment ) &&
116                                 ( empty($mod_keys) || false === strpos( $email, $mod_keys) ) )
117                                         return true;
118                         else
119                                 return false;
120                 } else {
121                         return false;
122                 }
123         }
124         return true;
125 }
126
127 /**
128  * Retrieve the approved comments for post $post_id.
129  *
130  * @since 2.0.0
131  * @since 4.1.0 Refactored to leverage {@see WP_Comment_Query} over a direct query.
132  *
133  * @param  int   $post_id The ID of the post.
134  * @param  array $args    Optional. See {@see WP_Comment_Query::query()} for information
135  *                        on accepted arguments.
136  * @return int|array $comments The approved comments, or number of comments if `$count`
137  *                             argument is true.
138  */
139 function get_approved_comments( $post_id, $args = array() ) {
140         if ( ! $post_id ) {
141                 return array();
142         }
143
144         $defaults = array(
145                 'status'  => 1,
146                 'post_id' => $post_id,
147                 'order'   => 'ASC',
148         );
149         $r = wp_parse_args( $args, $defaults );
150
151         $query = new WP_Comment_Query;
152         return $query->query( $r );
153 }
154
155 /**
156  * Retrieves comment data given a comment ID or comment object.
157  *
158  * If an object is passed then the comment data will be cached and then returned
159  * after being passed through a filter. If the comment is empty, then the global
160  * comment variable will be used, if it is set.
161  *
162  * @since 2.0.0
163  *
164  * @global wpdb $wpdb WordPress database abstraction object.
165  *
166  * @param object|string|int $comment Comment to retrieve.
167  * @param string $output Optional. OBJECT or ARRAY_A or ARRAY_N constants.
168  * @return object|array|null Depends on $output value.
169  */
170 function get_comment(&$comment, $output = OBJECT) {
171         global $wpdb;
172
173         if ( empty($comment) ) {
174                 if ( isset($GLOBALS['comment']) )
175                         $_comment = & $GLOBALS['comment'];
176                 else
177                         $_comment = null;
178         } elseif ( is_object($comment) ) {
179                 wp_cache_add($comment->comment_ID, $comment, 'comment');
180                 $_comment = $comment;
181         } else {
182                 if ( isset($GLOBALS['comment']) && ($GLOBALS['comment']->comment_ID == $comment) ) {
183                         $_comment = & $GLOBALS['comment'];
184                 } elseif ( ! $_comment = wp_cache_get($comment, 'comment') ) {
185                         $_comment = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment));
186                         if ( ! $_comment )
187                                 return null;
188                         wp_cache_add($_comment->comment_ID, $_comment, 'comment');
189                 }
190         }
191
192         /**
193          * Fires after a comment is retrieved.
194          *
195          * @since 2.3.0
196          *
197          * @param mixed $_comment Comment data.
198          */
199         $_comment = apply_filters( 'get_comment', $_comment );
200
201         if ( $output == OBJECT ) {
202                 return $_comment;
203         } elseif ( $output == ARRAY_A ) {
204                 $__comment = get_object_vars($_comment);
205                 return $__comment;
206         } elseif ( $output == ARRAY_N ) {
207                 $__comment = array_values(get_object_vars($_comment));
208                 return $__comment;
209         } else {
210                 return $_comment;
211         }
212 }
213
214 /**
215  * Retrieve a list of comments.
216  *
217  * The comment list can be for the blog as a whole or for an individual post.
218  *
219  * @since 2.7.0
220  *
221  * @global wpdb $wpdb WordPress database abstraction object.
222  *
223  * @param string|array $args Optional. Array or string of arguments. See {@see WP_Comment_Query::query()}
224  *                           for information on accepted arguments. Default empty.
225  * @return int|array List of comments or number of found comments if `$count` argument is true.
226  */
227 function get_comments( $args = '' ) {
228         $query = new WP_Comment_Query;
229         return $query->query( $args );
230 }
231
232 /**
233  * WordPress Comment Query class.
234  *
235  * See {@see WP_Comment_Query::query()} for accepted arguments.
236  *
237  * @since 3.1.0
238  */
239 class WP_Comment_Query {
240         /**
241          * SQL for database query.
242          *
243          * @since 4.0.1
244          * @access public
245          * @var string
246          */
247         public $request;
248
249         /**
250          * Metadata query container
251          *
252          * @since 3.5.0
253          * @access public
254          * @var object WP_Meta_Query
255          */
256         public $meta_query = false;
257
258         /**
259          * Date query container
260          *
261          * @since 3.7.0
262          * @access public
263          * @var object WP_Date_Query
264          */
265         public $date_query = false;
266
267         /**
268          * @var array
269          */
270         public $query_vars;
271
272         /**
273          * @var array
274          */
275         public $comments;
276
277         /**
278          * Make private/protected methods readable for backwards compatibility.
279          *
280          * @since 4.0.0
281          * @access public
282          *
283          * @param callable $name      Method to call.
284          * @param array    $arguments Arguments to pass when calling.
285          * @return mixed|bool Return value of the callback, false otherwise.
286          */
287         public function __call( $name, $arguments ) {
288                 return call_user_func_array( array( $this, $name ), $arguments );
289         }
290
291         /**
292          * Execute the query
293          *
294          * @since 3.1.0
295          * @since 4.1.0 Introduced 'comment__in', 'comment__not_in', 'post_author__in',
296          *              'post_author__not_in', 'author__in', 'author__not_in', 'post__in',
297          *              'post__not_in', 'include_unapproved', 'type__in', and 'type__not_in'
298          *              arguments to $query_vars.
299          *
300          * @param string|array $query_vars {
301          *     Optional. Array or query string of comment query parameters.
302          *
303          *     @type string       $author_email        Comment author email address. Default empty.
304          *     @type array        $author__in          Array of author IDs to include comments for. Default empty.
305          *     @type array        $author__not_in      Array of author IDs to exclude comments for. Default empty.
306          *     @type array        $comment__in         Array of comment IDs to include. Default empty.
307          *     @type array        $comment__not_in     Array of comment IDs to exclude. Default empty.
308          *     @type bool         $count               Whether to return a comment count (true) or array of comment
309          *                                             objects (false). Default false.
310          *     @type array        $date_query          Date query clauses to limit comments by. See {@see WP_Date_Query}.
311          *                                             Default null.
312          *     @type string       $fields              Comment fields to return. Accepts 'ids' for comment IDs only or
313          *                                             empty for all fields. Default empty.
314          *     @type int          $ID                  Currently unused.
315          *     @type array        $include_unapproved  Array of IDs or email addresses of users whose unapproved comments
316          *                                             will be returned by the query regardless of `$status`. Default empty.
317          *     @type int          $karma               Karma score to retrieve matching comments for. Default empty.
318          *     @type string       $meta_key            Include comments with a matching comment meta key. Default empty.
319          *     @type string       $meta_value          Include comments with a matching comment meta value. Requires
320          *                                             `$meta_key` to be set. Default empty.
321          *     @type array        $meta_query          Meta query clauses to limit retrieved comments by.
322          *                                             See {@see WP_Meta_Query}. Default empty.
323          *     @type int          $number              Maximum number of comments to retrieve. Default null (no limit).
324          *     @type int          $offset              Number of comments to offset the query. Used to build LIMIT clause.
325          *                                             Default 0.
326          *     @type string|array $orderby             Comment status or array of statuses. Accepts 'comment_agent',
327          *                                             'comment_approved', 'comment_author', 'comment_author_email',
328          *                                             'comment_author_IP', 'comment_author_url', 'comment_content',
329          *                                             'comment_date', 'comment_date_gmt', 'comment_ID', 'comment_karma',
330          *                                             'comment_parent', 'comment_post_ID', 'comment_type', 'user_id',
331          *                                             'meta_value', 'meta_value_num', or value of $meta_key.
332          *                                              Also accepts false, empty array, or 'none' to disable `ORDER BY`
333          *                                             clause. Default: 'comment_date_gmt'.
334          *     @type string       $order               How to order retrieved comments. Accepts 'ASC', 'DESC'.
335          *                                             Default: 'DESC'.
336          *     @type int          $parent              Parent ID of comment to retrieve children of. Default empty.
337          *     @type array        $post_author__in     Array of author IDs to retrieve comments for. Default empty.
338          *     @type array        $post_author__not_in Array of author IDs *not* to retrieve comments for. Default empty.
339          *     @type int          $post_ID             Currently unused.
340          *     @type int          $post_id             Limit results to those affiliated with a given post ID. Default 0.
341          *     @type array        $post__in            Array of post IDs to include affiliated comments for. Default empty.
342          *     @type array        $post__not_in        Array of post IDs to exclude affiliated comments for. Default empty.
343          *     @type int          $post_author         Comment author ID to limit results by. Default empty.
344          *     @type string       $post_status         Post status to retrieve affiliated comments for. Default empty.
345          *     @type string       $post_type           Post type to retrieve affiliated comments for. Default empty.
346          *     @type string       $post_name           Post name to retrieve affiliated comments for. Default empty.
347          *     @type int          $post_parent         Post parent ID to retrieve affiliated comments for. Default empty.
348          *     @type string       $search              Search term(s) to retrieve matching comments for. Default empty.
349          *     @type string       $status              Comment status to limit results by. Accepts 'hold'
350          *                                             (`comment_status=0`), 'approve' (`comment_status=1`), 'all', or a
351          *                                             custom comment status. Default 'all'.
352          *     @type string|array $type                Include comments of a given type, or array of types. Accepts
353          *                                             'comment', 'pings' (includes 'pingback' and 'trackback'), or any
354          *                                             custom type string. Default empty.
355          *     @type array        $type__in            Include comments from a given array of comment types. Default empty.
356          *     @type array        $type__not_in        Exclude comments from a given array of comment types. Default empty.
357          *     @type int          $user_id             Include comments for a specific user ID. Default empty.
358          * }
359          * @return int|array Array of comments or number of found comments if `$count` is set to true.
360          */
361         public function query( $query_vars ) {
362                 global $wpdb;
363
364                 $defaults = array(
365                         'author_email' => '',
366                         'author__in' => '',
367                         'author__not_in' => '',
368                         'include_unapproved' => '',
369                         'fields' => '',
370                         'ID' => '',
371                         'comment__in' => '',
372                         'comment__not_in' => '',
373                         'karma' => '',
374                         'number' => '',
375                         'offset' => '',
376                         'orderby' => '',
377                         'order' => 'DESC',
378                         'parent' => '',
379                         'post_author__in' => '',
380                         'post_author__not_in' => '',
381                         'post_ID' => '',
382                         'post_id' => 0,
383                         'post__in' => '',
384                         'post__not_in' => '',
385                         'post_author' => '',
386                         'post_name' => '',
387                         'post_parent' => '',
388                         'post_status' => '',
389                         'post_type' => '',
390                         'status' => 'all',
391                         'type' => '',
392                         'type__in' => '',
393                         'type__not_in' => '',
394                         'user_id' => '',
395                         'search' => '',
396                         'count' => false,
397                         'meta_key' => '',
398                         'meta_value' => '',
399                         'meta_query' => '',
400                         'date_query' => null, // See WP_Date_Query
401                 );
402
403                 $groupby = '';
404
405                 $this->query_vars = wp_parse_args( $query_vars, $defaults );
406
407                 // Parse meta query
408                 $this->meta_query = new WP_Meta_Query();
409                 $this->meta_query->parse_query_vars( $this->query_vars );
410
411                 /**
412                  * Fires before comments are retrieved.
413                  *
414                  * @since 3.1.0
415                  *
416                  * @param WP_Comment_Query &$this Current instance of WP_Comment_Query, passed by reference.
417                  */
418                 do_action_ref_array( 'pre_get_comments', array( &$this ) );
419
420                 // $args can be whatever, only use the args defined in defaults to compute the key
421                 $key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $defaults ) ) )  );
422                 $last_changed = wp_cache_get( 'last_changed', 'comment' );
423                 if ( ! $last_changed ) {
424                         $last_changed = microtime();
425                         wp_cache_set( 'last_changed', $last_changed, 'comment' );
426                 }
427                 $cache_key = "get_comments:$key:$last_changed";
428
429                 if ( $cache = wp_cache_get( $cache_key, 'comment' ) ) {
430                         return $cache;
431                 }
432
433                 $where = array();
434
435                 // Assemble clauses related to 'comment_approved'.
436                 $approved_clauses = array();
437
438                 // 'status' accepts an array or a comma-separated string.
439                 $status_clauses = array();
440                 $statuses = $this->query_vars['status'];
441                 if ( ! is_array( $statuses ) ) {
442                         $statuses = preg_split( '/[\s,]+/', $statuses );
443                 }
444
445                 // 'any' overrides other statuses.
446                 if ( ! in_array( 'any', $statuses ) ) {
447                         foreach ( $statuses as $status ) {
448                                 switch ( $status ) {
449                                         case 'hold' :
450                                                 $status_clauses[] = "comment_approved = '0'";
451                                                 break;
452
453                                         case 'approve' :
454                                                 $status_clauses[] = "comment_approved = '1'";
455                                                 break;
456
457                                         case 'all' :
458                                         case '' :
459                                                 $status_clauses[] = "( comment_approved = '0' OR comment_approved = '1' )";
460                                                 break;
461
462                                         default :
463                                                 $status_clauses[] = $wpdb->prepare( "comment_approved = %s", $status );
464                                                 break;
465                                 }
466                         }
467
468                         if ( ! empty( $status_clauses ) ) {
469                                 $approved_clauses[] = '( ' . implode( ' OR ', $status_clauses ) . ' )';
470                         }
471                 }
472
473                 // User IDs or emails whose unapproved comments are included, regardless of $status.
474                 if ( ! empty( $this->query_vars['include_unapproved'] ) ) {
475                         $include_unapproved = $this->query_vars['include_unapproved'];
476
477                         // Accepts arrays or comma-separated strings.
478                         if ( ! is_array( $include_unapproved ) ) {
479                                 $include_unapproved = preg_split( '/[\s,]+/', $include_unapproved );
480                         }
481
482                         $unapproved_ids = $unapproved_emails = array();
483                         foreach ( $include_unapproved as $unapproved_identifier ) {
484                                 // Numeric values are assumed to be user ids.
485                                 if ( is_numeric( $unapproved_identifier ) ) {
486                                         $approved_clauses[] = $wpdb->prepare( "( user_id = %d AND comment_approved = '0' )", $unapproved_identifier );
487
488                                 // Otherwise we match against email addresses.
489                                 } else {
490                                         $approved_clauses[] = $wpdb->prepare( "( comment_author_email = %s AND comment_approved = '0' )", $unapproved_identifier );
491                                 }
492                         }
493                 }
494
495                 // Collapse comment_approved clauses into a single OR-separated clause.
496                 if ( ! empty( $approved_clauses ) ) {
497                         if ( 1 === count( $approved_clauses ) ) {
498                                 $where[] = $approved_clauses[0];
499                         } else {
500                                 $where[] = '( ' . implode( ' OR ', $approved_clauses ) . ' )';
501                         }
502                 }
503
504                 $order = ( 'ASC' == strtoupper( $this->query_vars['order'] ) ) ? 'ASC' : 'DESC';
505
506                 // Disable ORDER BY with 'none', an empty array, or boolean false.
507                 if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
508                         $orderby = '';
509                 } else if ( ! empty( $this->query_vars['orderby'] ) ) {
510                         $ordersby = is_array( $this->query_vars['orderby'] ) ?
511                                 $this->query_vars['orderby'] :
512                                 preg_split( '/[,\s]/', $this->query_vars['orderby'] );
513
514                         $allowed_keys = array(
515                                 'comment_agent',
516                                 'comment_approved',
517                                 'comment_author',
518                                 'comment_author_email',
519                                 'comment_author_IP',
520                                 'comment_author_url',
521                                 'comment_content',
522                                 'comment_date',
523                                 'comment_date_gmt',
524                                 'comment_ID',
525                                 'comment_karma',
526                                 'comment_parent',
527                                 'comment_post_ID',
528                                 'comment_type',
529                                 'user_id',
530                         );
531                         if ( ! empty( $this->query_vars['meta_key'] ) ) {
532                                 $allowed_keys[] = $this->query_vars['meta_key'];
533                                 $allowed_keys[] = 'meta_value';
534                                 $allowed_keys[] = 'meta_value_num';
535                         }
536                         $ordersby = array_intersect( $ordersby, $allowed_keys );
537                         foreach ( $ordersby as $key => $value ) {
538                                 if ( $value == $this->query_vars['meta_key'] || $value == 'meta_value' ) {
539                                         $ordersby[ $key ] = "$wpdb->commentmeta.meta_value";
540                                 } elseif ( $value == 'meta_value_num' ) {
541                                         $ordersby[ $key ] = "$wpdb->commentmeta.meta_value+0";
542                                 }
543                         }
544                         $orderby = empty( $ordersby ) ? 'comment_date_gmt' : implode(', ', $ordersby);
545                 } else {
546                         $orderby = 'comment_date_gmt';
547                 }
548
549                 $number = absint( $this->query_vars['number'] );
550                 $offset = absint( $this->query_vars['offset'] );
551
552                 if ( ! empty( $number ) ) {
553                         if ( $offset ) {
554                                 $limits = 'LIMIT ' . $offset . ',' . $number;
555                         } else {
556                                 $limits = 'LIMIT ' . $number;
557                         }
558                 } else {
559                         $limits = '';
560                 }
561
562                 if ( $this->query_vars['count'] ) {
563                         $fields = 'COUNT(*)';
564                 } else {
565                         switch ( strtolower( $this->query_vars['fields'] ) ) {
566                                 case 'ids':
567                                         $fields = "$wpdb->comments.comment_ID";
568                                         break;
569                                 default:
570                                         $fields = "*";
571                                         break;
572                         }
573                 }
574
575                 $join = '';
576
577                 $post_id = absint( $this->query_vars['post_id'] );
578                 if ( ! empty( $post_id ) ) {
579                         $where[] = $wpdb->prepare( 'comment_post_ID = %d', $post_id );
580                 }
581
582                 // Parse comment IDs for an IN clause.
583                 if ( ! empty( $this->query_vars['comment__in'] ) ) {
584                         $where[] = 'comment_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['comment__in'] ) ) . ' )';
585                 }
586
587                 // Parse comment IDs for a NOT IN clause.
588                 if ( ! empty( $this->query_vars['comment__not_in'] ) ) {
589                         $where[] = 'comment_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['comment__not_in'] ) ) . ' )';
590                 }
591
592                 // Parse comment post IDs for an IN clause.
593                 if ( ! empty( $this->query_vars['post__in'] ) ) {
594                         $where[] = 'comment_post_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__in'] ) ) . ' )';
595                 }
596
597                 // Parse comment post IDs for a NOT IN clause.
598                 if ( ! empty( $this->query_vars['post__not_in'] ) ) {
599                         $where[] = 'comment_post_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__not_in'] ) ) . ' )';
600                 }
601
602                 if ( '' !== $this->query_vars['author_email'] ) {
603                         $where[] = $wpdb->prepare( 'comment_author_email = %s', $this->query_vars['author_email'] );
604                 }
605
606                 if ( '' !== $this->query_vars['karma'] ) {
607                         $where[] = $wpdb->prepare( 'comment_karma = %d', $this->query_vars['karma'] );
608                 }
609
610                 // Filtering by comment_type: 'type', 'type__in', 'type__not_in'.
611                 $raw_types = array(
612                         'IN' => array_merge( (array) $this->query_vars['type'], (array) $this->query_vars['type__in'] ),
613                         'NOT IN' => (array) $this->query_vars['type__not_in'],
614                 );
615
616                 $comment_types = array();
617                 foreach ( $raw_types as $operator => $_raw_types ) {
618                         $_raw_types = array_unique( $_raw_types );
619
620                         foreach ( $_raw_types as $type ) {
621                                 switch ( $type ) {
622                                         // An empty translates to 'all', for backward compatibility
623                                         case '':
624                                         case 'all' :
625                                                 break;
626
627                                         case 'comment':
628                                         case 'comments':
629                                                 $comment_types[ $operator ][] = "''";
630                                                 break;
631
632                                         case 'pings':
633                                                 $comment_types[ $operator ][] = "'pingback'";
634                                                 $comment_types[ $operator ][] = "'trackback'";
635                                                 break;
636
637                                         default:
638                                                 $comment_types[ $operator ][] = $wpdb->prepare( '%s', $type );
639                                                 break;
640                                 }
641                         }
642
643                         if ( ! empty( $comment_types[ $operator ] ) ) {
644                                 $types_sql = implode( ', ', $comment_types[ $operator ] );
645                                 $where[] = "comment_type $operator ($types_sql)";
646                         }
647                 }
648
649                 if ( '' !== $this->query_vars['parent'] ) {
650                         $where[] = $wpdb->prepare( 'comment_parent = %d', $this->query_vars['parent'] );
651                 }
652
653                 if ( is_array( $this->query_vars['user_id'] ) ) {
654                         $where[] = 'user_id IN (' . implode( ',', array_map( 'absint', $this->query_vars['user_id'] ) ) . ')';
655                 } elseif ( '' !== $this->query_vars['user_id'] ) {
656                         $where[] = $wpdb->prepare( 'user_id = %d', $this->query_vars['user_id'] );
657                 }
658
659                 if ( '' !== $this->query_vars['search'] ) {
660                         $search_sql = $this->get_search_sql(
661                                 $this->query_vars['search'],
662                                 array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' )
663                         );
664
665                         // Strip leading 'AND'.
666                         $where[] = preg_replace( '/^\s*AND\s*/', '', $search_sql );
667                 }
668
669                 // If any post-related query vars are passed, join the posts table.
670                 $join_posts_table = false;
671                 $plucked = wp_array_slice_assoc( $this->query_vars, array( 'post_author', 'post_name', 'post_parent', 'post_status', 'post_type' ) );
672                 $post_fields = array_filter( $plucked );
673
674                 if ( ! empty( $post_fields ) ) {
675                         $join_posts_table = true;
676                         foreach ( $post_fields as $field_name => $field_value ) {
677                                 $where[] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} = %s", $field_value );
678                         }
679                 }
680
681                 // Comment author IDs for an IN clause.
682                 if ( ! empty( $this->query_vars['author__in'] ) ) {
683                         $where[] = 'user_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__in'] ) ) . ' )';
684                 }
685
686                 // Comment author IDs for a NOT IN clause.
687                 if ( ! empty( $this->query_vars['author__not_in'] ) ) {
688                         $where[] = 'user_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__not_in'] ) ) . ' )';
689                 }
690
691                 // Post author IDs for an IN clause.
692                 if ( ! empty( $this->query_vars['post_author__in'] ) ) {
693                         $join_posts_table = true;
694                         $where[] = 'post_author IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__in'] ) ) . ' )';
695                 }
696
697                 // Post author IDs for a NOT IN clause.
698                 if ( ! empty( $this->query_vars['post_author__not_in'] ) ) {
699                         $join_posts_table = true;
700                         $where[] = 'post_author NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__not_in'] ) ) . ' )';
701                 }
702
703                 if ( $join_posts_table ) {
704                         $join = "JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
705                 }
706
707                 if ( ! empty( $this->meta_query->queries ) ) {
708                         $clauses = $this->meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $this );
709                         $join .= $clauses['join'];
710
711                         // Strip leading 'AND'.
712                         $where[] = preg_replace( '/^\s*AND\s*/', '', $clauses['where'] );
713
714                         if ( ! $this->query_vars['count'] ) {
715                                 $groupby = "{$wpdb->comments}.comment_ID";
716                         }
717                 }
718
719                 $date_query = $this->query_vars['date_query'];
720                 if ( ! empty( $date_query ) && is_array( $date_query ) ) {
721                         $date_query_object = new WP_Date_Query( $date_query, 'comment_date' );
722                         $where[] = preg_replace( '/^\s*AND\s*/', '', $date_query_object->get_sql() );
723                 }
724
725                 $where = implode( ' AND ', $where );
726
727                 $pieces = array( 'fields', 'join', 'where', 'orderby', 'order', 'limits', 'groupby' );
728                 /**
729                  * Filter the comment query clauses.
730                  *
731                  * @since 3.1.0
732                  *
733                  * @param array            $pieces A compacted array of comment query clauses.
734                  * @param WP_Comment_Query &$this  Current instance of WP_Comment_Query, passed by reference.
735                  */
736                 $clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) );
737
738                 $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
739                 $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
740                 $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
741                 $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
742                 $order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
743                 $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
744                 $groupby = isset( $clauses[ 'groupby' ] ) ? $clauses[ 'groupby' ] : '';
745
746                 if ( $where ) {
747                         $where = 'WHERE ' . $where;
748                 }
749
750                 if ( $groupby ) {
751                         $groupby = 'GROUP BY ' . $groupby;
752                 }
753
754                 if ( $orderby ) {
755                         $orderby = "ORDER BY $orderby $order";
756                 }
757
758                 $this->request = "SELECT $fields FROM $wpdb->comments $join $where $groupby $orderby $limits";
759
760                 if ( $this->query_vars['count'] ) {
761                         return $wpdb->get_var( $this->request );
762                 }
763
764                 if ( 'ids' == $this->query_vars['fields'] ) {
765                         $this->comments = $wpdb->get_col( $this->request );
766                         return array_map( 'intval', $this->comments );
767                 }
768
769                 $results = $wpdb->get_results( $this->request );
770                 /**
771                  * Filter the comment query results.
772                  *
773                  * @since 3.1.0
774                  *
775                  * @param array            $results  An array of comments.
776                  * @param WP_Comment_Query &$this    Current instance of WP_Comment_Query, passed by reference.
777                  */
778                 $comments = apply_filters_ref_array( 'the_comments', array( $results, &$this ) );
779
780                 wp_cache_add( $cache_key, $comments, 'comment' );
781
782                 return $comments;
783         }
784
785         /**
786          * Used internally to generate an SQL string for searching across multiple columns
787          *
788          * @access protected
789          * @since 3.1.0
790          *
791          * @param string $string
792          * @param array $cols
793          * @return string
794          */
795         protected function get_search_sql( $string, $cols ) {
796                 global $wpdb;
797
798                 $like = '%' . $wpdb->esc_like( $string ) . '%';
799
800                 $searches = array();
801                 foreach ( $cols as $col ) {
802                         $searches[] = $wpdb->prepare( "$col LIKE %s", $like );
803                 }
804
805                 return ' AND (' . implode(' OR ', $searches) . ')';
806         }
807 }
808
809 /**
810  * Retrieve all of the WordPress supported comment statuses.
811  *
812  * Comments have a limited set of valid status values, this provides the comment
813  * status values and descriptions.
814  *
815  * @since 2.7.0
816  *
817  * @return array List of comment statuses.
818  */
819 function get_comment_statuses() {
820         $status = array(
821                 'hold'          => __('Unapproved'),
822                 /* translators: comment status  */
823                 'approve'       => _x('Approved', 'adjective'),
824                 /* translators: comment status */
825                 'spam'          => _x('Spam', 'adjective'),
826         );
827
828         return $status;
829 }
830
831 /**
832  * The date the last comment was modified.
833  *
834  * @since 1.5.0
835  *
836  * @global wpdb $wpdb WordPress database abstraction object.
837  *
838  * @param string $timezone Which timezone to use in reference to 'gmt', 'blog',
839  *              or 'server' locations.
840  * @return string Last comment modified date.
841  */
842 function get_lastcommentmodified($timezone = 'server') {
843         global $wpdb;
844         static $cache_lastcommentmodified = array();
845
846         if ( isset($cache_lastcommentmodified[$timezone]) )
847                 return $cache_lastcommentmodified[$timezone];
848
849         $add_seconds_server = date('Z');
850
851         switch ( strtolower($timezone)) {
852                 case 'gmt':
853                         $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
854                         break;
855                 case 'blog':
856                         $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
857                         break;
858                 case 'server':
859                         $lastcommentmodified = $wpdb->get_var($wpdb->prepare("SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server));
860                         break;
861         }
862
863         $cache_lastcommentmodified[$timezone] = $lastcommentmodified;
864
865         return $lastcommentmodified;
866 }
867
868 /**
869  * The amount of comments in a post or total comments.
870  *
871  * A lot like {@link wp_count_comments()}, in that they both return comment
872  * stats (albeit with different types). The {@link wp_count_comments()} actual
873  * caches, but this function does not.
874  *
875  * @since 2.0.0
876  *
877  * @global wpdb $wpdb WordPress database abstraction object.
878  *
879  * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide.
880  * @return array The amount of spam, approved, awaiting moderation, and total comments.
881  */
882 function get_comment_count( $post_id = 0 ) {
883         global $wpdb;
884
885         $post_id = (int) $post_id;
886
887         $where = '';
888         if ( $post_id > 0 ) {
889                 $where = $wpdb->prepare("WHERE comment_post_ID = %d", $post_id);
890         }
891
892         $totals = (array) $wpdb->get_results("
893                 SELECT comment_approved, COUNT( * ) AS total
894                 FROM {$wpdb->comments}
895                 {$where}
896                 GROUP BY comment_approved
897         ", ARRAY_A);
898
899         $comment_count = array(
900                 "approved"              => 0,
901                 "awaiting_moderation"   => 0,
902                 "spam"                  => 0,
903                 "total_comments"        => 0
904         );
905
906         foreach ( $totals as $row ) {
907                 switch ( $row['comment_approved'] ) {
908                         case 'spam':
909                                 $comment_count['spam'] = $row['total'];
910                                 $comment_count["total_comments"] += $row['total'];
911                                 break;
912                         case 1:
913                                 $comment_count['approved'] = $row['total'];
914                                 $comment_count['total_comments'] += $row['total'];
915                                 break;
916                         case 0:
917                                 $comment_count['awaiting_moderation'] = $row['total'];
918                                 $comment_count['total_comments'] += $row['total'];
919                                 break;
920                         default:
921                                 break;
922                 }
923         }
924
925         return $comment_count;
926 }
927
928 //
929 // Comment meta functions
930 //
931
932 /**
933  * Add meta data field to a comment.
934  *
935  * @since 2.9.0
936  * @link http://codex.wordpress.org/Function_Reference/add_comment_meta
937  *
938  * @param int $comment_id Comment ID.
939  * @param string $meta_key Metadata name.
940  * @param mixed $meta_value Metadata value.
941  * @param bool $unique Optional, default is false. Whether the same key should not be added.
942  * @return int|bool Meta ID on success, false on failure.
943  */
944 function add_comment_meta($comment_id, $meta_key, $meta_value, $unique = false) {
945         return add_metadata('comment', $comment_id, $meta_key, $meta_value, $unique);
946 }
947
948 /**
949  * Remove metadata matching criteria from a comment.
950  *
951  * You can match based on the key, or key and value. Removing based on key and
952  * value, will keep from removing duplicate metadata with the same key. It also
953  * allows removing all metadata matching key, if needed.
954  *
955  * @since 2.9.0
956  * @link http://codex.wordpress.org/Function_Reference/delete_comment_meta
957  *
958  * @param int $comment_id comment ID
959  * @param string $meta_key Metadata name.
960  * @param mixed $meta_value Optional. Metadata value.
961  * @return bool True on success, false on failure.
962  */
963 function delete_comment_meta($comment_id, $meta_key, $meta_value = '') {
964         return delete_metadata('comment', $comment_id, $meta_key, $meta_value);
965 }
966
967 /**
968  * Retrieve comment meta field for a comment.
969  *
970  * @since 2.9.0
971  * @link http://codex.wordpress.org/Function_Reference/get_comment_meta
972  *
973  * @param int $comment_id Comment ID.
974  * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
975  * @param bool $single Whether to return a single value.
976  * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
977  *  is true.
978  */
979 function get_comment_meta($comment_id, $key = '', $single = false) {
980         return get_metadata('comment', $comment_id, $key, $single);
981 }
982
983 /**
984  * Update comment meta field based on comment ID.
985  *
986  * Use the $prev_value parameter to differentiate between meta fields with the
987  * same key and comment ID.
988  *
989  * If the meta field for the comment does not exist, it will be added.
990  *
991  * @since 2.9.0
992  * @link http://codex.wordpress.org/Function_Reference/update_comment_meta
993  *
994  * @param int $comment_id Comment ID.
995  * @param string $meta_key Metadata key.
996  * @param mixed $meta_value Metadata value.
997  * @param mixed $prev_value Optional. Previous value to check before removing.
998  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
999  */
1000 function update_comment_meta($comment_id, $meta_key, $meta_value, $prev_value = '') {
1001         return update_metadata('comment', $comment_id, $meta_key, $meta_value, $prev_value);
1002 }
1003
1004 /**
1005  * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
1006  * to recall previous comments by this commentator that are still held in moderation.
1007  *
1008  * @param object $comment Comment object.
1009  * @param object $user Comment author's object.
1010  *
1011  * @since 3.4.0
1012  */
1013 function wp_set_comment_cookies($comment, $user) {
1014         if ( $user->exists() )
1015                 return;
1016
1017         /**
1018          * Filter the lifetime of the comment cookie in seconds.
1019          *
1020          * @since 2.8.0
1021          *
1022          * @param int $seconds Comment cookie lifetime. Default 30000000.
1023          */
1024         $comment_cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 );
1025         $secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
1026         setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
1027         setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
1028         setcookie( 'comment_author_url_' . COOKIEHASH, esc_url($comment->comment_author_url), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
1029 }
1030
1031 /**
1032  * Sanitizes the cookies sent to the user already.
1033  *
1034  * Will only do anything if the cookies have already been created for the user.
1035  * Mostly used after cookies had been sent to use elsewhere.
1036  *
1037  * @since 2.0.4
1038  */
1039 function sanitize_comment_cookies() {
1040         if ( isset( $_COOKIE['comment_author_' . COOKIEHASH] ) ) {
1041                 /**
1042                  * Filter the comment author's name cookie before it is set.
1043                  *
1044                  * When this filter hook is evaluated in wp_filter_comment(),
1045                  * the comment author's name string is passed.
1046                  *
1047                  * @since 1.5.0
1048                  *
1049                  * @param string $author_cookie The comment author name cookie.
1050                  */
1051                 $comment_author = apply_filters( 'pre_comment_author_name', $_COOKIE['comment_author_' . COOKIEHASH] );
1052                 $comment_author = wp_unslash($comment_author);
1053                 $comment_author = esc_attr($comment_author);
1054                 $_COOKIE['comment_author_' . COOKIEHASH] = $comment_author;
1055         }
1056
1057         if ( isset( $_COOKIE['comment_author_email_' . COOKIEHASH] ) ) {
1058                 /**
1059                  * Filter the comment author's email cookie before it is set.
1060                  *
1061                  * When this filter hook is evaluated in wp_filter_comment(),
1062                  * the comment author's email string is passed.
1063                  *
1064                  * @since 1.5.0
1065                  *
1066                  * @param string $author_email_cookie The comment author email cookie.
1067                  */
1068                 $comment_author_email = apply_filters( 'pre_comment_author_email', $_COOKIE['comment_author_email_' . COOKIEHASH] );
1069                 $comment_author_email = wp_unslash($comment_author_email);
1070                 $comment_author_email = esc_attr($comment_author_email);
1071                 $_COOKIE['comment_author_email_'.COOKIEHASH] = $comment_author_email;
1072         }
1073
1074         if ( isset( $_COOKIE['comment_author_url_' . COOKIEHASH] ) ) {
1075                 /**
1076                  * Filter the comment author's URL cookie before it is set.
1077                  *
1078                  * When this filter hook is evaluated in wp_filter_comment(),
1079                  * the comment author's URL string is passed.
1080                  *
1081                  * @since 1.5.0
1082                  *
1083                  * @param string $author_url_cookie The comment author URL cookie.
1084                  */
1085                 $comment_author_url = apply_filters( 'pre_comment_author_url', $_COOKIE['comment_author_url_' . COOKIEHASH] );
1086                 $comment_author_url = wp_unslash($comment_author_url);
1087                 $_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
1088         }
1089 }
1090
1091 /**
1092  * Validates whether this comment is allowed to be made.
1093  *
1094  * @since 2.0.0
1095  *
1096  * @global wpdb $wpdb WordPress database abstraction object.
1097  *
1098  * @param array $commentdata Contains information on the comment
1099  * @return mixed Signifies the approval status (0|1|'spam')
1100  */
1101 function wp_allow_comment( $commentdata ) {
1102         global $wpdb;
1103
1104         // Simple duplicate check
1105         // expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
1106         $dupe = $wpdb->prepare(
1107                 "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
1108                 wp_unslash( $commentdata['comment_post_ID'] ),
1109                 wp_unslash( $commentdata['comment_parent'] ),
1110                 wp_unslash( $commentdata['comment_author'] )
1111         );
1112         if ( $commentdata['comment_author_email'] ) {
1113                 $dupe .= $wpdb->prepare(
1114                         "OR comment_author_email = %s ",
1115                         wp_unslash( $commentdata['comment_author_email'] )
1116                 );
1117         }
1118         $dupe .= $wpdb->prepare(
1119                 ") AND comment_content = %s LIMIT 1",
1120                 wp_unslash( $commentdata['comment_content'] )
1121         );
1122         if ( $wpdb->get_var( $dupe ) ) {
1123                 /**
1124                  * Fires immediately after a duplicate comment is detected.
1125                  *
1126                  * @since 3.0.0
1127                  *
1128                  * @param array $commentdata Comment data.
1129                  */
1130                 do_action( 'comment_duplicate_trigger', $commentdata );
1131                 if ( defined( 'DOING_AJAX' ) ) {
1132                         die( __('Duplicate comment detected; it looks as though you&#8217;ve already said that!') );
1133                 }
1134                 wp_die( __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ), 409 );
1135         }
1136
1137         /**
1138          * Fires immediately before a comment is marked approved.
1139          *
1140          * Allows checking for comment flooding.
1141          *
1142          * @since 2.3.0
1143          *
1144          * @param string $comment_author_IP    Comment author's IP address.
1145          * @param string $comment_author_email Comment author's email.
1146          * @param string $comment_date_gmt     GMT date the comment was posted.
1147          */
1148         do_action(
1149                 'check_comment_flood',
1150                 $commentdata['comment_author_IP'],
1151                 $commentdata['comment_author_email'],
1152                 $commentdata['comment_date_gmt']
1153         );
1154
1155         if ( ! empty( $commentdata['user_id'] ) ) {
1156                 $user = get_userdata( $commentdata['user_id'] );
1157                 $post_author = $wpdb->get_var( $wpdb->prepare(
1158                         "SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
1159                         $commentdata['comment_post_ID']
1160                 ) );
1161         }
1162
1163         if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
1164                 // The author and the admins get respect.
1165                 $approved = 1;
1166         } else {
1167                 // Everyone else's comments will be checked.
1168                 if ( check_comment(
1169                         $commentdata['comment_author'],
1170                         $commentdata['comment_author_email'],
1171                         $commentdata['comment_author_url'],
1172                         $commentdata['comment_content'],
1173                         $commentdata['comment_author_IP'],
1174                         $commentdata['comment_agent'],
1175                         $commentdata['comment_type']
1176                 ) ) {
1177                         $approved = 1;
1178                 } else {
1179                         $approved = 0;
1180                 }
1181
1182                 if ( wp_blacklist_check(
1183                         $commentdata['comment_author'],
1184                         $commentdata['comment_author_email'],
1185                         $commentdata['comment_author_url'],
1186                         $commentdata['comment_content'],
1187                         $commentdata['comment_author_IP'],
1188                         $commentdata['comment_agent']
1189                 ) ) {
1190                         $approved = 'spam';
1191                 }
1192         }
1193
1194         /**
1195          * Filter a comment's approval status before it is set.
1196          *
1197          * @since 2.1.0
1198          *
1199          * @param bool|string $approved    The approval status. Accepts 1, 0, or 'spam'.
1200          * @param array       $commentdata Comment data.
1201          */
1202         $approved = apply_filters( 'pre_comment_approved', $approved, $commentdata );
1203         return $approved;
1204 }
1205
1206 /**
1207  * Check whether comment flooding is occurring.
1208  *
1209  * Won't run, if current user can manage options, so to not block
1210  * administrators.
1211  *
1212  * @since 2.3.0
1213  *
1214  * @global wpdb $wpdb WordPress database abstraction object.
1215  *
1216  * @param string $ip Comment IP.
1217  * @param string $email Comment author email address.
1218  * @param string $date MySQL time string.
1219  */
1220 function check_comment_flood_db( $ip, $email, $date ) {
1221         global $wpdb;
1222         if ( current_user_can( 'manage_options' ) )
1223                 return; // don't throttle admins
1224         $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
1225         if ( $lasttime = $wpdb->get_var( $wpdb->prepare( "SELECT `comment_date_gmt` FROM `$wpdb->comments` WHERE `comment_date_gmt` >= %s AND ( `comment_author_IP` = %s OR `comment_author_email` = %s ) ORDER BY `comment_date_gmt` DESC LIMIT 1", $hour_ago, $ip, $email ) ) ) {
1226                 $time_lastcomment = mysql2date('U', $lasttime, false);
1227                 $time_newcomment  = mysql2date('U', $date, false);
1228                 /**
1229                  * Filter the comment flood status.
1230                  *
1231                  * @since 2.1.0
1232                  *
1233                  * @param bool $bool             Whether a comment flood is occurring. Default false.
1234                  * @param int  $time_lastcomment Timestamp of when the last comment was posted.
1235                  * @param int  $time_newcomment  Timestamp of when the new comment was posted.
1236                  */
1237                 $flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
1238                 if ( $flood_die ) {
1239                         /**
1240                          * Fires before the comment flood message is triggered.
1241                          *
1242                          * @since 1.5.0
1243                          *
1244                          * @param int $time_lastcomment Timestamp of when the last comment was posted.
1245                          * @param int $time_newcomment  Timestamp of when the new comment was posted.
1246                          */
1247                         do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
1248
1249                         if ( defined('DOING_AJAX') )
1250                                 die( __('You are posting comments too quickly. Slow down.') );
1251
1252                         wp_die( __( 'You are posting comments too quickly. Slow down.' ), 429 );
1253                 }
1254         }
1255 }
1256
1257 /**
1258  * Separates an array of comments into an array keyed by comment_type.
1259  *
1260  * @since 2.7.0
1261  *
1262  * @param array $comments Array of comments
1263  * @return array Array of comments keyed by comment_type.
1264  */
1265 function separate_comments(&$comments) {
1266         $comments_by_type = array('comment' => array(), 'trackback' => array(), 'pingback' => array(), 'pings' => array());
1267         $count = count($comments);
1268         for ( $i = 0; $i < $count; $i++ ) {
1269                 $type = $comments[$i]->comment_type;
1270                 if ( empty($type) )
1271                         $type = 'comment';
1272                 $comments_by_type[$type][] = &$comments[$i];
1273                 if ( 'trackback' == $type || 'pingback' == $type )
1274                         $comments_by_type['pings'][] = &$comments[$i];
1275         }
1276
1277         return $comments_by_type;
1278 }
1279
1280 /**
1281  * Calculate the total number of comment pages.
1282  *
1283  * @since 2.7.0
1284  *
1285  * @uses Walker_Comment
1286  *
1287  * @param array $comments Optional array of comment objects. Defaults to $wp_query->comments
1288  * @param int $per_page Optional comments per page.
1289  * @param boolean $threaded Optional control over flat or threaded comments.
1290  * @return int Number of comment pages.
1291  */
1292 function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
1293         global $wp_query;
1294
1295         if ( null === $comments && null === $per_page && null === $threaded && !empty($wp_query->max_num_comment_pages) )
1296                 return $wp_query->max_num_comment_pages;
1297
1298         if ( ( ! $comments || ! is_array( $comments ) ) && ! empty( $wp_query->comments )  )
1299                 $comments = $wp_query->comments;
1300
1301         if ( empty($comments) )
1302                 return 0;
1303
1304         if ( ! get_option( 'page_comments' ) )
1305                 return 1;
1306
1307         if ( !isset($per_page) )
1308                 $per_page = (int) get_query_var('comments_per_page');
1309         if ( 0 === $per_page )
1310                 $per_page = (int) get_option('comments_per_page');
1311         if ( 0 === $per_page )
1312                 return 1;
1313
1314         if ( !isset($threaded) )
1315                 $threaded = get_option('thread_comments');
1316
1317         if ( $threaded ) {
1318                 $walker = new Walker_Comment;
1319                 $count = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
1320         } else {
1321                 $count = ceil( count( $comments ) / $per_page );
1322         }
1323
1324         return $count;
1325 }
1326
1327 /**
1328  * Calculate what page number a comment will appear on for comment paging.
1329  *
1330  * @since 2.7.0
1331  *
1332  * @param int $comment_ID Comment ID.
1333  * @param array $args Optional args.
1334  * @return int|null Comment page number or null on error.
1335  */
1336 function get_page_of_comment( $comment_ID, $args = array() ) {
1337         global $wpdb;
1338
1339         if ( !$comment = get_comment( $comment_ID ) )
1340                 return;
1341
1342         $defaults = array( 'type' => 'all', 'page' => '', 'per_page' => '', 'max_depth' => '' );
1343         $args = wp_parse_args( $args, $defaults );
1344
1345         if ( '' === $args['per_page'] && get_option('page_comments') )
1346                 $args['per_page'] = get_query_var('comments_per_page');
1347         if ( empty($args['per_page']) ) {
1348                 $args['per_page'] = 0;
1349                 $args['page'] = 0;
1350         }
1351         if ( $args['per_page'] < 1 )
1352                 return 1;
1353
1354         if ( '' === $args['max_depth'] ) {
1355                 if ( get_option('thread_comments') )
1356                         $args['max_depth'] = get_option('thread_comments_depth');
1357                 else
1358                         $args['max_depth'] = -1;
1359         }
1360
1361         // Find this comment's top level parent if threading is enabled
1362         if ( $args['max_depth'] > 1 && 0 != $comment->comment_parent )
1363                 return get_page_of_comment( $comment->comment_parent, $args );
1364
1365         $allowedtypes = array(
1366                 'comment' => '',
1367                 'pingback' => 'pingback',
1368                 'trackback' => 'trackback',
1369         );
1370
1371         $comtypewhere = ( 'all' != $args['type'] && isset($allowedtypes[$args['type']]) ) ? " AND comment_type = '" . $allowedtypes[$args['type']] . "'" : '';
1372
1373         // Count comments older than this one
1374         $oldercoms = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = 0 AND comment_approved = '1' AND comment_date_gmt < '%s'" . $comtypewhere, $comment->comment_post_ID, $comment->comment_date_gmt ) );
1375
1376         // No older comments? Then it's page #1.
1377         if ( 0 == $oldercoms )
1378                 return 1;
1379
1380         // Divide comments older than this one by comments per page to get this comment's page number
1381         return ceil( ( $oldercoms + 1 ) / $args['per_page'] );
1382 }
1383
1384 /**
1385  * Does comment contain blacklisted characters or words.
1386  *
1387  * @since 1.5.0
1388  *
1389  * @param string $author The author of the comment
1390  * @param string $email The email of the comment
1391  * @param string $url The url used in the comment
1392  * @param string $comment The comment content
1393  * @param string $user_ip The comment author IP address
1394  * @param string $user_agent The author's browser user agent
1395  * @return bool True if comment contains blacklisted content, false if comment does not
1396  */
1397 function wp_blacklist_check($author, $email, $url, $comment, $user_ip, $user_agent) {
1398         /**
1399          * Fires before the comment is tested for blacklisted characters or words.
1400          *
1401          * @since 1.5.0
1402          *
1403          * @param string $author     Comment author.
1404          * @param string $email      Comment author's email.
1405          * @param string $url        Comment author's URL.
1406          * @param string $comment    Comment content.
1407          * @param string $user_ip    Comment author's IP address.
1408          * @param string $user_agent Comment author's browser user agent.
1409          */
1410         do_action( 'wp_blacklist_check', $author, $email, $url, $comment, $user_ip, $user_agent );
1411
1412         $mod_keys = trim( get_option('blacklist_keys') );
1413         if ( '' == $mod_keys )
1414                 return false; // If moderation keys are empty
1415         $words = explode("\n", $mod_keys );
1416
1417         foreach ( (array) $words as $word ) {
1418                 $word = trim($word);
1419
1420                 // Skip empty lines
1421                 if ( empty($word) ) { continue; }
1422
1423                 // Do some escaping magic so that '#' chars in the
1424                 // spam words don't break things:
1425                 $word = preg_quote($word, '#');
1426
1427                 $pattern = "#$word#i";
1428                 if (
1429                            preg_match($pattern, $author)
1430                         || preg_match($pattern, $email)
1431                         || preg_match($pattern, $url)
1432                         || preg_match($pattern, $comment)
1433                         || preg_match($pattern, $user_ip)
1434                         || preg_match($pattern, $user_agent)
1435                  )
1436                         return true;
1437         }
1438         return false;
1439 }
1440
1441 /**
1442  * Retrieve total comments for blog or single post.
1443  *
1444  * The properties of the returned object contain the 'moderated', 'approved',
1445  * and spam comments for either the entire blog or single post. Those properties
1446  * contain the amount of comments that match the status. The 'total_comments'
1447  * property contains the integer of total comments.
1448  *
1449  * The comment stats are cached and then retrieved, if they already exist in the
1450  * cache.
1451  *
1452  * @since 2.5.0
1453  *
1454  * @param int $post_id Optional. Post ID.
1455  * @return object Comment stats.
1456  */
1457 function wp_count_comments( $post_id = 0 ) {
1458         global $wpdb;
1459
1460         $post_id = (int) $post_id;
1461
1462         /**
1463          * Filter the comments count for a given post.
1464          *
1465          * @since 2.7.0
1466          *
1467          * @param array $count   An empty array.
1468          * @param int   $post_id The post ID.
1469          */
1470         $stats = apply_filters( 'wp_count_comments', array(), $post_id );
1471         if ( !empty($stats) )
1472                 return $stats;
1473
1474         $count = wp_cache_get("comments-{$post_id}", 'counts');
1475
1476         if ( false !== $count )
1477                 return $count;
1478
1479         $where = '';
1480         if ( $post_id > 0 )
1481                 $where = $wpdb->prepare( "WHERE comment_post_ID = %d", $post_id );
1482
1483         $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} {$where} GROUP BY comment_approved", ARRAY_A );
1484
1485         $total = 0;
1486         $approved = array('0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed');
1487         foreach ( (array) $count as $row ) {
1488                 // Don't count post-trashed toward totals
1489                 if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] )
1490                         $total += $row['num_comments'];
1491                 if ( isset( $approved[$row['comment_approved']] ) )
1492                         $stats[$approved[$row['comment_approved']]] = $row['num_comments'];
1493         }
1494
1495         $stats['total_comments'] = $total;
1496         foreach ( $approved as $key ) {
1497                 if ( empty($stats[$key]) )
1498                         $stats[$key] = 0;
1499         }
1500
1501         $stats = (object) $stats;
1502         wp_cache_set("comments-{$post_id}", $stats, 'counts');
1503
1504         return $stats;
1505 }
1506
1507 /**
1508  * Trashes or deletes a comment.
1509  *
1510  * The comment is moved to trash instead of permanently deleted unless trash is
1511  * disabled, item is already in the trash, or $force_delete is true.
1512  *
1513  * The post comment count will be updated if the comment was approved and has a
1514  * post ID available.
1515  *
1516  * @since 2.0.0
1517  *
1518  * @global wpdb $wpdb WordPress database abstraction object.
1519  *
1520  * @param int $comment_id Comment ID
1521  * @param bool $force_delete Whether to bypass trash and force deletion. Default is false.
1522  * @return bool True on success, false on failure.
1523  */
1524 function wp_delete_comment($comment_id, $force_delete = false) {
1525         global $wpdb;
1526         if (!$comment = get_comment($comment_id))
1527                 return false;
1528
1529         if ( !$force_delete && EMPTY_TRASH_DAYS && !in_array( wp_get_comment_status($comment_id), array( 'trash', 'spam' ) ) )
1530                 return wp_trash_comment($comment_id);
1531
1532         /**
1533          * Fires immediately before a comment is deleted from the database.
1534          *
1535          * @since 1.2.0
1536          *
1537          * @param int $comment_id The comment ID.
1538          */
1539         do_action( 'delete_comment', $comment_id );
1540
1541         // Move children up a level.
1542         $children = $wpdb->get_col( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment_id) );
1543         if ( !empty($children) ) {
1544                 $wpdb->update($wpdb->comments, array('comment_parent' => $comment->comment_parent), array('comment_parent' => $comment_id));
1545                 clean_comment_cache($children);
1546         }
1547
1548         // Delete metadata
1549         $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id = %d", $comment_id ) );
1550         foreach ( $meta_ids as $mid )
1551                 delete_metadata_by_mid( 'comment', $mid );
1552
1553         if ( ! $wpdb->delete( $wpdb->comments, array( 'comment_ID' => $comment_id ) ) )
1554                 return false;
1555
1556         /**
1557          * Fires immediately after a comment is deleted from the database.
1558          *
1559          * @since 2.9.0
1560          *
1561          * @param int $comment_id The comment ID.
1562          */
1563         do_action( 'deleted_comment', $comment_id );
1564
1565         $post_id = $comment->comment_post_ID;
1566         if ( $post_id && $comment->comment_approved == 1 )
1567                 wp_update_comment_count($post_id);
1568
1569         clean_comment_cache($comment_id);
1570
1571         /** This action is documented in wp-includes/comment.php */
1572         do_action( 'wp_set_comment_status', $comment_id, 'delete' );
1573
1574         wp_transition_comment_status('delete', $comment->comment_approved, $comment);
1575         return true;
1576 }
1577
1578 /**
1579  * Moves a comment to the Trash
1580  *
1581  * If trash is disabled, comment is permanently deleted.
1582  *
1583  * @since 2.9.0
1584  *
1585  * @param int $comment_id Comment ID.
1586  * @return bool True on success, false on failure.
1587  */
1588 function wp_trash_comment($comment_id) {
1589         if ( !EMPTY_TRASH_DAYS )
1590                 return wp_delete_comment($comment_id, true);
1591
1592         if ( !$comment = get_comment($comment_id) )
1593                 return false;
1594
1595         /**
1596          * Fires immediately before a comment is sent to the Trash.
1597          *
1598          * @since 2.9.0
1599          *
1600          * @param int $comment_id The comment ID.
1601          */
1602         do_action( 'trash_comment', $comment_id );
1603
1604         if ( wp_set_comment_status($comment_id, 'trash') ) {
1605                 add_comment_meta($comment_id, '_wp_trash_meta_status', $comment->comment_approved);
1606                 add_comment_meta($comment_id, '_wp_trash_meta_time', time() );
1607
1608                 /**
1609                  * Fires immediately after a comment is sent to Trash.
1610                  *
1611                  * @since 2.9.0
1612                  *
1613                  * @param int $comment_id The comment ID.
1614                  */
1615                 do_action( 'trashed_comment', $comment_id );
1616                 return true;
1617         }
1618
1619         return false;
1620 }
1621
1622 /**
1623  * Removes a comment from the Trash
1624  *
1625  * @since 2.9.0
1626  *
1627  * @param int $comment_id Comment ID.
1628  * @return bool True on success, false on failure.
1629  */
1630 function wp_untrash_comment($comment_id) {
1631         if ( ! (int)$comment_id )
1632                 return false;
1633
1634         /**
1635          * Fires immediately before a comment is restored from the Trash.
1636          *
1637          * @since 2.9.0
1638          *
1639          * @param int $comment_id The comment ID.
1640          */
1641         do_action( 'untrash_comment', $comment_id );
1642
1643         $status = (string) get_comment_meta($comment_id, '_wp_trash_meta_status', true);
1644         if ( empty($status) )
1645                 $status = '0';
1646
1647         if ( wp_set_comment_status($comment_id, $status) ) {
1648                 delete_comment_meta($comment_id, '_wp_trash_meta_time');
1649                 delete_comment_meta($comment_id, '_wp_trash_meta_status');
1650                 /**
1651                  * Fires immediately after a comment is restored from the Trash.
1652                  *
1653                  * @since 2.9.0
1654                  *
1655                  * @param int $comment_id The comment ID.
1656                  */
1657                 do_action( 'untrashed_comment', $comment_id );
1658                 return true;
1659         }
1660
1661         return false;
1662 }
1663
1664 /**
1665  * Marks a comment as Spam
1666  *
1667  * @since 2.9.0
1668  *
1669  * @param int $comment_id Comment ID.
1670  * @return bool True on success, false on failure.
1671  */
1672 function wp_spam_comment($comment_id) {
1673         if ( !$comment = get_comment($comment_id) )
1674                 return false;
1675
1676         /**
1677          * Fires immediately before a comment is marked as Spam.
1678          *
1679          * @since 2.9.0
1680          *
1681          * @param int $comment_id The comment ID.
1682          */
1683         do_action( 'spam_comment', $comment_id );
1684
1685         if ( wp_set_comment_status($comment_id, 'spam') ) {
1686                 add_comment_meta($comment_id, '_wp_trash_meta_status', $comment->comment_approved);
1687                 /**
1688                  * Fires immediately after a comment is marked as Spam.
1689                  *
1690                  * @since 2.9.0
1691                  *
1692                  * @param int $comment_id The comment ID.
1693                  */
1694                 do_action( 'spammed_comment', $comment_id );
1695                 return true;
1696         }
1697
1698         return false;
1699 }
1700
1701 /**
1702  * Removes a comment from the Spam
1703  *
1704  * @since 2.9.0
1705  *
1706  * @param int $comment_id Comment ID.
1707  * @return bool True on success, false on failure.
1708  */
1709 function wp_unspam_comment($comment_id) {
1710         if ( ! (int)$comment_id )
1711                 return false;
1712
1713         /**
1714          * Fires immediately before a comment is unmarked as Spam.
1715          *
1716          * @since 2.9.0
1717          *
1718          * @param int $comment_id The comment ID.
1719          */
1720         do_action( 'unspam_comment', $comment_id );
1721
1722         $status = (string) get_comment_meta($comment_id, '_wp_trash_meta_status', true);
1723         if ( empty($status) )
1724                 $status = '0';
1725
1726         if ( wp_set_comment_status($comment_id, $status) ) {
1727                 delete_comment_meta($comment_id, '_wp_trash_meta_status');
1728                 /**
1729                  * Fires immediately after a comment is unmarked as Spam.
1730                  *
1731                  * @since 2.9.0
1732                  *
1733                  * @param int $comment_id The comment ID.
1734                  */
1735                 do_action( 'unspammed_comment', $comment_id );
1736                 return true;
1737         }
1738
1739         return false;
1740 }
1741
1742 /**
1743  * The status of a comment by ID.
1744  *
1745  * @since 1.0.0
1746  *
1747  * @param int $comment_id Comment ID
1748  * @return false|string Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure.
1749  */
1750 function wp_get_comment_status($comment_id) {
1751         $comment = get_comment($comment_id);
1752         if ( !$comment )
1753                 return false;
1754
1755         $approved = $comment->comment_approved;
1756
1757         if ( $approved == null )
1758                 return false;
1759         elseif ( $approved == '1' )
1760                 return 'approved';
1761         elseif ( $approved == '0' )
1762                 return 'unapproved';
1763         elseif ( $approved == 'spam' )
1764                 return 'spam';
1765         elseif ( $approved == 'trash' )
1766                 return 'trash';
1767         else
1768                 return false;
1769 }
1770
1771 /**
1772  * Call hooks for when a comment status transition occurs.
1773  *
1774  * Calls hooks for comment status transitions. If the new comment status is not the same
1775  * as the previous comment status, then two hooks will be ran, the first is
1776  * 'transition_comment_status' with new status, old status, and comment data. The
1777  * next action called is 'comment_OLDSTATUS_to_NEWSTATUS' the NEWSTATUS is the
1778  * $new_status parameter and the OLDSTATUS is $old_status parameter; it has the
1779  * comment data.
1780  *
1781  * The final action will run whether or not the comment statuses are the same. The
1782  * action is named 'comment_NEWSTATUS_COMMENTTYPE', NEWSTATUS is from the $new_status
1783  * parameter and COMMENTTYPE is comment_type comment data.
1784  *
1785  * @since 2.7.0
1786  *
1787  * @param string $new_status New comment status.
1788  * @param string $old_status Previous comment status.
1789  * @param object $comment Comment data.
1790  */
1791 function wp_transition_comment_status($new_status, $old_status, $comment) {
1792         /*
1793          * Translate raw statuses to human readable formats for the hooks.
1794          * This is not a complete list of comment status, it's only the ones
1795          * that need to be renamed
1796          */
1797         $comment_statuses = array(
1798                 0         => 'unapproved',
1799                 'hold'    => 'unapproved', // wp_set_comment_status() uses "hold"
1800                 1         => 'approved',
1801                 'approve' => 'approved', // wp_set_comment_status() uses "approve"
1802         );
1803         if ( isset($comment_statuses[$new_status]) ) $new_status = $comment_statuses[$new_status];
1804         if ( isset($comment_statuses[$old_status]) ) $old_status = $comment_statuses[$old_status];
1805
1806         // Call the hooks
1807         if ( $new_status != $old_status ) {
1808                 /**
1809                  * Fires when the comment status is in transition.
1810                  *
1811                  * @since 2.7.0
1812                  *
1813                  * @param int|string $new_status The new comment status.
1814                  * @param int|string $old_status The old comment status.
1815                  * @param object     $comment    The comment data.
1816                  */
1817                 do_action( 'transition_comment_status', $new_status, $old_status, $comment );
1818                 /**
1819                  * Fires when the comment status is in transition from one specific status to another.
1820                  *
1821                  * The dynamic portions of the hook name, `$old_status`, and `$new_status`,
1822                  * refer to the old and new comment statuses, respectively.
1823                  *
1824                  * @since 2.7.0
1825                  *
1826                  * @param object $comment Comment object.
1827                  */
1828                 do_action( "comment_{$old_status}_to_{$new_status}", $comment );
1829         }
1830         /**
1831          * Fires when the status of a specific comment type is in transition.
1832          *
1833          * The dynamic portions of the hook name, `$new_status`, and `$comment->comment_type`,
1834          * refer to the new comment status, and the type of comment, respectively.
1835          *
1836          * Typical comment types include an empty string (standard comment), 'pingback',
1837          * or 'trackback'.
1838          *
1839          * @since 2.7.0
1840          *
1841          * @param int $comment_ID The comment ID.
1842          * @param obj $comment    Comment object.
1843          */
1844         do_action( "comment_{$new_status}_{$comment->comment_type}", $comment->comment_ID, $comment );
1845 }
1846
1847 /**
1848  * Get current commenter's name, email, and URL.
1849  *
1850  * Expects cookies content to already be sanitized. User of this function might
1851  * wish to recheck the returned array for validity.
1852  *
1853  * @see sanitize_comment_cookies() Use to sanitize cookies
1854  *
1855  * @since 2.0.4
1856  *
1857  * @return array Comment author, email, url respectively.
1858  */
1859 function wp_get_current_commenter() {
1860         // Cookies should already be sanitized.
1861
1862         $comment_author = '';
1863         if ( isset($_COOKIE['comment_author_'.COOKIEHASH]) )
1864                 $comment_author = $_COOKIE['comment_author_'.COOKIEHASH];
1865
1866         $comment_author_email = '';
1867         if ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) )
1868                 $comment_author_email = $_COOKIE['comment_author_email_'.COOKIEHASH];
1869
1870         $comment_author_url = '';
1871         if ( isset($_COOKIE['comment_author_url_'.COOKIEHASH]) )
1872                 $comment_author_url = $_COOKIE['comment_author_url_'.COOKIEHASH];
1873
1874         /**
1875          * Filter the current commenter's name, email, and URL.
1876          *
1877          * @since 3.1.0
1878          *
1879          * @param string $comment_author       Comment author's name.
1880          * @param string $comment_author_email Comment author's email.
1881          * @param string $comment_author_url   Comment author's URL.
1882          */
1883         return apply_filters( 'wp_get_current_commenter', compact('comment_author', 'comment_author_email', 'comment_author_url') );
1884 }
1885
1886 /**
1887  * Inserts a comment to the database.
1888  *
1889  * The available comment data key names are 'comment_author_IP', 'comment_date',
1890  * 'comment_date_gmt', 'comment_parent', 'comment_approved', and 'user_id'.
1891  *
1892  * @since 2.0.0
1893  *
1894  * @global wpdb $wpdb WordPress database abstraction object.
1895  *
1896  * @param array $commentdata Contains information on the comment.
1897  * @return int|bool The new comment's ID on success, false on failure.
1898  */
1899 function wp_insert_comment( $commentdata ) {
1900         global $wpdb;
1901         $data = wp_unslash( $commentdata );
1902
1903         $comment_author       = ! isset( $data['comment_author'] )       ? '' : $data['comment_author'];
1904         $comment_author_email = ! isset( $data['comment_author_email'] ) ? '' : $data['comment_author_email'];
1905         $comment_author_url   = ! isset( $data['comment_author_url'] )   ? '' : $data['comment_author_url'];
1906         $comment_author_IP    = ! isset( $data['comment_author_IP'] )    ? '' : $data['comment_author_IP'];
1907
1908         $comment_date     = ! isset( $data['comment_date'] )     ? current_time( 'mysql' )            : $data['comment_date'];
1909         $comment_date_gmt = ! isset( $data['comment_date_gmt'] ) ? get_gmt_from_date( $comment_date ) : $data['comment_date_gmt'];
1910
1911         $comment_post_ID  = ! isset( $data['comment_post_ID'] )  ? '' : $data['comment_post_ID'];
1912         $comment_content  = ! isset( $data['comment_content'] )  ? '' : $data['comment_content'];
1913         $comment_karma    = ! isset( $data['comment_karma'] )    ? 0  : $data['comment_karma'];
1914         $comment_approved = ! isset( $data['comment_approved'] ) ? 1  : $data['comment_approved'];
1915         $comment_agent    = ! isset( $data['comment_agent'] )    ? '' : $data['comment_agent'];
1916         $comment_type     = ! isset( $data['comment_type'] )     ? '' : $data['comment_type'];
1917         $comment_parent   = ! isset( $data['comment_parent'] )   ? 0  : $data['comment_parent'];
1918
1919         $user_id  = ! isset( $data['user_id'] ) ? 0 : $data['user_id'];
1920
1921         $compacted = compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_date', 'comment_date_gmt', 'comment_content', 'comment_karma', 'comment_approved', 'comment_agent', 'comment_type', 'comment_parent', 'user_id' );
1922         if ( ! $wpdb->insert( $wpdb->comments, $compacted ) ) {
1923                 return false;
1924         }
1925
1926         $id = (int) $wpdb->insert_id;
1927
1928         if ( $comment_approved == 1 ) {
1929                 wp_update_comment_count( $comment_post_ID );
1930         }
1931         $comment = get_comment( $id );
1932
1933         /**
1934          * Fires immediately after a comment is inserted into the database.
1935          *
1936          * @since 2.8.0
1937          *
1938          * @param int $id      The comment ID.
1939          * @param obj $comment Comment object.
1940          */
1941         do_action( 'wp_insert_comment', $id, $comment );
1942
1943         wp_cache_set( 'last_changed', microtime(), 'comment' );
1944
1945         return $id;
1946 }
1947
1948 /**
1949  * Filters and sanitizes comment data.
1950  *
1951  * Sets the comment data 'filtered' field to true when finished. This can be
1952  * checked as to whether the comment should be filtered and to keep from
1953  * filtering the same comment more than once.
1954  *
1955  * @since 2.0.0
1956  *
1957  * @param array $commentdata Contains information on the comment.
1958  * @return array Parsed comment information.
1959  */
1960 function wp_filter_comment($commentdata) {
1961         if ( isset( $commentdata['user_ID'] ) ) {
1962                 /**
1963                  * Filter the comment author's user id before it is set.
1964                  *
1965                  * The first time this filter is evaluated, 'user_ID' is checked
1966                  * (for back-compat), followed by the standard 'user_id' value.
1967                  *
1968                  * @since 1.5.0
1969                  *
1970                  * @param int $user_ID The comment author's user ID.
1971                  */
1972                 $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_ID'] );
1973         } elseif ( isset( $commentdata['user_id'] ) ) {
1974                 /** This filter is documented in wp-includes/comment.php */
1975                 $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_id'] );
1976         }
1977
1978         /**
1979          * Filter the comment author's browser user agent before it is set.
1980          *
1981          * @since 1.5.0
1982          *
1983          * @param int $comment_agent The comment author's browser user agent.
1984          */
1985         $commentdata['comment_agent'] = apply_filters( 'pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) );
1986         /** This filter is documented in wp-includes/comment.php */
1987         $commentdata['comment_author'] = apply_filters( 'pre_comment_author_name', $commentdata['comment_author'] );
1988         /**
1989          * Filter the comment content before it is set.
1990          *
1991          * @since 1.5.0
1992          *
1993          * @param int $comment_content The comment content.
1994          */
1995         $commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] );
1996         /**
1997          * Filter the comment author's IP before it is set.
1998          *
1999          * @since 1.5.0
2000          *
2001          * @param int $comment_author_ip The comment author's IP.
2002          */
2003         $commentdata['comment_author_IP'] = apply_filters( 'pre_comment_user_ip', $commentdata['comment_author_IP'] );
2004         /** This filter is documented in wp-includes/comment.php */
2005         $commentdata['comment_author_url'] = apply_filters( 'pre_comment_author_url', $commentdata['comment_author_url'] );
2006         /** This filter is documented in wp-includes/comment.php */
2007         $commentdata['comment_author_email'] = apply_filters( 'pre_comment_author_email', $commentdata['comment_author_email'] );
2008         $commentdata['filtered'] = true;
2009         return $commentdata;
2010 }
2011
2012 /**
2013  * Whether a comment should be blocked because of comment flood.
2014  *
2015  * @since 2.1.0
2016  *
2017  * @param bool $block Whether plugin has already blocked comment.
2018  * @param int $time_lastcomment Timestamp for last comment.
2019  * @param int $time_newcomment Timestamp for new comment.
2020  * @return bool Whether comment should be blocked.
2021  */
2022 function wp_throttle_comment_flood($block, $time_lastcomment, $time_newcomment) {
2023         if ( $block ) // a plugin has already blocked... we'll let that decision stand
2024                 return $block;
2025         if ( ($time_newcomment - $time_lastcomment) < 15 )
2026                 return true;
2027         return false;
2028 }
2029
2030 /**
2031  * Adds a new comment to the database.
2032  *
2033  * Filters new comment to ensure that the fields are sanitized and valid before
2034  * inserting comment into database. Calls 'comment_post' action with comment ID
2035  * and whether comment is approved by WordPress. Also has 'preprocess_comment'
2036  * filter for processing the comment data before the function handles it.
2037  *
2038  * We use REMOTE_ADDR here directly. If you are behind a proxy, you should ensure
2039  * that it is properly set, such as in wp-config.php, for your environment.
2040  * See {@link https://core.trac.wordpress.org/ticket/9235}
2041  *
2042  * @since 1.5.0
2043  * @param array $commentdata Contains information on the comment.
2044  * @return int|bool The ID of the comment on success, false on failure.
2045  */
2046 function wp_new_comment( $commentdata ) {
2047         if ( isset( $commentdata['user_ID'] ) ) {
2048                 $commentdata['user_id'] = $commentdata['user_ID'] = (int) $commentdata['user_ID'];
2049         }
2050
2051         $prefiltered_user_id = ( isset( $commentdata['user_id'] ) ) ? (int) $commentdata['user_id'] : 0;
2052
2053         /**
2054          * Filter a comment's data before it is sanitized and inserted into the database.
2055          *
2056          * @since 1.5.0
2057          *
2058          * @param array $commentdata Comment data.
2059          */
2060         $commentdata = apply_filters( 'preprocess_comment', $commentdata );
2061
2062         $commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID'];
2063         if ( isset( $commentdata['user_ID'] ) && $prefiltered_user_id !== (int) $commentdata['user_ID'] ) {
2064                 $commentdata['user_id'] = $commentdata['user_ID'] = (int) $commentdata['user_ID'];
2065         } elseif ( isset( $commentdata['user_id'] ) ) {
2066                 $commentdata['user_id'] = (int) $commentdata['user_id'];
2067         }
2068
2069         $commentdata['comment_parent'] = isset($commentdata['comment_parent']) ? absint($commentdata['comment_parent']) : 0;
2070         $parent_status = ( 0 < $commentdata['comment_parent'] ) ? wp_get_comment_status($commentdata['comment_parent']) : '';
2071         $commentdata['comment_parent'] = ( 'approved' == $parent_status || 'unapproved' == $parent_status ) ? $commentdata['comment_parent'] : 0;
2072
2073         $commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '',$_SERVER['REMOTE_ADDR'] );
2074         $commentdata['comment_agent']     = isset( $_SERVER['HTTP_USER_AGENT'] ) ? substr( $_SERVER['HTTP_USER_AGENT'], 0, 254 ) : '';
2075
2076         $commentdata['comment_date']     = current_time('mysql');
2077         $commentdata['comment_date_gmt'] = current_time('mysql', 1);
2078
2079         $commentdata = wp_filter_comment($commentdata);
2080
2081         $commentdata['comment_approved'] = wp_allow_comment($commentdata);
2082
2083         $comment_ID = wp_insert_comment($commentdata);
2084         if ( ! $comment_ID ) {
2085                 return false;
2086         }
2087
2088         /**
2089          * Fires immediately after a comment is inserted into the database.
2090          *
2091          * @since 1.2.0
2092          *
2093          * @param int $comment_ID       The comment ID.
2094          * @param int $comment_approved 1 (true) if the comment is approved, 0 (false) if not.
2095          */
2096         do_action( 'comment_post', $comment_ID, $commentdata['comment_approved'] );
2097
2098         if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
2099                 if ( '0' == $commentdata['comment_approved'] ) {
2100                         wp_notify_moderator( $comment_ID );
2101                 }
2102
2103                 // wp_notify_postauthor() checks if notifying the author of their own comment.
2104                 // By default, it won't, but filters can override this.
2105                 if ( get_option( 'comments_notify' ) && $commentdata['comment_approved'] ) {
2106                         wp_notify_postauthor( $comment_ID );
2107                 }
2108         }
2109
2110         return $comment_ID;
2111 }
2112
2113 /**
2114  * Sets the status of a comment.
2115  *
2116  * The 'wp_set_comment_status' action is called after the comment is handled.
2117  * If the comment status is not in the list, then false is returned.
2118  *
2119  * @since 1.0.0
2120  *
2121  * @param int $comment_id Comment ID.
2122  * @param string $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'.
2123  * @param bool $wp_error Whether to return a WP_Error object if there is a failure. Default is false.
2124  * @return bool|WP_Error True on success, false or WP_Error on failure.
2125  */
2126 function wp_set_comment_status($comment_id, $comment_status, $wp_error = false) {
2127         global $wpdb;
2128
2129         switch ( $comment_status ) {
2130                 case 'hold':
2131                 case '0':
2132                         $status = '0';
2133                         break;
2134                 case 'approve':
2135                 case '1':
2136                         $status = '1';
2137                         if ( get_option('comments_notify') ) {
2138                                 wp_notify_postauthor( $comment_id );
2139                         }
2140                         break;
2141                 case 'spam':
2142                         $status = 'spam';
2143                         break;
2144                 case 'trash':
2145                         $status = 'trash';
2146                         break;
2147                 default:
2148                         return false;
2149         }
2150
2151         $comment_old = clone get_comment($comment_id);
2152
2153         if ( !$wpdb->update( $wpdb->comments, array('comment_approved' => $status), array('comment_ID' => $comment_id) ) ) {
2154                 if ( $wp_error )
2155                         return new WP_Error('db_update_error', __('Could not update comment status'), $wpdb->last_error);
2156                 else
2157                         return false;
2158         }
2159
2160         clean_comment_cache($comment_id);
2161
2162         $comment = get_comment($comment_id);
2163
2164         /**
2165          * Fires immediately before transitioning a comment's status from one to another
2166          * in the database.
2167          *
2168          * @since 1.5.0
2169          *
2170          * @param int         $comment_id     Comment ID.
2171          * @param string|bool $comment_status Current comment status. Possible values include
2172          *                                    'hold', 'approve', 'spam', 'trash', or false.
2173          */
2174         do_action( 'wp_set_comment_status', $comment_id, $comment_status );
2175
2176         wp_transition_comment_status($comment_status, $comment_old->comment_approved, $comment);
2177
2178         wp_update_comment_count($comment->comment_post_ID);
2179
2180         return true;
2181 }
2182
2183 /**
2184  * Updates an existing comment in the database.
2185  *
2186  * Filters the comment and makes sure certain fields are valid before updating.
2187  *
2188  * @since 2.0.0
2189  *
2190  * @global wpdb $wpdb WordPress database abstraction object.
2191  *
2192  * @param array $commentarr Contains information on the comment.
2193  * @return int Comment was updated if value is 1, or was not updated if value is 0.
2194  */
2195 function wp_update_comment($commentarr) {
2196         global $wpdb;
2197
2198         // First, get all of the original fields
2199         $comment = get_comment($commentarr['comment_ID'], ARRAY_A);
2200         if ( empty( $comment ) ) {
2201                 return 0;
2202         }
2203         // Escape data pulled from DB.
2204         $comment = wp_slash($comment);
2205
2206         $old_status = $comment['comment_approved'];
2207
2208         // Merge old and new fields with new fields overwriting old ones.
2209         $commentarr = array_merge($comment, $commentarr);
2210
2211         $commentarr = wp_filter_comment( $commentarr );
2212
2213         // Now extract the merged array.
2214         $data = wp_unslash( $commentarr );
2215
2216         /**
2217          * Filter the comment content before it is updated in the database.
2218          *
2219          * @since 1.5.0
2220          *
2221          * @param string $comment_content The comment data.
2222          */
2223         $data['comment_content'] = apply_filters( 'comment_save_pre', $data['comment_content'] );
2224
2225         $data['comment_date_gmt'] = get_gmt_from_date( $data['comment_date'] );
2226
2227         if ( ! isset( $data['comment_approved'] ) ) {
2228                 $data['comment_approved'] = 1;
2229         } else if ( 'hold' == $data['comment_approved'] ) {
2230                 $data['comment_approved'] = 0;
2231         } else if ( 'approve' == $data['comment_approved'] ) {
2232                 $data['comment_approved'] = 1;
2233         }
2234
2235         $comment_ID = $data['comment_ID'];
2236         $comment_post_ID = $data['comment_post_ID'];
2237         $keys = array( 'comment_content', 'comment_author', 'comment_author_email', 'comment_approved', 'comment_karma', 'comment_author_url', 'comment_date', 'comment_date_gmt', 'comment_parent' );
2238         $data = wp_array_slice_assoc( $data, $keys );
2239         $rval = $wpdb->update( $wpdb->comments, $data, compact( 'comment_ID' ) );
2240
2241         clean_comment_cache( $comment_ID );
2242         wp_update_comment_count( $comment_post_ID );
2243         /**
2244          * Fires immediately after a comment is updated in the database.
2245          *
2246          * The hook also fires immediately before comment status transition hooks are fired.
2247          *
2248          * @since 1.2.0
2249          *
2250          * @param int $comment_ID The comment ID.
2251          */
2252         do_action( 'edit_comment', $comment_ID );
2253         $comment = get_comment($comment_ID);
2254         wp_transition_comment_status($comment->comment_approved, $old_status, $comment);
2255         return $rval;
2256 }
2257
2258 /**
2259  * Whether to defer comment counting.
2260  *
2261  * When setting $defer to true, all post comment counts will not be updated
2262  * until $defer is set to false. When $defer is set to false, then all
2263  * previously deferred updated post comment counts will then be automatically
2264  * updated without having to call wp_update_comment_count() after.
2265  *
2266  * @since 2.5.0
2267  * @staticvar bool $_defer
2268  *
2269  * @param bool $defer
2270  * @return bool
2271  */
2272 function wp_defer_comment_counting($defer=null) {
2273         static $_defer = false;
2274
2275         if ( is_bool($defer) ) {
2276                 $_defer = $defer;
2277                 // flush any deferred counts
2278                 if ( !$defer )
2279                         wp_update_comment_count( null, true );
2280         }
2281
2282         return $_defer;
2283 }
2284
2285 /**
2286  * Updates the comment count for post(s).
2287  *
2288  * When $do_deferred is false (is by default) and the comments have been set to
2289  * be deferred, the post_id will be added to a queue, which will be updated at a
2290  * later date and only updated once per post ID.
2291  *
2292  * If the comments have not be set up to be deferred, then the post will be
2293  * updated. When $do_deferred is set to true, then all previous deferred post
2294  * IDs will be updated along with the current $post_id.
2295  *
2296  * @since 2.1.0
2297  * @see wp_update_comment_count_now() For what could cause a false return value
2298  *
2299  * @param int $post_id Post ID
2300  * @param bool $do_deferred Whether to process previously deferred post comment counts
2301  * @return bool|null True on success, false on failure
2302  */
2303 function wp_update_comment_count($post_id, $do_deferred=false) {
2304         static $_deferred = array();
2305
2306         if ( $do_deferred ) {
2307                 $_deferred = array_unique($_deferred);
2308                 foreach ( $_deferred as $i => $_post_id ) {
2309                         wp_update_comment_count_now($_post_id);
2310                         unset( $_deferred[$i] ); /** @todo Move this outside of the foreach and reset $_deferred to an array instead */
2311                 }
2312         }
2313
2314         if ( wp_defer_comment_counting() ) {
2315                 $_deferred[] = $post_id;
2316                 return true;
2317         }
2318         elseif ( $post_id ) {
2319                 return wp_update_comment_count_now($post_id);
2320         }
2321
2322 }
2323
2324 /**
2325  * Updates the comment count for the post.
2326  *
2327  * @since 2.5.0
2328  *
2329  * @global wpdb $wpdb WordPress database abstraction object.
2330  *
2331  * @param int $post_id Post ID
2332  * @return bool True on success, false on '0' $post_id or if post with ID does not exist.
2333  */
2334 function wp_update_comment_count_now($post_id) {
2335         global $wpdb;
2336         $post_id = (int) $post_id;
2337         if ( !$post_id )
2338                 return false;
2339         if ( !$post = get_post($post_id) )
2340                 return false;
2341
2342         $old = (int) $post->comment_count;
2343         $new = (int) $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id) );
2344         $wpdb->update( $wpdb->posts, array('comment_count' => $new), array('ID' => $post_id) );
2345
2346         clean_post_cache( $post );
2347
2348         /**
2349          * Fires immediately after a post's comment count is updated in the database.
2350          *
2351          * @since 2.3.0
2352          *
2353          * @param int $post_id Post ID.
2354          * @param int $new     The new comment count.
2355          * @param int $old     The old comment count.
2356          */
2357         do_action( 'wp_update_comment_count', $post_id, $new, $old );
2358         /** This action is documented in wp-includes/post.php */
2359         do_action( 'edit_post', $post_id, $post );
2360
2361         return true;
2362 }
2363
2364 //
2365 // Ping and trackback functions.
2366 //
2367
2368 /**
2369  * Finds a pingback server URI based on the given URL.
2370  *
2371  * Checks the HTML for the rel="pingback" link and x-pingback headers. It does
2372  * a check for the x-pingback headers first and returns that, if available. The
2373  * check for the rel="pingback" has more overhead than just the header.
2374  *
2375  * @since 1.5.0
2376  *
2377  * @param string $url URL to ping.
2378  * @param int $deprecated Not Used.
2379  * @return false|string False on failure, string containing URI on success.
2380  */
2381 function discover_pingback_server_uri( $url, $deprecated = '' ) {
2382         if ( !empty( $deprecated ) )
2383                 _deprecated_argument( __FUNCTION__, '2.7' );
2384
2385         $pingback_str_dquote = 'rel="pingback"';
2386         $pingback_str_squote = 'rel=\'pingback\'';
2387
2388         /** @todo Should use Filter Extension or custom preg_match instead. */
2389         $parsed_url = parse_url($url);
2390
2391         if ( ! isset( $parsed_url['host'] ) ) // Not an URL. This should never happen.
2392                 return false;
2393
2394         //Do not search for a pingback server on our own uploads
2395         $uploads_dir = wp_upload_dir();
2396         if ( 0 === strpos($url, $uploads_dir['baseurl']) )
2397                 return false;
2398
2399         $response = wp_safe_remote_head( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
2400
2401         if ( is_wp_error( $response ) )
2402                 return false;
2403
2404         if ( wp_remote_retrieve_header( $response, 'x-pingback' ) )
2405                 return wp_remote_retrieve_header( $response, 'x-pingback' );
2406
2407         // Not an (x)html, sgml, or xml page, no use going further.
2408         if ( preg_match('#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'content-type' )) )
2409                 return false;
2410
2411         // Now do a GET since we're going to look in the html headers (and we're sure it's not a binary file)
2412         $response = wp_safe_remote_get( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
2413
2414         if ( is_wp_error( $response ) )
2415                 return false;
2416
2417         $contents = wp_remote_retrieve_body( $response );
2418
2419         $pingback_link_offset_dquote = strpos($contents, $pingback_str_dquote);
2420         $pingback_link_offset_squote = strpos($contents, $pingback_str_squote);
2421         if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
2422                 $quote = ($pingback_link_offset_dquote) ? '"' : '\'';
2423                 $pingback_link_offset = ($quote=='"') ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
2424                 $pingback_href_pos = @strpos($contents, 'href=', $pingback_link_offset);
2425                 $pingback_href_start = $pingback_href_pos+6;
2426                 $pingback_href_end = @strpos($contents, $quote, $pingback_href_start);
2427                 $pingback_server_url_len = $pingback_href_end - $pingback_href_start;
2428                 $pingback_server_url = substr($contents, $pingback_href_start, $pingback_server_url_len);
2429
2430                 // We may find rel="pingback" but an incomplete pingback URL
2431                 if ( $pingback_server_url_len > 0 ) { // We got it!
2432                         return $pingback_server_url;
2433                 }
2434         }
2435
2436         return false;
2437 }
2438
2439 /**
2440  * Perform all pingbacks, enclosures, trackbacks, and send to pingback services.
2441  *
2442  * @since 2.1.0
2443  *
2444  * @global wpdb $wpdb WordPress database abstraction object.
2445  */
2446 function do_all_pings() {
2447         global $wpdb;
2448
2449         // Do pingbacks
2450         while ($ping = $wpdb->get_row("SELECT ID, post_content, meta_id FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_pingme' LIMIT 1")) {
2451                 delete_metadata_by_mid( 'post', $ping->meta_id );
2452                 pingback( $ping->post_content, $ping->ID );
2453         }
2454
2455         // Do Enclosures
2456         while ($enclosure = $wpdb->get_row("SELECT ID, post_content, meta_id FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_encloseme' LIMIT 1")) {
2457                 delete_metadata_by_mid( 'post', $enclosure->meta_id );
2458                 do_enclose( $enclosure->post_content, $enclosure->ID );
2459         }
2460
2461         // Do Trackbacks
2462         $trackbacks = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE to_ping <> '' AND post_status = 'publish'");
2463         if ( is_array($trackbacks) )
2464                 foreach ( $trackbacks as $trackback )
2465                         do_trackbacks($trackback);
2466
2467         //Do Update Services/Generic Pings
2468         generic_ping();
2469 }
2470
2471 /**
2472  * Perform trackbacks.
2473  *
2474  * @since 1.5.0
2475  *
2476  * @global wpdb $wpdb WordPress database abstraction object.
2477  *
2478  * @param int $post_id Post ID to do trackbacks on.
2479  */
2480 function do_trackbacks($post_id) {
2481         global $wpdb;
2482
2483         $post = get_post( $post_id );
2484         $to_ping = get_to_ping($post_id);
2485         $pinged  = get_pung($post_id);
2486         if ( empty($to_ping) ) {
2487                 $wpdb->update($wpdb->posts, array('to_ping' => ''), array('ID' => $post_id) );
2488                 return;
2489         }
2490
2491         if ( empty($post->post_excerpt) ) {
2492                 /** This filter is documented in wp-includes/post-template.php */
2493                 $excerpt = apply_filters( 'the_content', $post->post_content, $post->ID );
2494         } else {
2495                 /** This filter is documented in wp-includes/post-template.php */
2496                 $excerpt = apply_filters( 'the_excerpt', $post->post_excerpt );
2497         }
2498
2499         $excerpt = str_replace(']]>', ']]&gt;', $excerpt);
2500         $excerpt = wp_html_excerpt($excerpt, 252, '&#8230;');
2501
2502         /** This filter is documented in wp-includes/post-template.php */
2503         $post_title = apply_filters( 'the_title', $post->post_title, $post->ID );
2504         $post_title = strip_tags($post_title);
2505
2506         if ( $to_ping ) {
2507                 foreach ( (array) $to_ping as $tb_ping ) {
2508                         $tb_ping = trim($tb_ping);
2509                         if ( !in_array($tb_ping, $pinged) ) {
2510                                 trackback($tb_ping, $post_title, $excerpt, $post_id);
2511                                 $pinged[] = $tb_ping;
2512                         } else {
2513                                 $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $tb_ping, $post_id) );
2514                         }
2515                 }
2516         }
2517 }
2518
2519 /**
2520  * Sends pings to all of the ping site services.
2521  *
2522  * @since 1.2.0
2523  *
2524  * @param int $post_id Post ID.
2525  * @return int Same as Post ID from parameter
2526  */
2527 function generic_ping( $post_id = 0 ) {
2528         $services = get_option('ping_sites');
2529
2530         $services = explode("\n", $services);
2531         foreach ( (array) $services as $service ) {
2532                 $service = trim($service);
2533                 if ( '' != $service )
2534                         weblog_ping($service);
2535         }
2536
2537         return $post_id;
2538 }
2539
2540 /**
2541  * Pings back the links found in a post.
2542  *
2543  * @since 0.71
2544  * @uses $wp_version
2545  *
2546  * @param string $content Post content to check for links.
2547  * @param int $post_ID Post ID.
2548  */
2549 function pingback($content, $post_ID) {
2550         global $wp_version;
2551         include_once(ABSPATH . WPINC . '/class-IXR.php');
2552         include_once(ABSPATH . WPINC . '/class-wp-http-ixr-client.php');
2553
2554         // original code by Mort (http://mort.mine.nu:8080)
2555         $post_links = array();
2556
2557         $pung = get_pung($post_ID);
2558
2559         // Step 1
2560         // Parsing the post, external links (if any) are stored in the $post_links array
2561         $post_links_temp = wp_extract_urls( $content );
2562
2563         // Step 2.
2564         // Walking thru the links array
2565         // first we get rid of links pointing to sites, not to specific files
2566         // Example:
2567         // http://dummy-weblog.org
2568         // http://dummy-weblog.org/
2569         // http://dummy-weblog.org/post.php
2570         // We don't wanna ping first and second types, even if they have a valid <link/>
2571
2572         foreach ( (array) $post_links_temp as $link_test ) :
2573                 if ( !in_array($link_test, $pung) && (url_to_postid($link_test) != $post_ID) // If we haven't pung it already and it isn't a link to itself
2574                                 && !is_local_attachment($link_test) ) : // Also, let's never ping local attachments.
2575                         if ( $test = @parse_url($link_test) ) {
2576                                 if ( isset($test['query']) )
2577                                         $post_links[] = $link_test;
2578                                 elseif ( isset( $test['path'] ) && ( $test['path'] != '/' ) && ( $test['path'] != '' ) )
2579                                         $post_links[] = $link_test;
2580                         }
2581                 endif;
2582         endforeach;
2583
2584         $post_links = array_unique( $post_links );
2585         /**
2586          * Fires just before pinging back links found in a post.
2587          *
2588          * @since 2.0.0
2589          *
2590          * @param array &$post_links An array of post links to be checked, passed by reference.
2591          * @param array &$pung       Whether a link has already been pinged, passed by reference.
2592          * @param int   $post_ID     The post ID.
2593          */
2594         do_action_ref_array( 'pre_ping', array( &$post_links, &$pung, $post_ID ) );
2595
2596         foreach ( (array) $post_links as $pagelinkedto ) {
2597                 $pingback_server_url = discover_pingback_server_uri( $pagelinkedto );
2598
2599                 if ( $pingback_server_url ) {
2600                         @ set_time_limit( 60 );
2601                         // Now, the RPC call
2602                         $pagelinkedfrom = get_permalink($post_ID);
2603
2604                         // using a timeout of 3 seconds should be enough to cover slow servers
2605                         $client = new WP_HTTP_IXR_Client($pingback_server_url);
2606                         $client->timeout = 3;
2607                         /**
2608                          * Filter the user agent sent when pinging-back a URL.
2609                          *
2610                          * @since 2.9.0
2611                          *
2612                          * @param string $concat_useragent    The user agent concatenated with ' -- WordPress/'
2613                          *                                    and the WordPress version.
2614                          * @param string $useragent           The useragent.
2615                          * @param string $pingback_server_url The server URL being linked to.
2616                          * @param string $pagelinkedto        URL of page linked to.
2617                          * @param string $pagelinkedfrom      URL of page linked from.
2618                          */
2619                         $client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . $wp_version, $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom );
2620                         // when set to true, this outputs debug messages by itself
2621                         $client->debug = false;
2622
2623                         if ( $client->query('pingback.ping', $pagelinkedfrom, $pagelinkedto) || ( isset($client->error->code) && 48 == $client->error->code ) ) // Already registered
2624                                 add_ping( $post_ID, $pagelinkedto );
2625                 }
2626         }
2627 }
2628
2629 /**
2630  * Check whether blog is public before returning sites.
2631  *
2632  * @since 2.1.0
2633  *
2634  * @param mixed $sites Will return if blog is public, will not return if not public.
2635  * @return mixed Empty string if blog is not public, returns $sites, if site is public.
2636  */
2637 function privacy_ping_filter($sites) {
2638         if ( '0' != get_option('blog_public') )
2639                 return $sites;
2640         else
2641                 return '';
2642 }
2643
2644 /**
2645  * Send a Trackback.
2646  *
2647  * Updates database when sending trackback to prevent duplicates.
2648  *
2649  * @since 0.71
2650  *
2651  * @global wpdb $wpdb WordPress database abstraction object.
2652  *
2653  * @param string $trackback_url URL to send trackbacks.
2654  * @param string $title Title of post.
2655  * @param string $excerpt Excerpt of post.
2656  * @param int $ID Post ID.
2657  * @return mixed Database query from update.
2658  */
2659 function trackback($trackback_url, $title, $excerpt, $ID) {
2660         global $wpdb;
2661
2662         if ( empty($trackback_url) )
2663                 return;
2664
2665         $options = array();
2666         $options['timeout'] = 4;
2667         $options['body'] = array(
2668                 'title' => $title,
2669                 'url' => get_permalink($ID),
2670                 'blog_name' => get_option('blogname'),
2671                 'excerpt' => $excerpt
2672         );
2673
2674         $response = wp_safe_remote_post( $trackback_url, $options );
2675
2676         if ( is_wp_error( $response ) )
2677                 return;
2678
2679         $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $ID) );
2680         return $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $ID) );
2681 }
2682
2683 /**
2684  * Send a pingback.
2685  *
2686  * @since 1.2.0
2687  * @uses $wp_version
2688  *
2689  * @param string $server Host of blog to connect to.
2690  * @param string $path Path to send the ping.
2691  */
2692 function weblog_ping($server = '', $path = '') {
2693         global $wp_version;
2694         include_once(ABSPATH . WPINC . '/class-IXR.php');
2695         include_once(ABSPATH . WPINC . '/class-wp-http-ixr-client.php');
2696
2697         // using a timeout of 3 seconds should be enough to cover slow servers
2698         $client = new WP_HTTP_IXR_Client($server, ((!strlen(trim($path)) || ('/' == $path)) ? false : $path));
2699         $client->timeout = 3;
2700         $client->useragent .= ' -- WordPress/'.$wp_version;
2701
2702         // when set to true, this outputs debug messages by itself
2703         $client->debug = false;
2704         $home = trailingslashit( home_url() );
2705         if ( !$client->query('weblogUpdates.extendedPing', get_option('blogname'), $home, get_bloginfo('rss2_url') ) ) // then try a normal ping
2706                 $client->query('weblogUpdates.ping', get_option('blogname'), $home);
2707 }
2708
2709 /**
2710  * Default filter attached to pingback_ping_source_uri to validate the pingback's Source URI
2711  *
2712  * @since 3.5.1
2713  * @see wp_http_validate_url()
2714  *
2715  * @param string $source_uri
2716  * @return string
2717  */
2718 function pingback_ping_source_uri( $source_uri ) {
2719         return (string) wp_http_validate_url( $source_uri );
2720 }
2721
2722 /**
2723  * Default filter attached to xmlrpc_pingback_error.
2724  *
2725  * Returns a generic pingback error code unless the error code is 48,
2726  * which reports that the pingback is already registered.
2727  *
2728  * @since 3.5.1
2729  * @link http://www.hixie.ch/specs/pingback/pingback#TOC3
2730  *
2731  * @param IXR_Error $ixr_error
2732  * @return IXR_Error
2733  */
2734 function xmlrpc_pingback_error( $ixr_error ) {
2735         if ( $ixr_error->code === 48 )
2736                 return $ixr_error;
2737         return new IXR_Error( 0, '' );
2738 }
2739
2740 //
2741 // Cache
2742 //
2743
2744 /**
2745  * Removes comment ID from the comment cache.
2746  *
2747  * @since 2.3.0
2748  *
2749  * @param int|array $ids Comment ID or array of comment IDs to remove from cache
2750  */
2751 function clean_comment_cache($ids) {
2752         foreach ( (array) $ids as $id )
2753                 wp_cache_delete($id, 'comment');
2754
2755         wp_cache_set( 'last_changed', microtime(), 'comment' );
2756 }
2757
2758 /**
2759  * Updates the comment cache of given comments.
2760  *
2761  * Will add the comments in $comments to the cache. If comment ID already exists
2762  * in the comment cache then it will not be updated. The comment is added to the
2763  * cache using the comment group with the key using the ID of the comments.
2764  *
2765  * @since 2.3.0
2766  *
2767  * @param array $comments Array of comment row objects
2768  */
2769 function update_comment_cache($comments) {
2770         foreach ( (array) $comments as $comment )
2771                 wp_cache_add($comment->comment_ID, $comment, 'comment');
2772 }
2773
2774 //
2775 // Internal
2776 //
2777
2778 /**
2779  * Close comments on old posts on the fly, without any extra DB queries. Hooked to the_posts.
2780  *
2781  * @access private
2782  * @since 2.7.0
2783  *
2784  * @param object $posts Post data object.
2785  * @param object $query Query object.
2786  * @return object
2787  */
2788 function _close_comments_for_old_posts( $posts, $query ) {
2789         if ( empty( $posts ) || ! $query->is_singular() || ! get_option( 'close_comments_for_old_posts' ) )
2790                 return $posts;
2791
2792         /**
2793          * Filter the list of post types to automatically close comments for.
2794          *
2795          * @since 3.2.0
2796          *
2797          * @param array $post_types An array of registered post types. Default array with 'post'.
2798          */
2799         $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
2800         if ( ! in_array( $posts[0]->post_type, $post_types ) )
2801                 return $posts;
2802
2803         $days_old = (int) get_option( 'close_comments_days_old' );
2804         if ( ! $days_old )
2805                 return $posts;
2806
2807         if ( time() - strtotime( $posts[0]->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) {
2808                 $posts[0]->comment_status = 'closed';
2809                 $posts[0]->ping_status = 'closed';
2810         }
2811
2812         return $posts;
2813 }
2814
2815 /**
2816  * Close comments on an old post. Hooked to comments_open and pings_open.
2817  *
2818  * @access private
2819  * @since 2.7.0
2820  *
2821  * @param bool $open Comments open or closed
2822  * @param int $post_id Post ID
2823  * @return bool $open
2824  */
2825 function _close_comments_for_old_post( $open, $post_id ) {
2826         if ( ! $open )
2827                 return $open;
2828
2829         if ( !get_option('close_comments_for_old_posts') )
2830                 return $open;
2831
2832         $days_old = (int) get_option('close_comments_days_old');
2833         if ( !$days_old )
2834                 return $open;
2835
2836         $post = get_post($post_id);
2837
2838         /** This filter is documented in wp-includes/comment.php */
2839         $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
2840         if ( ! in_array( $post->post_type, $post_types ) )
2841                 return $open;
2842
2843         if ( time() - strtotime( $post->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) )
2844                 return false;
2845
2846         return $open;
2847 }