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