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