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