Wordpress 3.0.4
[autoinstalls/wordpress.git] / wp-includes / comment.php
1 <?php
2 /**
3  * Manages WordPress comments
4  *
5  * @package WordPress
6  * @subpackage Comment
7  */
8
9 /**
10  * Checks whether a comment passes internal checks to be allowed to add.
11  *
12  * If comment moderation is set in the administration, then all comments,
13  * regardless of their type and whitelist will be set to false. If the number of
14  * links exceeds the amount in the administration, then the check fails. If any
15  * of the parameter contents match the blacklist of words, then the check fails.
16  *
17  * If the number of links exceeds the amount in the administration, then the
18  * check fails. If any of the parameter contents match the blacklist of words,
19  * then the check fails.
20  *
21  * If the comment author was approved before, then the comment is
22  * automatically whitelisted.
23  *
24  * If none of the checks fail, then the failback is to set the check to pass
25  * (return true).
26  *
27  * @since 1.2.0
28  * @uses $wpdb
29  *
30  * @param string $author Comment Author's name
31  * @param string $email Comment Author's email
32  * @param string $url Comment Author's URL
33  * @param string $comment Comment contents
34  * @param string $user_ip Comment Author's IP address
35  * @param string $user_agent Comment Author's User Agent
36  * @param string $comment_type Comment type, either user submitted comment,
37  *              trackback, or pingback
38  * @return bool Whether the checks passed (true) and the comments should be
39  *              displayed or set to moderated
40  */
41 function check_comment($author, $email, $url, $comment, $user_ip, $user_agent, $comment_type) {
42         global $wpdb;
43
44         if ( 1 == get_option('comment_moderation') )
45                 return false; // If moderation is set to manual
46
47         // Check # of external links
48         if ( $max_links = get_option( 'comment_max_links' ) ) {
49                 $num_links = preg_match_all( '/<a [^>]*href/i', apply_filters( 'comment_text', $comment ), $out );
50                 $num_links = apply_filters( 'comment_max_links_url', $num_links, $url ); // provide for counting of $url as a link
51                 if ( $num_links >= $max_links )
52                         return false;
53         }
54
55         $mod_keys = trim(get_option('moderation_keys'));
56         if ( !empty($mod_keys) ) {
57                 $words = explode("\n", $mod_keys );
58
59                 foreach ( (array) $words as $word) {
60                         $word = trim($word);
61
62                         // Skip empty lines
63                         if ( empty($word) )
64                                 continue;
65
66                         // Do some escaping magic so that '#' chars in the
67                         // spam words don't break things:
68                         $word = preg_quote($word, '#');
69
70                         $pattern = "#$word#i";
71                         if ( preg_match($pattern, $author) ) return false;
72                         if ( preg_match($pattern, $email) ) return false;
73                         if ( preg_match($pattern, $url) ) return false;
74                         if ( preg_match($pattern, $comment) ) return false;
75                         if ( preg_match($pattern, $user_ip) ) return false;
76                         if ( preg_match($pattern, $user_agent) ) return false;
77                 }
78         }
79
80         // Comment whitelisting:
81         if ( 1 == get_option('comment_whitelist')) {
82                 if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
83                         // expected_slashed ($author, $email)
84                         $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");
85                         if ( ( 1 == $ok_to_comment ) &&
86                                 ( empty($mod_keys) || false === strpos( $email, $mod_keys) ) )
87                                         return true;
88                         else
89                                 return false;
90                 } else {
91                         return false;
92                 }
93         }
94         return true;
95 }
96
97 /**
98  * Retrieve the approved comments for post $post_id.
99  *
100  * @since 2.0.0
101  * @uses $wpdb
102  *
103  * @param int $post_id The ID of the post
104  * @return array $comments The approved comments
105  */
106 function get_approved_comments($post_id) {
107         global $wpdb;
108         return $wpdb->get_results($wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' ORDER BY comment_date", $post_id));
109 }
110
111 /**
112  * Retrieves comment data given a comment ID or comment object.
113  *
114  * If an object is passed then the comment data will be cached and then returned
115  * after being passed through a filter. If the comment is empty, then the global
116  * comment variable will be used, if it is set.
117  *
118  * If the comment is empty, then the global comment variable will be used, if it
119  * is set.
120  *
121  * @since 2.0.0
122  * @uses $wpdb
123  *
124  * @param object|string|int $comment Comment to retrieve.
125  * @param string $output Optional. OBJECT or ARRAY_A or ARRAY_N constants.
126  * @return object|array|null Depends on $output value.
127  */
128 function &get_comment(&$comment, $output = OBJECT) {
129         global $wpdb;
130         $null = null;
131
132         if ( empty($comment) ) {
133                 if ( isset($GLOBALS['comment']) )
134                         $_comment = & $GLOBALS['comment'];
135                 else
136                         $_comment = null;
137         } elseif ( is_object($comment) ) {
138                 wp_cache_add($comment->comment_ID, $comment, 'comment');
139                 $_comment = $comment;
140         } else {
141                 if ( isset($GLOBALS['comment']) && ($GLOBALS['comment']->comment_ID == $comment) ) {
142                         $_comment = & $GLOBALS['comment'];
143                 } elseif ( ! $_comment = wp_cache_get($comment, 'comment') ) {
144                         $_comment = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment));
145                         if ( ! $_comment )
146                                 return $null;
147                         wp_cache_add($_comment->comment_ID, $_comment, 'comment');
148                 }
149         }
150
151         $_comment = apply_filters('get_comment', $_comment);
152
153         if ( $output == OBJECT ) {
154                 return $_comment;
155         } elseif ( $output == ARRAY_A ) {
156                 $__comment = get_object_vars($_comment);
157                 return $__comment;
158         } elseif ( $output == ARRAY_N ) {
159                 $__comment = array_values(get_object_vars($_comment));
160                 return $__comment;
161         } else {
162                 return $_comment;
163         }
164 }
165
166 /**
167  * Retrieve a list of comments.
168  *
169  * The comment list can be for the blog as a whole or for an individual post.
170  *
171  * The list of comment arguments are 'status', 'orderby', 'comment_date_gmt',
172  * 'order', 'number', 'offset', and 'post_id'.
173  *
174  * @since 2.7.0
175  * @uses $wpdb
176  *
177  * @param mixed $args Optional. Array or string of options to override defaults.
178  * @return array List of comments.
179  */
180 function get_comments( $args = '' ) {
181         global $wpdb;
182
183         $defaults = array(
184                 'author_email' => '',
185                 'ID' => '',
186                 'karma' => '',
187                 'number' => '',
188                 'offset' => '',
189                 'orderby' => '',
190                 'order' => 'DESC',
191                 'parent' => '',
192                 'post_ID' => '',
193                 'post_id' => 0,
194                 'status' => '',
195                 'type' => '',
196                 'user_id' => '',
197         );
198
199         $args = wp_parse_args( $args, $defaults );
200         extract( $args, EXTR_SKIP );
201
202         // $args can be whatever, only use the args defined in defaults to compute the key
203         $key = md5( serialize( compact(array_keys($defaults)) )  );
204         $last_changed = wp_cache_get('last_changed', 'comment');
205         if ( !$last_changed ) {
206                 $last_changed = time();
207                 wp_cache_set('last_changed', $last_changed, 'comment');
208         }
209         $cache_key = "get_comments:$key:$last_changed";
210
211         if ( $cache = wp_cache_get( $cache_key, 'comment' ) ) {
212                 return $cache;
213         }
214
215         $post_id = absint($post_id);
216
217         if ( 'hold' == $status )
218                 $approved = "comment_approved = '0'";
219         elseif ( 'approve' == $status )
220                 $approved = "comment_approved = '1'";
221         elseif ( 'spam' == $status )
222                 $approved = "comment_approved = 'spam'";
223         elseif ( 'trash' == $status )
224                 $approved = "comment_approved = 'trash'";
225         else
226                 $approved = "( comment_approved = '0' OR comment_approved = '1' )";
227
228         $order = ( 'ASC' == $order ) ? 'ASC' : 'DESC';
229
230         if ( ! empty( $orderby ) ) {
231                 $ordersby = is_array($orderby) ? $orderby : preg_split('/[,\s]/', $orderby);
232                 $ordersby = array_intersect(
233                         $ordersby,
234                         array(
235                                 'comment_agent',
236                                 'comment_approved',
237                                 'comment_author',
238                                 'comment_author_email',
239                                 'comment_author_IP',
240                                 'comment_author_url',
241                                 'comment_content',
242                                 'comment_date',
243                                 'comment_date_gmt',
244                                 'comment_ID',
245                                 'comment_karma',
246                                 'comment_parent',
247                                 'comment_post_ID',
248                                 'comment_type',
249                                 'user_id',
250                         )
251                 );
252                 $orderby = empty( $ordersby ) ? 'comment_date_gmt' : implode(', ', $ordersby);
253         } else {
254                 $orderby = 'comment_date_gmt';
255         }
256
257         $number = absint($number);
258         $offset = absint($offset);
259
260         if ( !empty($number) ) {
261                 if ( $offset )
262                         $number = 'LIMIT ' . $offset . ',' . $number;
263                 else
264                         $number = 'LIMIT ' . $number;
265
266         } else {
267                 $number = '';
268         }
269
270         $post_where = '';
271
272         if ( ! empty($post_id) )
273                 $post_where .= $wpdb->prepare( 'comment_post_ID = %d AND ', $post_id );
274         if ( '' !== $author_email )
275                 $post_where .= $wpdb->prepare( 'comment_author_email = %s AND ', $author_email );
276         if ( '' !== $karma )
277                 $post_where .= $wpdb->prepare( 'comment_karma = %d AND ', $karma );
278         if ( 'comment' == $type )
279                 $post_where .= "comment_type = '' AND ";
280         elseif ( ! empty( $type ) )
281                 $post_where .= $wpdb->prepare( 'comment_type = %s AND ', $type );
282         if ( '' !== $parent )
283                 $post_where .= $wpdb->prepare( 'comment_parent = %d AND ', $parent );
284         if ( '' !== $user_id )
285                 $post_where .= $wpdb->prepare( 'user_id = %d AND ', $user_id );
286
287         $comments = $wpdb->get_results( "SELECT * FROM $wpdb->comments WHERE $post_where $approved ORDER BY $orderby $order $number" );
288         wp_cache_add( $cache_key, $comments, 'comment' );
289
290         return $comments;
291 }
292
293 /**
294  * Retrieve all of the WordPress supported comment statuses.
295  *
296  * Comments have a limited set of valid status values, this provides the comment
297  * status values and descriptions.
298  *
299  * @package WordPress
300  * @subpackage Post
301  * @since 2.7.0
302  *
303  * @return array List of comment statuses.
304  */
305 function get_comment_statuses( ) {
306         $status = array(
307                 'hold'          => __('Unapproved'),
308                 /* translators: comment status  */
309                 'approve'       => _x('Approved', 'adjective'),
310                 /* translators: comment status */
311                 'spam'          => _x('Spam', 'adjective'),
312         );
313
314         return $status;
315 }
316
317
318 /**
319  * The date the last comment was modified.
320  *
321  * @since 1.5.0
322  * @uses $wpdb
323  * @global array $cache_lastcommentmodified
324  *
325  * @param string $timezone Which timezone to use in reference to 'gmt', 'blog',
326  *              or 'server' locations.
327  * @return string Last comment modified date.
328  */
329 function get_lastcommentmodified($timezone = 'server') {
330         global $cache_lastcommentmodified, $wpdb;
331
332         if ( isset($cache_lastcommentmodified[$timezone]) )
333                 return $cache_lastcommentmodified[$timezone];
334
335         $add_seconds_server = date('Z');
336
337         switch ( strtolower($timezone)) {
338                 case 'gmt':
339                         $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
340                         break;
341                 case 'blog':
342                         $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
343                         break;
344                 case 'server':
345                         $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));
346                         break;
347         }
348
349         $cache_lastcommentmodified[$timezone] = $lastcommentmodified;
350
351         return $lastcommentmodified;
352 }
353
354 /**
355  * The amount of comments in a post or total comments.
356  *
357  * A lot like {@link wp_count_comments()}, in that they both return comment
358  * stats (albeit with different types). The {@link wp_count_comments()} actual
359  * caches, but this function does not.
360  *
361  * @since 2.0.0
362  * @uses $wpdb
363  *
364  * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide.
365  * @return array The amount of spam, approved, awaiting moderation, and total comments.
366  */
367 function get_comment_count( $post_id = 0 ) {
368         global $wpdb;
369
370         $post_id = (int) $post_id;
371
372         $where = '';
373         if ( $post_id > 0 ) {
374                 $where = $wpdb->prepare("WHERE comment_post_ID = %d", $post_id);
375         }
376
377         $totals = (array) $wpdb->get_results("
378                 SELECT comment_approved, COUNT( * ) AS total
379                 FROM {$wpdb->comments}
380                 {$where}
381                 GROUP BY comment_approved
382         ", ARRAY_A);
383
384         $comment_count = array(
385                 "approved"              => 0,
386                 "awaiting_moderation"   => 0,
387                 "spam"                  => 0,
388                 "total_comments"        => 0
389         );
390
391         foreach ( $totals as $row ) {
392                 switch ( $row['comment_approved'] ) {
393                         case 'spam':
394                                 $comment_count['spam'] = $row['total'];
395                                 $comment_count["total_comments"] += $row['total'];
396                                 break;
397                         case 1:
398                                 $comment_count['approved'] = $row['total'];
399                                 $comment_count['total_comments'] += $row['total'];
400                                 break;
401                         case 0:
402                                 $comment_count['awaiting_moderation'] = $row['total'];
403                                 $comment_count['total_comments'] += $row['total'];
404                                 break;
405                         default:
406                                 break;
407                 }
408         }
409
410         return $comment_count;
411 }
412
413 //
414 // Comment meta functions
415 //
416
417 /**
418  * Add meta data field to a comment.
419  *
420  * @since 2.9.0
421  * @uses add_metadata
422  * @link http://codex.wordpress.org/Function_Reference/add_comment_meta
423  *
424  * @param int $comment_id Comment ID.
425  * @param string $key Metadata name.
426  * @param mixed $value Metadata value.
427  * @param bool $unique Optional, default is false. Whether the same key should not be added.
428  * @return bool False for failure. True for success.
429  */
430 function add_comment_meta($comment_id, $meta_key, $meta_value, $unique = false) {
431         return add_metadata('comment', $comment_id, $meta_key, $meta_value, $unique);
432 }
433
434 /**
435  * Remove metadata matching criteria from a comment.
436  *
437  * You can match based on the key, or key and value. Removing based on key and
438  * value, will keep from removing duplicate metadata with the same key. It also
439  * allows removing all metadata matching key, if needed.
440  *
441  * @since 2.9.0
442  * @uses delete_metadata
443  * @link http://codex.wordpress.org/Function_Reference/delete_comment_meta
444  *
445  * @param int $comment_id comment ID
446  * @param string $meta_key Metadata name.
447  * @param mixed $meta_value Optional. Metadata value.
448  * @return bool False for failure. True for success.
449  */
450 function delete_comment_meta($comment_id, $meta_key, $meta_value = '') {
451         return delete_metadata('comment', $comment_id, $meta_key, $meta_value);
452 }
453
454 /**
455  * Retrieve comment meta field for a comment.
456  *
457  * @since 2.9.0
458  * @uses get_metadata
459  * @link http://codex.wordpress.org/Function_Reference/get_comment_meta
460  *
461  * @param int $comment_id Comment ID.
462  * @param string $key The meta key to retrieve.
463  * @param bool $single Whether to return a single value.
464  * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
465  *  is true.
466  */
467 function get_comment_meta($comment_id, $key, $single = false) {
468         return get_metadata('comment', $comment_id, $key, $single);
469 }
470
471 /**
472  * Update comment meta field based on comment ID.
473  *
474  * Use the $prev_value parameter to differentiate between meta fields with the
475  * same key and comment ID.
476  *
477  * If the meta field for the comment does not exist, it will be added.
478  *
479  * @since 2.9.0
480  * @uses update_metadata
481  * @link http://codex.wordpress.org/Function_Reference/update_comment_meta
482  *
483  * @param int $comment_id Comment ID.
484  * @param string $key Metadata key.
485  * @param mixed $value Metadata value.
486  * @param mixed $prev_value Optional. Previous value to check before removing.
487  * @return bool False on failure, true if success.
488  */
489 function update_comment_meta($comment_id, $meta_key, $meta_value, $prev_value = '') {
490         return update_metadata('comment', $comment_id, $meta_key, $meta_value, $prev_value);
491 }
492
493 /**
494  * Sanitizes the cookies sent to the user already.
495  *
496  * Will only do anything if the cookies have already been created for the user.
497  * Mostly used after cookies had been sent to use elsewhere.
498  *
499  * @since 2.0.4
500  */
501 function sanitize_comment_cookies() {
502         if ( isset($_COOKIE['comment_author_'.COOKIEHASH]) ) {
503                 $comment_author = apply_filters('pre_comment_author_name', $_COOKIE['comment_author_'.COOKIEHASH]);
504                 $comment_author = stripslashes($comment_author);
505                 $comment_author = esc_attr($comment_author);
506                 $_COOKIE['comment_author_'.COOKIEHASH] = $comment_author;
507         }
508
509         if ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) ) {
510                 $comment_author_email = apply_filters('pre_comment_author_email', $_COOKIE['comment_author_email_'.COOKIEHASH]);
511                 $comment_author_email = stripslashes($comment_author_email);
512                 $comment_author_email = esc_attr($comment_author_email);
513                 $_COOKIE['comment_author_email_'.COOKIEHASH] = $comment_author_email;
514         }
515
516         if ( isset($_COOKIE['comment_author_url_'.COOKIEHASH]) ) {
517                 $comment_author_url = apply_filters('pre_comment_author_url', $_COOKIE['comment_author_url_'.COOKIEHASH]);
518                 $comment_author_url = stripslashes($comment_author_url);
519                 $_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
520         }
521 }
522
523 /**
524  * Validates whether this comment is allowed to be made.
525  *
526  * @since 2.0.0
527  * @uses $wpdb
528  * @uses apply_filters() Calls 'pre_comment_approved' hook on the type of comment
529  * @uses apply_filters() Calls 'comment_duplicate_trigger' hook on commentdata.
530  * @uses do_action() Calls 'check_comment_flood' hook on $comment_author_IP, $comment_author_email, and $comment_date_gmt
531  *
532  * @param array $commentdata Contains information on the comment
533  * @return mixed Signifies the approval status (0|1|'spam')
534  */
535 function wp_allow_comment($commentdata) {
536         global $wpdb;
537         extract($commentdata, EXTR_SKIP);
538
539         // Simple duplicate check
540         // expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
541         $dupe = "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = '$comment_post_ID' AND comment_approved != 'trash' AND ( comment_author = '$comment_author' ";
542         if ( $comment_author_email )
543                 $dupe .= "OR comment_author_email = '$comment_author_email' ";
544         $dupe .= ") AND comment_content = '$comment_content' LIMIT 1";
545         if ( $wpdb->get_var($dupe) ) {
546                 do_action( 'comment_duplicate_trigger', $commentdata );
547                 if ( defined('DOING_AJAX') )
548                         die( __('Duplicate comment detected; it looks as though you&#8217;ve already said that!') );
549
550                 wp_die( __('Duplicate comment detected; it looks as though you&#8217;ve already said that!') );
551         }
552
553         do_action( 'check_comment_flood', $comment_author_IP, $comment_author_email, $comment_date_gmt );
554
555         if ( isset($user_id) && $user_id) {
556                 $userdata = get_userdata($user_id);
557                 $user = new WP_User($user_id);
558                 $post_author = $wpdb->get_var($wpdb->prepare("SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1", $comment_post_ID));
559         }
560
561         if ( isset($userdata) && ( $user_id == $post_author || $user->has_cap('moderate_comments') ) ) {
562                 // The author and the admins get respect.
563                 $approved = 1;
564          } else {
565                 // Everyone else's comments will be checked.
566                 if ( check_comment($comment_author, $comment_author_email, $comment_author_url, $comment_content, $comment_author_IP, $comment_agent, $comment_type) )
567                         $approved = 1;
568                 else
569                         $approved = 0;
570                 if ( wp_blacklist_check($comment_author, $comment_author_email, $comment_author_url, $comment_content, $comment_author_IP, $comment_agent) )
571                         $approved = 'spam';
572         }
573
574         $approved = apply_filters('pre_comment_approved', $approved);
575         return $approved;
576 }
577
578 /**
579  * Check whether comment flooding is occurring.
580  *
581  * Won't run, if current user can manage options, so to not block
582  * administrators.
583  *
584  * @since 2.3.0
585  * @uses $wpdb
586  * @uses apply_filters() Calls 'comment_flood_filter' filter with first
587  *              parameter false, last comment timestamp, new comment timestamp.
588  * @uses do_action() Calls 'comment_flood_trigger' action with parameters with
589  *              last comment timestamp and new comment timestamp.
590  *
591  * @param string $ip Comment IP.
592  * @param string $email Comment author email address.
593  * @param string $date MySQL time string.
594  */
595 function check_comment_flood_db( $ip, $email, $date ) {
596         global $wpdb;
597         if ( current_user_can( 'manage_options' ) )
598                 return; // don't throttle admins
599         $hour_ago = gmdate( 'Y-m-d H:i:s', time() - 3600 );
600         if ( $lasttime = $wpdb->get_var( $wpdb->prepare( "SELECT `comment_date_gmt` FROM `$wpdb->comments` WHERE `comment_date_gmt` >= %s AND ( `comment_author_IP` = %s OR `comment_author_email` = %s ) ORDER BY `comment_date_gmt` DESC LIMIT 1", $hour_ago, $ip, $email ) ) ) {
601                 $time_lastcomment = mysql2date('U', $lasttime, false);
602                 $time_newcomment  = mysql2date('U', $date, false);
603                 $flood_die = apply_filters('comment_flood_filter', false, $time_lastcomment, $time_newcomment);
604                 if ( $flood_die ) {
605                         do_action('comment_flood_trigger', $time_lastcomment, $time_newcomment);
606
607                         if ( defined('DOING_AJAX') )
608                                 die( __('You are posting comments too quickly.  Slow down.') );
609
610                         wp_die( __('You are posting comments too quickly.  Slow down.'), '', array('response' => 403) );
611                 }
612         }
613 }
614
615 /**
616  * Separates an array of comments into an array keyed by comment_type.
617  *
618  * @since 2.7.0
619  *
620  * @param array $comments Array of comments
621  * @return array Array of comments keyed by comment_type.
622  */
623 function &separate_comments(&$comments) {
624         $comments_by_type = array('comment' => array(), 'trackback' => array(), 'pingback' => array(), 'pings' => array());
625         $count = count($comments);
626         for ( $i = 0; $i < $count; $i++ ) {
627                 $type = $comments[$i]->comment_type;
628                 if ( empty($type) )
629                         $type = 'comment';
630                 $comments_by_type[$type][] = &$comments[$i];
631                 if ( 'trackback' == $type || 'pingback' == $type )
632                         $comments_by_type['pings'][] = &$comments[$i];
633         }
634
635         return $comments_by_type;
636 }
637
638 /**
639  * Calculate the total number of comment pages.
640  *
641  * @since 2.7.0
642  * @uses get_query_var() Used to fill in the default for $per_page parameter.
643  * @uses get_option() Used to fill in defaults for parameters.
644  * @uses Walker_Comment
645  *
646  * @param array $comments Optional array of comment objects.  Defaults to $wp_query->comments
647  * @param int $per_page Optional comments per page.
648  * @param boolean $threaded Optional control over flat or threaded comments.
649  * @return int Number of comment pages.
650  */
651 function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
652         global $wp_query;
653
654         if ( null === $comments && null === $per_page && null === $threaded && !empty($wp_query->max_num_comment_pages) )
655                 return $wp_query->max_num_comment_pages;
656
657         if ( !$comments || !is_array($comments) )
658                 $comments = $wp_query->comments;
659
660         if ( empty($comments) )
661                 return 0;
662
663         if ( !isset($per_page) )
664                 $per_page = (int) get_query_var('comments_per_page');
665         if ( 0 === $per_page )
666                 $per_page = (int) get_option('comments_per_page');
667         if ( 0 === $per_page )
668                 return 1;
669
670         if ( !isset($threaded) )
671                 $threaded = get_option('thread_comments');
672
673         if ( $threaded ) {
674                 $walker = new Walker_Comment;
675                 $count = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
676         } else {
677                 $count = ceil( count( $comments ) / $per_page );
678         }
679
680         return $count;
681 }
682
683 /**
684  * Calculate what page number a comment will appear on for comment paging.
685  *
686  * @since 2.7.0
687  * @uses get_comment() Gets the full comment of the $comment_ID parameter.
688  * @uses get_option() Get various settings to control function and defaults.
689  * @uses get_page_of_comment() Used to loop up to top level comment.
690  *
691  * @param int $comment_ID Comment ID.
692  * @param array $args Optional args.
693  * @return int|null Comment page number or null on error.
694  */
695 function get_page_of_comment( $comment_ID, $args = array() ) {
696         global $wpdb;
697
698         if ( !$comment = get_comment( $comment_ID ) )
699                 return;
700
701         $defaults = array( 'type' => 'all', 'page' => '', 'per_page' => '', 'max_depth' => '' );
702         $args = wp_parse_args( $args, $defaults );
703
704         if ( '' === $args['per_page'] && get_option('page_comments') )
705                 $args['per_page'] = get_query_var('comments_per_page');
706         if ( empty($args['per_page']) ) {
707                 $args['per_page'] = 0;
708                 $args['page'] = 0;
709         }
710         if ( $args['per_page'] < 1 )
711                 return 1;
712
713         if ( '' === $args['max_depth'] ) {
714                 if ( get_option('thread_comments') )
715                         $args['max_depth'] = get_option('thread_comments_depth');
716                 else
717                         $args['max_depth'] = -1;
718         }
719
720         // Find this comment's top level parent if threading is enabled
721         if ( $args['max_depth'] > 1 && 0 != $comment->comment_parent )
722                 return get_page_of_comment( $comment->comment_parent, $args );
723
724         $allowedtypes = array(
725                 'comment' => '',
726                 'pingback' => 'pingback',
727                 'trackback' => 'trackback',
728         );
729
730         $comtypewhere = ( 'all' != $args['type'] && isset($allowedtypes[$args['type']]) ) ? " AND comment_type = '" . $allowedtypes[$args['type']] . "'" : '';
731
732         // Count comments older than this one
733         $oldercoms = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = 0 AND comment_approved = '1' AND comment_date_gmt < '%s'" . $comtypewhere, $comment->comment_post_ID, $comment->comment_date_gmt ) );
734
735         // No older comments? Then it's page #1.
736         if ( 0 == $oldercoms )
737                 return 1;
738
739         // Divide comments older than this one by comments per page to get this comment's page number
740         return ceil( ( $oldercoms + 1 ) / $args['per_page'] );
741 }
742
743 /**
744  * Does comment contain blacklisted characters or words.
745  *
746  * @since 1.5.0
747  * @uses do_action() Calls 'wp_blacklist_check' hook for all parameters.
748  *
749  * @param string $author The author of the comment
750  * @param string $email The email of the comment
751  * @param string $url The url used in the comment
752  * @param string $comment The comment content
753  * @param string $user_ip The comment author IP address
754  * @param string $user_agent The author's browser user agent
755  * @return bool True if comment contains blacklisted content, false if comment does not
756  */
757 function wp_blacklist_check($author, $email, $url, $comment, $user_ip, $user_agent) {
758         do_action('wp_blacklist_check', $author, $email, $url, $comment, $user_ip, $user_agent);
759
760         $mod_keys = trim( get_option('blacklist_keys') );
761         if ( '' == $mod_keys )
762                 return false; // If moderation keys are empty
763         $words = explode("\n", $mod_keys );
764
765         foreach ( (array) $words as $word ) {
766                 $word = trim($word);
767
768                 // Skip empty lines
769                 if ( empty($word) ) { continue; }
770
771                 // Do some escaping magic so that '#' chars in the
772                 // spam words don't break things:
773                 $word = preg_quote($word, '#');
774
775                 $pattern = "#$word#i";
776                 if (
777                            preg_match($pattern, $author)
778                         || preg_match($pattern, $email)
779                         || preg_match($pattern, $url)
780                         || preg_match($pattern, $comment)
781                         || preg_match($pattern, $user_ip)
782                         || preg_match($pattern, $user_agent)
783                  )
784                         return true;
785         }
786         return false;
787 }
788
789 /**
790  * Retrieve total comments for blog or single post.
791  *
792  * The properties of the returned object contain the 'moderated', 'approved',
793  * and spam comments for either the entire blog or single post. Those properties
794  * contain the amount of comments that match the status. The 'total_comments'
795  * property contains the integer of total comments.
796  *
797  * The comment stats are cached and then retrieved, if they already exist in the
798  * cache.
799  *
800  * @since 2.5.0
801  *
802  * @param int $post_id Optional. Post ID.
803  * @return object Comment stats.
804  */
805 function wp_count_comments( $post_id = 0 ) {
806         global $wpdb;
807
808         $post_id = (int) $post_id;
809
810         $stats = apply_filters('wp_count_comments', array(), $post_id);
811         if ( !empty($stats) )
812                 return $stats;
813
814         $count = wp_cache_get("comments-{$post_id}", 'counts');
815
816         if ( false !== $count )
817                 return $count;
818
819         $where = '';
820         if ( $post_id > 0 )
821                 $where = $wpdb->prepare( "WHERE comment_post_ID = %d", $post_id );
822
823         $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} {$where} GROUP BY comment_approved", ARRAY_A );
824
825         $total = 0;
826         $approved = array('0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed');
827         $known_types = array_keys( $approved );
828         foreach ( (array) $count as $row ) {
829                 // Don't count post-trashed toward totals
830                 if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] )
831                         $total += $row['num_comments'];
832                 if ( in_array( $row['comment_approved'], $known_types ) )
833                         $stats[$approved[$row['comment_approved']]] = $row['num_comments'];
834         }
835
836         $stats['total_comments'] = $total;
837         foreach ( $approved as $key ) {
838                 if ( empty($stats[$key]) )
839                         $stats[$key] = 0;
840         }
841
842         $stats = (object) $stats;
843         wp_cache_set("comments-{$post_id}", $stats, 'counts');
844
845         return $stats;
846 }
847
848 /**
849  * Trashes or deletes a comment.
850  *
851  * The comment is moved to trash instead of permanently deleted unless trash is
852  * disabled, item is already in the trash, or $force_delete is true.
853  *
854  * The post comment count will be updated if the comment was approved and has a
855  * post ID available.
856  *
857  * @since 2.0.0
858  * @uses $wpdb
859  * @uses do_action() Calls 'delete_comment' hook on comment ID
860  * @uses do_action() Calls 'deleted_comment' hook on comment ID after deletion, on success
861  * @uses do_action() Calls 'wp_set_comment_status' hook on comment ID with 'delete' set for the second parameter
862  * @uses wp_transition_comment_status() Passes new and old comment status along with $comment object
863  *
864  * @param int $comment_id Comment ID
865  * @param bool $force_delete Whether to bypass trash and force deletion. Default is false.
866  * @return bool False if delete comment query failure, true on success.
867  */
868 function wp_delete_comment($comment_id, $force_delete = false) {
869         global $wpdb;
870         if (!$comment = get_comment($comment_id))
871                 return false;
872
873         if ( !$force_delete && EMPTY_TRASH_DAYS && !in_array( wp_get_comment_status($comment_id), array( 'trash', 'spam' ) ) )
874                 return wp_trash_comment($comment_id);
875
876         do_action('delete_comment', $comment_id);
877
878         // Move children up a level.
879         $children = $wpdb->get_col( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment_id) );
880         if ( !empty($children) ) {
881                 $wpdb->update($wpdb->comments, array('comment_parent' => $comment->comment_parent), array('comment_parent' => $comment_id));
882                 clean_comment_cache($children);
883         }
884
885         // Delete metadata
886         $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id = %d ", $comment_id ) );
887         if ( !empty($meta_ids) ) {
888                 do_action( 'delete_commentmeta', $meta_ids );
889                 $in_meta_ids = "'" . implode("', '", $meta_ids) . "'";
890                 $wpdb->query( "DELETE FROM $wpdb->commentmeta WHERE meta_id IN ($in_meta_ids)" );
891                 do_action( 'deleted_commentmeta', $meta_ids );
892         }
893
894         if ( ! $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment_id) ) )
895                 return false;
896         do_action('deleted_comment', $comment_id);
897
898         $post_id = $comment->comment_post_ID;
899         if ( $post_id && $comment->comment_approved == 1 )
900                 wp_update_comment_count($post_id);
901
902         clean_comment_cache($comment_id);
903
904         do_action('wp_set_comment_status', $comment_id, 'delete');
905         wp_transition_comment_status('delete', $comment->comment_approved, $comment);
906         return true;
907 }
908
909 /**
910  * Moves a comment to the Trash
911  *
912  * If trash is disabled, comment is permanently deleted.
913  *
914  * @since 2.9.0
915  * @uses do_action() on 'trash_comment' before trashing
916  * @uses do_action() on 'trashed_comment' after trashing
917  * @uses wp_delete_comment() if trash is disabled
918  *
919  * @param int $comment_id Comment ID.
920  * @return mixed False on failure
921  */
922 function wp_trash_comment($comment_id) {
923         if ( !EMPTY_TRASH_DAYS )
924                 return wp_delete_comment($comment_id, true);
925
926         if ( !$comment = get_comment($comment_id) )
927                 return false;
928
929         do_action('trash_comment', $comment_id);
930
931         if ( wp_set_comment_status($comment_id, 'trash') ) {
932                 add_comment_meta($comment_id, '_wp_trash_meta_status', $comment->comment_approved);
933                 add_comment_meta($comment_id, '_wp_trash_meta_time', time() );
934                 do_action('trashed_comment', $comment_id);
935                 return true;
936         }
937
938         return false;
939 }
940
941 /**
942  * Removes a comment from the Trash
943  *
944  * @since 2.9.0
945  * @uses do_action() on 'untrash_comment' before untrashing
946  * @uses do_action() on 'untrashed_comment' after untrashing
947  *
948  * @param int $comment_id Comment ID.
949  * @return mixed False on failure
950  */
951 function wp_untrash_comment($comment_id) {
952         if ( ! (int)$comment_id )
953                 return false;
954
955         do_action('untrash_comment', $comment_id);
956
957         $status = (string) get_comment_meta($comment_id, '_wp_trash_meta_status', true);
958         if ( empty($status) )
959                 $status = '0';
960
961         if ( wp_set_comment_status($comment_id, $status) ) {
962                 delete_comment_meta($comment_id, '_wp_trash_meta_time');
963                 delete_comment_meta($comment_id, '_wp_trash_meta_status');
964                 do_action('untrashed_comment', $comment_id);
965                 return true;
966         }
967
968         return false;
969 }
970
971 /**
972  * Marks a comment as Spam
973  *
974  * @since 2.9.0
975  * @uses do_action() on 'spam_comment' before spamming
976  * @uses do_action() on 'spammed_comment' after spamming
977  *
978  * @param int $comment_id Comment ID.
979  * @return mixed False on failure
980  */
981 function wp_spam_comment($comment_id) {
982         if ( !$comment = get_comment($comment_id) )
983                 return false;
984
985         do_action('spam_comment', $comment_id);
986
987         if ( wp_set_comment_status($comment_id, 'spam') ) {
988                 add_comment_meta($comment_id, '_wp_trash_meta_status', $comment->comment_approved);
989                 do_action('spammed_comment', $comment_id);
990                 return true;
991         }
992
993         return false;
994 }
995
996 /**
997  * Removes a comment from the Spam
998  *
999  * @since 2.9.0
1000  * @uses do_action() on 'unspam_comment' before unspamming
1001  * @uses do_action() on 'unspammed_comment' after unspamming
1002  *
1003  * @param int $comment_id Comment ID.
1004  * @return mixed False on failure
1005  */
1006 function wp_unspam_comment($comment_id) {
1007         if ( ! (int)$comment_id )
1008                 return false;
1009
1010         do_action('unspam_comment', $comment_id);
1011
1012         $status = (string) get_comment_meta($comment_id, '_wp_trash_meta_status', true);
1013         if ( empty($status) )
1014                 $status = '0';
1015
1016         if ( wp_set_comment_status($comment_id, $status) ) {
1017                 delete_comment_meta($comment_id, '_wp_trash_meta_status');
1018                 do_action('unspammed_comment', $comment_id);
1019                 return true;
1020         }
1021
1022         return false;
1023 }
1024
1025 /**
1026  * The status of a comment by ID.
1027  *
1028  * @since 1.0.0
1029  *
1030  * @param int $comment_id Comment ID
1031  * @return string|bool Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure.
1032  */
1033 function wp_get_comment_status($comment_id) {
1034         $comment = get_comment($comment_id);
1035         if ( !$comment )
1036                 return false;
1037
1038         $approved = $comment->comment_approved;
1039
1040         if ( $approved == NULL )
1041                 return false;
1042         elseif ( $approved == '1' )
1043                 return 'approved';
1044         elseif ( $approved == '0' )
1045                 return 'unapproved';
1046         elseif ( $approved == 'spam' )
1047                 return 'spam';
1048         elseif ( $approved == 'trash' )
1049                 return 'trash';
1050         else
1051                 return false;
1052 }
1053
1054 /**
1055  * Call hooks for when a comment status transition occurs.
1056  *
1057  * Calls hooks for comment status transitions. If the new comment status is not the same
1058  * as the previous comment status, then two hooks will be ran, the first is
1059  * 'transition_comment_status' with new status, old status, and comment data. The
1060  * next action called is 'comment_OLDSTATUS_to_NEWSTATUS' the NEWSTATUS is the
1061  * $new_status parameter and the OLDSTATUS is $old_status parameter; it has the
1062  * comment data.
1063  *
1064  * The final action will run whether or not the comment statuses are the same. The
1065  * action is named 'comment_NEWSTATUS_COMMENTTYPE', NEWSTATUS is from the $new_status
1066  * parameter and COMMENTTYPE is comment_type comment data.
1067  *
1068  * @since 2.7.0
1069  *
1070  * @param string $new_status New comment status.
1071  * @param string $old_status Previous comment status.
1072  * @param object $comment Comment data.
1073  */
1074 function wp_transition_comment_status($new_status, $old_status, $comment) {
1075         // Translate raw statuses to human readable formats for the hooks
1076         // This is not a complete list of comment status, it's only the ones that need to be renamed
1077         $comment_statuses = array(
1078                 0         => 'unapproved',
1079                 'hold'    => 'unapproved', // wp_set_comment_status() uses "hold"
1080                 1         => 'approved',
1081                 'approve' => 'approved', // wp_set_comment_status() uses "approve"
1082         );
1083         if ( isset($comment_statuses[$new_status]) ) $new_status = $comment_statuses[$new_status];
1084         if ( isset($comment_statuses[$old_status]) ) $old_status = $comment_statuses[$old_status];
1085
1086         // Call the hooks
1087         if ( $new_status != $old_status ) {
1088                 do_action('transition_comment_status', $new_status, $old_status, $comment);
1089                 do_action("comment_${old_status}_to_$new_status", $comment);
1090         }
1091         do_action("comment_${new_status}_$comment->comment_type", $comment->comment_ID, $comment);
1092 }
1093
1094 /**
1095  * Get current commenter's name, email, and URL.
1096  *
1097  * Expects cookies content to already be sanitized. User of this function might
1098  * wish to recheck the returned array for validity.
1099  *
1100  * @see sanitize_comment_cookies() Use to sanitize cookies
1101  *
1102  * @since 2.0.4
1103  *
1104  * @return array Comment author, email, url respectively.
1105  */
1106 function wp_get_current_commenter() {
1107         // Cookies should already be sanitized.
1108
1109         $comment_author = '';
1110         if ( isset($_COOKIE['comment_author_'.COOKIEHASH]) )
1111                 $comment_author = $_COOKIE['comment_author_'.COOKIEHASH];
1112
1113         $comment_author_email = '';
1114         if ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) )
1115                 $comment_author_email = $_COOKIE['comment_author_email_'.COOKIEHASH];
1116
1117         $comment_author_url = '';
1118         if ( isset($_COOKIE['comment_author_url_'.COOKIEHASH]) )
1119                 $comment_author_url = $_COOKIE['comment_author_url_'.COOKIEHASH];
1120
1121         return compact('comment_author', 'comment_author_email', 'comment_author_url');
1122 }
1123
1124 /**
1125  * Inserts a comment to the database.
1126  *
1127  * The available comment data key names are 'comment_author_IP', 'comment_date',
1128  * 'comment_date_gmt', 'comment_parent', 'comment_approved', and 'user_id'.
1129  *
1130  * @since 2.0.0
1131  * @uses $wpdb
1132  *
1133  * @param array $commentdata Contains information on the comment.
1134  * @return int The new comment's ID.
1135  */
1136 function wp_insert_comment($commentdata) {
1137         global $wpdb;
1138         extract(stripslashes_deep($commentdata), EXTR_SKIP);
1139
1140         if ( ! isset($comment_author_IP) )
1141                 $comment_author_IP = '';
1142         if ( ! isset($comment_date) )
1143                 $comment_date = current_time('mysql');
1144         if ( ! isset($comment_date_gmt) )
1145                 $comment_date_gmt = get_gmt_from_date($comment_date);
1146         if ( ! isset($comment_parent) )
1147                 $comment_parent = 0;
1148         if ( ! isset($comment_approved) )
1149                 $comment_approved = 1;
1150         if ( ! isset($comment_karma) )
1151                 $comment_karma = 0;
1152         if ( ! isset($user_id) )
1153                 $user_id = 0;
1154         if ( ! isset($comment_type) )
1155                 $comment_type = '';
1156
1157         $data = 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');
1158         $wpdb->insert($wpdb->comments, $data);
1159
1160         $id = (int) $wpdb->insert_id;
1161
1162         if ( $comment_approved == 1 )
1163                 wp_update_comment_count($comment_post_ID);
1164
1165         $comment = get_comment($id);
1166         do_action('wp_insert_comment', $id, $comment);
1167
1168         return $id;
1169 }
1170
1171 /**
1172  * Filters and sanitizes comment data.
1173  *
1174  * Sets the comment data 'filtered' field to true when finished. This can be
1175  * checked as to whether the comment should be filtered and to keep from
1176  * filtering the same comment more than once.
1177  *
1178  * @since 2.0.0
1179  * @uses apply_filters() Calls 'pre_user_id' hook on comment author's user ID
1180  * @uses apply_filters() Calls 'pre_comment_user_agent' hook on comment author's user agent
1181  * @uses apply_filters() Calls 'pre_comment_author_name' hook on comment author's name
1182  * @uses apply_filters() Calls 'pre_comment_content' hook on the comment's content
1183  * @uses apply_filters() Calls 'pre_comment_user_ip' hook on comment author's IP
1184  * @uses apply_filters() Calls 'pre_comment_author_url' hook on comment author's URL
1185  * @uses apply_filters() Calls 'pre_comment_author_email' hook on comment author's email address
1186  *
1187  * @param array $commentdata Contains information on the comment.
1188  * @return array Parsed comment information.
1189  */
1190 function wp_filter_comment($commentdata) {
1191         if ( isset($commentdata['user_ID']) )
1192                 $commentdata['user_id'] = apply_filters('pre_user_id', $commentdata['user_ID']);
1193         elseif ( isset($commentdata['user_id']) )
1194                 $commentdata['user_id'] = apply_filters('pre_user_id', $commentdata['user_id']);
1195         $commentdata['comment_agent']        = apply_filters('pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) );
1196         $commentdata['comment_author']       = apply_filters('pre_comment_author_name', $commentdata['comment_author']);
1197         $commentdata['comment_content']      = apply_filters('pre_comment_content', $commentdata['comment_content']);
1198         $commentdata['comment_author_IP']    = apply_filters('pre_comment_user_ip', $commentdata['comment_author_IP']);
1199         $commentdata['comment_author_url']   = apply_filters('pre_comment_author_url', $commentdata['comment_author_url']);
1200         $commentdata['comment_author_email'] = apply_filters('pre_comment_author_email', $commentdata['comment_author_email']);
1201         $commentdata['filtered'] = true;
1202         return $commentdata;
1203 }
1204
1205 /**
1206  * Whether comment should be blocked because of comment flood.
1207  *
1208  * @since 2.1.0
1209  *
1210  * @param bool $block Whether plugin has already blocked comment.
1211  * @param int $time_lastcomment Timestamp for last comment.
1212  * @param int $time_newcomment Timestamp for new comment.
1213  * @return bool Whether comment should be blocked.
1214  */
1215 function wp_throttle_comment_flood($block, $time_lastcomment, $time_newcomment) {
1216         if ( $block ) // a plugin has already blocked... we'll let that decision stand
1217                 return $block;
1218         if ( ($time_newcomment - $time_lastcomment) < 15 )
1219                 return true;
1220         return false;
1221 }
1222
1223 /**
1224  * Adds a new comment to the database.
1225  *
1226  * Filters new comment to ensure that the fields are sanitized and valid before
1227  * inserting comment into database. Calls 'comment_post' action with comment ID
1228  * and whether comment is approved by WordPress. Also has 'preprocess_comment'
1229  * filter for processing the comment data before the function handles it.
1230  *
1231  * @since 1.5.0
1232  * @uses apply_filters() Calls 'preprocess_comment' hook on $commentdata parameter array before processing
1233  * @uses do_action() Calls 'comment_post' hook on $comment_ID returned from adding the comment and if the comment was approved.
1234  * @uses wp_filter_comment() Used to filter comment before adding comment.
1235  * @uses wp_allow_comment() checks to see if comment is approved.
1236  * @uses wp_insert_comment() Does the actual comment insertion to the database.
1237  *
1238  * @param array $commentdata Contains information on the comment.
1239  * @return int The ID of the comment after adding.
1240  */
1241 function wp_new_comment( $commentdata ) {
1242         $commentdata = apply_filters('preprocess_comment', $commentdata);
1243
1244         $commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID'];
1245         if ( isset($commentdata['user_ID']) )
1246                 $commentdata['user_id'] = $commentdata['user_ID'] = (int) $commentdata['user_ID'];
1247         elseif ( isset($commentdata['user_id']) )
1248                 $commentdata['user_id'] = (int) $commentdata['user_id'];
1249
1250         $commentdata['comment_parent'] = isset($commentdata['comment_parent']) ? absint($commentdata['comment_parent']) : 0;
1251         $parent_status = ( 0 < $commentdata['comment_parent'] ) ? wp_get_comment_status($commentdata['comment_parent']) : '';
1252         $commentdata['comment_parent'] = ( 'approved' == $parent_status || 'unapproved' == $parent_status ) ? $commentdata['comment_parent'] : 0;
1253
1254         $commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '',$_SERVER['REMOTE_ADDR'] );
1255         $commentdata['comment_agent']     = substr($_SERVER['HTTP_USER_AGENT'], 0, 254);
1256
1257         $commentdata['comment_date']     = current_time('mysql');
1258         $commentdata['comment_date_gmt'] = current_time('mysql', 1);
1259
1260         $commentdata = wp_filter_comment($commentdata);
1261
1262         $commentdata['comment_approved'] = wp_allow_comment($commentdata);
1263
1264         $comment_ID = wp_insert_comment($commentdata);
1265
1266         do_action('comment_post', $comment_ID, $commentdata['comment_approved']);
1267
1268         if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
1269                 if ( '0' == $commentdata['comment_approved'] )
1270                         wp_notify_moderator($comment_ID);
1271
1272                 $post = &get_post($commentdata['comment_post_ID']); // Don't notify if it's your own comment
1273
1274                 if ( get_option('comments_notify') && $commentdata['comment_approved'] && ( ! isset( $commentdata['user_id'] ) || $post->post_author != $commentdata['user_id'] ) )
1275                         wp_notify_postauthor($comment_ID, empty( $commentdata['comment_type'] ) ? $commentdata['comment_type'] : '' );
1276         }
1277
1278         return $comment_ID;
1279 }
1280
1281 /**
1282  * Sets the status of a comment.
1283  *
1284  * The 'wp_set_comment_status' action is called after the comment is handled and
1285  * will only be called, if the comment status is either 'hold', 'approve', or
1286  * 'spam'. If the comment status is not in the list, then false is returned and
1287  * if the status is 'delete', then the comment is deleted without calling the
1288  * action.
1289  *
1290  * @since 1.0.0
1291  * @uses wp_transition_comment_status() Passes new and old comment status along with $comment object
1292  *
1293  * @param int $comment_id Comment ID.
1294  * @param string $comment_status New comment status, either 'hold', 'approve', 'spam', or 'delete'.
1295  * @param bool $wp_error Whether to return a WP_Error object if there is a failure. Default is false.
1296  * @return bool False on failure or deletion and true on success.
1297  */
1298 function wp_set_comment_status($comment_id, $comment_status, $wp_error = false) {
1299         global $wpdb;
1300
1301         $status = '0';
1302         switch ( $comment_status ) {
1303                 case 'hold':
1304                 case '0':
1305                         $status = '0';
1306                         break;
1307                 case 'approve':
1308                 case '1':
1309                         $status = '1';
1310                         if ( get_option('comments_notify') ) {
1311                                 $comment = get_comment($comment_id);
1312                                 wp_notify_postauthor($comment_id, $comment->comment_type);
1313                         }
1314                         break;
1315                 case 'spam':
1316                         $status = 'spam';
1317                         break;
1318                 case 'trash':
1319                         $status = 'trash';
1320                         break;
1321                 default:
1322                         return false;
1323         }
1324
1325         $comment_old = wp_clone(get_comment($comment_id));
1326
1327         if ( !$wpdb->update( $wpdb->comments, array('comment_approved' => $status), array('comment_ID' => $comment_id) ) ) {
1328                 if ( $wp_error )
1329                         return new WP_Error('db_update_error', __('Could not update comment status'), $wpdb->last_error);
1330                 else
1331                         return false;
1332         }
1333
1334         clean_comment_cache($comment_id);
1335
1336         $comment = get_comment($comment_id);
1337
1338         do_action('wp_set_comment_status', $comment_id, $comment_status);
1339         wp_transition_comment_status($comment_status, $comment_old->comment_approved, $comment);
1340
1341         wp_update_comment_count($comment->comment_post_ID);
1342
1343         return true;
1344 }
1345
1346 /**
1347  * Updates an existing comment in the database.
1348  *
1349  * Filters the comment and makes sure certain fields are valid before updating.
1350  *
1351  * @since 2.0.0
1352  * @uses $wpdb
1353  * @uses wp_transition_comment_status() Passes new and old comment status along with $comment object
1354  *
1355  * @param array $commentarr Contains information on the comment.
1356  * @return int Comment was updated if value is 1, or was not updated if value is 0.
1357  */
1358 function wp_update_comment($commentarr) {
1359         global $wpdb;
1360
1361         // First, get all of the original fields
1362         $comment = get_comment($commentarr['comment_ID'], ARRAY_A);
1363
1364         // Escape data pulled from DB.
1365         $comment = esc_sql($comment);
1366
1367         $old_status = $comment['comment_approved'];
1368
1369         // Merge old and new fields with new fields overwriting old ones.
1370         $commentarr = array_merge($comment, $commentarr);
1371
1372         $commentarr = wp_filter_comment( $commentarr );
1373
1374         // Now extract the merged array.
1375         extract(stripslashes_deep($commentarr), EXTR_SKIP);
1376
1377         $comment_content = apply_filters('comment_save_pre', $comment_content);
1378
1379         $comment_date_gmt = get_gmt_from_date($comment_date);
1380
1381         if ( !isset($comment_approved) )
1382                 $comment_approved = 1;
1383         else if ( 'hold' == $comment_approved )
1384                 $comment_approved = 0;
1385         else if ( 'approve' == $comment_approved )
1386                 $comment_approved = 1;
1387
1388         $data = compact('comment_content', 'comment_author', 'comment_author_email', 'comment_approved', 'comment_karma', 'comment_author_url', 'comment_date', 'comment_date_gmt');
1389         $rval = $wpdb->update( $wpdb->comments, $data, compact( 'comment_ID' ) );
1390
1391         clean_comment_cache($comment_ID);
1392         wp_update_comment_count($comment_post_ID);
1393         do_action('edit_comment', $comment_ID);
1394         $comment = get_comment($comment_ID);
1395         wp_transition_comment_status($comment->comment_approved, $old_status, $comment);
1396         return $rval;
1397 }
1398
1399 /**
1400  * Whether to defer comment counting.
1401  *
1402  * When setting $defer to true, all post comment counts will not be updated
1403  * until $defer is set to false. When $defer is set to false, then all
1404  * previously deferred updated post comment counts will then be automatically
1405  * updated without having to call wp_update_comment_count() after.
1406  *
1407  * @since 2.5.0
1408  * @staticvar bool $_defer
1409  *
1410  * @param bool $defer
1411  * @return unknown
1412  */
1413 function wp_defer_comment_counting($defer=null) {
1414         static $_defer = false;
1415
1416         if ( is_bool($defer) ) {
1417                 $_defer = $defer;
1418                 // flush any deferred counts
1419                 if ( !$defer )
1420                         wp_update_comment_count( null, true );
1421         }
1422
1423         return $_defer;
1424 }
1425
1426 /**
1427  * Updates the comment count for post(s).
1428  *
1429  * When $do_deferred is false (is by default) and the comments have been set to
1430  * be deferred, the post_id will be added to a queue, which will be updated at a
1431  * later date and only updated once per post ID.
1432  *
1433  * If the comments have not be set up to be deferred, then the post will be
1434  * updated. When $do_deferred is set to true, then all previous deferred post
1435  * IDs will be updated along with the current $post_id.
1436  *
1437  * @since 2.1.0
1438  * @see wp_update_comment_count_now() For what could cause a false return value
1439  *
1440  * @param int $post_id Post ID
1441  * @param bool $do_deferred Whether to process previously deferred post comment counts
1442  * @return bool True on success, false on failure
1443  */
1444 function wp_update_comment_count($post_id, $do_deferred=false) {
1445         static $_deferred = array();
1446
1447         if ( $do_deferred ) {
1448                 $_deferred = array_unique($_deferred);
1449                 foreach ( $_deferred as $i => $_post_id ) {
1450                         wp_update_comment_count_now($_post_id);
1451                         unset( $_deferred[$i] ); /** @todo Move this outside of the foreach and reset $_deferred to an array instead */
1452                 }
1453         }
1454
1455         if ( wp_defer_comment_counting() ) {
1456                 $_deferred[] = $post_id;
1457                 return true;
1458         }
1459         elseif ( $post_id ) {
1460                 return wp_update_comment_count_now($post_id);
1461         }
1462
1463 }
1464
1465 /**
1466  * Updates the comment count for the post.
1467  *
1468  * @since 2.5.0
1469  * @uses $wpdb
1470  * @uses do_action() Calls 'wp_update_comment_count' hook on $post_id, $new, and $old
1471  * @uses do_action() Calls 'edit_posts' hook on $post_id and $post
1472  *
1473  * @param int $post_id Post ID
1474  * @return bool False on '0' $post_id or if post with ID does not exist. True on success.
1475  */
1476 function wp_update_comment_count_now($post_id) {
1477         global $wpdb;
1478         $post_id = (int) $post_id;
1479         if ( !$post_id )
1480                 return false;
1481         if ( !$post = get_post($post_id) )
1482                 return false;
1483
1484         $old = (int) $post->comment_count;
1485         $new = (int) $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id) );
1486         $wpdb->update( $wpdb->posts, array('comment_count' => $new), array('ID' => $post_id) );
1487
1488         if ( 'page' == $post->post_type )
1489                 clean_page_cache( $post_id );
1490         else
1491                 clean_post_cache( $post_id );
1492
1493         do_action('wp_update_comment_count', $post_id, $new, $old);
1494         do_action('edit_post', $post_id, $post);
1495
1496         return true;
1497 }
1498
1499 //
1500 // Ping and trackback functions.
1501 //
1502
1503 /**
1504  * Finds a pingback server URI based on the given URL.
1505  *
1506  * Checks the HTML for the rel="pingback" link and x-pingback headers. It does
1507  * a check for the x-pingback headers first and returns that, if available. The
1508  * check for the rel="pingback" has more overhead than just the header.
1509  *
1510  * @since 1.5.0
1511  *
1512  * @param string $url URL to ping.
1513  * @param int $deprecated Not Used.
1514  * @return bool|string False on failure, string containing URI on success.
1515  */
1516 function discover_pingback_server_uri( $url, $deprecated = '' ) {
1517         if ( !empty( $deprecated ) )
1518                 _deprecated_argument( __FUNCTION__, '2.7' );
1519
1520         $pingback_str_dquote = 'rel="pingback"';
1521         $pingback_str_squote = 'rel=\'pingback\'';
1522
1523         /** @todo Should use Filter Extension or custom preg_match instead. */
1524         $parsed_url = parse_url($url);
1525
1526         if ( ! isset( $parsed_url['host'] ) ) // Not an URL. This should never happen.
1527                 return false;
1528
1529         //Do not search for a pingback server on our own uploads
1530         $uploads_dir = wp_upload_dir();
1531         if ( 0 === strpos($url, $uploads_dir['baseurl']) )
1532                 return false;
1533
1534         $response = wp_remote_head( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
1535
1536         if ( is_wp_error( $response ) )
1537                 return false;
1538
1539         if ( isset( $response['headers']['x-pingback'] ) )
1540                 return $response['headers']['x-pingback'];
1541
1542         // Not an (x)html, sgml, or xml page, no use going further.
1543         if ( isset( $response['headers']['content-type'] ) && preg_match('#(image|audio|video|model)/#is', $response['headers']['content-type']) )
1544                 return false;
1545
1546         // Now do a GET since we're going to look in the html headers (and we're sure its not a binary file)
1547         $response = wp_remote_get( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
1548
1549         if ( is_wp_error( $response ) )
1550                 return false;
1551
1552         $contents = $response['body'];
1553
1554         $pingback_link_offset_dquote = strpos($contents, $pingback_str_dquote);
1555         $pingback_link_offset_squote = strpos($contents, $pingback_str_squote);
1556         if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
1557                 $quote = ($pingback_link_offset_dquote) ? '"' : '\'';
1558                 $pingback_link_offset = ($quote=='"') ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
1559                 $pingback_href_pos = @strpos($contents, 'href=', $pingback_link_offset);
1560                 $pingback_href_start = $pingback_href_pos+6;
1561                 $pingback_href_end = @strpos($contents, $quote, $pingback_href_start);
1562                 $pingback_server_url_len = $pingback_href_end - $pingback_href_start;
1563                 $pingback_server_url = substr($contents, $pingback_href_start, $pingback_server_url_len);
1564
1565                 // We may find rel="pingback" but an incomplete pingback URL
1566                 if ( $pingback_server_url_len > 0 ) { // We got it!
1567                         return $pingback_server_url;
1568                 }
1569         }
1570
1571         return false;
1572 }
1573
1574 /**
1575  * Perform all pingbacks, enclosures, trackbacks, and send to pingback services.
1576  *
1577  * @since 2.1.0
1578  * @uses $wpdb
1579  */
1580 function do_all_pings() {
1581         global $wpdb;
1582
1583         // Do pingbacks
1584         while ($ping = $wpdb->get_row("SELECT * FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_pingme' LIMIT 1")) {
1585                 $mid = $wpdb->get_var( "SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = {$ping->ID} AND meta_key = '_pingme' LIMIT 1");
1586                 do_action( 'delete_postmeta', $mid );
1587                 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_id = %d", $mid ) );
1588                 do_action( 'deleted_postmeta', $mid );
1589                 pingback($ping->post_content, $ping->ID);
1590         }
1591
1592         // Do Enclosures
1593         while ($enclosure = $wpdb->get_row("SELECT * FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_encloseme' LIMIT 1")) {
1594                 $mid = $wpdb->get_var( $wpdb->prepare("SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_encloseme'", $enclosure->ID) );
1595                 do_action( 'delete_postmeta', $mid );
1596                 $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_id =  %d", $mid) );
1597                 do_action( 'deleted_postmeta', $mid );
1598                 do_enclose($enclosure->post_content, $enclosure->ID);
1599         }
1600
1601         // Do Trackbacks
1602         $trackbacks = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE to_ping <> '' AND post_status = 'publish'");
1603         if ( is_array($trackbacks) )
1604                 foreach ( $trackbacks as $trackback )
1605                         do_trackbacks($trackback);
1606
1607         //Do Update Services/Generic Pings
1608         generic_ping();
1609 }
1610
1611 /**
1612  * Perform trackbacks.
1613  *
1614  * @since 1.5.0
1615  * @uses $wpdb
1616  *
1617  * @param int $post_id Post ID to do trackbacks on.
1618  */
1619 function do_trackbacks($post_id) {
1620         global $wpdb;
1621
1622         $post = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id) );
1623         $to_ping = get_to_ping($post_id);
1624         $pinged  = get_pung($post_id);
1625         if ( empty($to_ping) ) {
1626                 $wpdb->update($wpdb->posts, array('to_ping' => ''), array('ID' => $post_id) );
1627                 return;
1628         }
1629
1630         if ( empty($post->post_excerpt) )
1631                 $excerpt = apply_filters('the_content', $post->post_content);
1632         else
1633                 $excerpt = apply_filters('the_excerpt', $post->post_excerpt);
1634         $excerpt = str_replace(']]>', ']]&gt;', $excerpt);
1635         $excerpt = wp_html_excerpt($excerpt, 252) . '...';
1636
1637         $post_title = apply_filters('the_title', $post->post_title);
1638         $post_title = strip_tags($post_title);
1639
1640         if ( $to_ping ) {
1641                 foreach ( (array) $to_ping as $tb_ping ) {
1642                         $tb_ping = trim($tb_ping);
1643                         if ( !in_array($tb_ping, $pinged) ) {
1644                                 trackback($tb_ping, $post_title, $excerpt, $post_id);
1645                                 $pinged[] = $tb_ping;
1646                         } else {
1647                                 $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $tb_ping, $post_id) );
1648                         }
1649                 }
1650         }
1651 }
1652
1653 /**
1654  * Sends pings to all of the ping site services.
1655  *
1656  * @since 1.2.0
1657  *
1658  * @param int $post_id Post ID. Not actually used.
1659  * @return int Same as Post ID from parameter
1660  */
1661 function generic_ping($post_id = 0) {
1662         $services = get_option('ping_sites');
1663
1664         $services = explode("\n", $services);
1665         foreach ( (array) $services as $service ) {
1666                 $service = trim($service);
1667                 if ( '' != $service )
1668                         weblog_ping($service);
1669         }
1670
1671         return $post_id;
1672 }
1673
1674 /**
1675  * Pings back the links found in a post.
1676  *
1677  * @since 0.71
1678  * @uses $wp_version
1679  * @uses IXR_Client
1680  *
1681  * @param string $content Post content to check for links.
1682  * @param int $post_ID Post ID.
1683  */
1684 function pingback($content, $post_ID) {
1685         global $wp_version;
1686         include_once(ABSPATH . WPINC . '/class-IXR.php');
1687
1688         // original code by Mort (http://mort.mine.nu:8080)
1689         $post_links = array();
1690
1691         $pung = get_pung($post_ID);
1692
1693         // Variables
1694         $ltrs = '\w';
1695         $gunk = '/#~:.?+=&%@!\-';
1696         $punc = '.:?\-';
1697         $any = $ltrs . $gunk . $punc;
1698
1699         // Step 1
1700         // Parsing the post, external links (if any) are stored in the $post_links array
1701         // This regexp comes straight from phpfreaks.com
1702         // http://www.phpfreaks.com/quickcode/Extract_All_URLs_on_a_Page/15.php
1703         preg_match_all("{\b http : [$any] +? (?= [$punc] * [^$any] | $)}x", $content, $post_links_temp);
1704
1705         // Step 2.
1706         // Walking thru the links array
1707         // first we get rid of links pointing to sites, not to specific files
1708         // Example:
1709         // http://dummy-weblog.org
1710         // http://dummy-weblog.org/
1711         // http://dummy-weblog.org/post.php
1712         // We don't wanna ping first and second types, even if they have a valid <link/>
1713
1714         foreach ( (array) $post_links_temp[0] as $link_test ) :
1715                 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
1716                                 && !is_local_attachment($link_test) ) : // Also, let's never ping local attachments.
1717                         if ( $test = @parse_url($link_test) ) {
1718                                 if ( isset($test['query']) )
1719                                         $post_links[] = $link_test;
1720                                 elseif ( ($test['path'] != '/') && ($test['path'] != '') )
1721                                         $post_links[] = $link_test;
1722                         }
1723                 endif;
1724         endforeach;
1725
1726         do_action_ref_array('pre_ping', array(&$post_links, &$pung));
1727
1728         foreach ( (array) $post_links as $pagelinkedto ) {
1729                 $pingback_server_url = discover_pingback_server_uri($pagelinkedto, 2048);
1730
1731                 if ( $pingback_server_url ) {
1732                         @ set_time_limit( 60 );
1733                          // Now, the RPC call
1734                         $pagelinkedfrom = get_permalink($post_ID);
1735
1736                         // using a timeout of 3 seconds should be enough to cover slow servers
1737                         $client = new IXR_Client($pingback_server_url);
1738                         $client->timeout = 3;
1739                         $client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . $wp_version, $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom);
1740                         // when set to true, this outputs debug messages by itself
1741                         $client->debug = false;
1742
1743                         if ( $client->query('pingback.ping', $pagelinkedfrom, $pagelinkedto) || ( isset($client->error->code) && 48 == $client->error->code ) ) // Already registered
1744                                 add_ping( $post_ID, $pagelinkedto );
1745                 }
1746         }
1747 }
1748
1749 /**
1750  * Check whether blog is public before returning sites.
1751  *
1752  * @since 2.1.0
1753  *
1754  * @param mixed $sites Will return if blog is public, will not return if not public.
1755  * @return mixed Empty string if blog is not public, returns $sites, if site is public.
1756  */
1757 function privacy_ping_filter($sites) {
1758         if ( '0' != get_option('blog_public') )
1759                 return $sites;
1760         else
1761                 return '';
1762 }
1763
1764 /**
1765  * Send a Trackback.
1766  *
1767  * Updates database when sending trackback to prevent duplicates.
1768  *
1769  * @since 0.71
1770  * @uses $wpdb
1771  *
1772  * @param string $trackback_url URL to send trackbacks.
1773  * @param string $title Title of post.
1774  * @param string $excerpt Excerpt of post.
1775  * @param int $ID Post ID.
1776  * @return mixed Database query from update.
1777  */
1778 function trackback($trackback_url, $title, $excerpt, $ID) {
1779         global $wpdb;
1780
1781         if ( empty($trackback_url) )
1782                 return;
1783
1784         $options = array();
1785         $options['timeout'] = 4;
1786         $options['body'] = array(
1787                 'title' => $title,
1788                 'url' => get_permalink($ID),
1789                 'blog_name' => get_option('blogname'),
1790                 'excerpt' => $excerpt
1791         );
1792
1793         $response = wp_remote_post($trackback_url, $options);
1794
1795         if ( is_wp_error( $response ) )
1796                 return;
1797
1798         $tb_url = addslashes( $trackback_url );
1799         $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', '$tb_url') WHERE ID = %d", $ID) );
1800         return $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, '$tb_url', '')) WHERE ID = %d", $ID) );
1801 }
1802
1803 /**
1804  * Send a pingback.
1805  *
1806  * @since 1.2.0
1807  * @uses $wp_version
1808  * @uses IXR_Client
1809  *
1810  * @param string $server Host of blog to connect to.
1811  * @param string $path Path to send the ping.
1812  */
1813 function weblog_ping($server = '', $path = '') {
1814         global $wp_version;
1815         include_once(ABSPATH . WPINC . '/class-IXR.php');
1816
1817         // using a timeout of 3 seconds should be enough to cover slow servers
1818         $client = new IXR_Client($server, ((!strlen(trim($path)) || ('/' == $path)) ? false : $path));
1819         $client->timeout = 3;
1820         $client->useragent .= ' -- WordPress/'.$wp_version;
1821
1822         // when set to true, this outputs debug messages by itself
1823         $client->debug = false;
1824         $home = trailingslashit( home_url() );
1825         if ( !$client->query('weblogUpdates.extendedPing', get_option('blogname'), $home, get_bloginfo('rss2_url') ) ) // then try a normal ping
1826                 $client->query('weblogUpdates.ping', get_option('blogname'), $home);
1827 }
1828
1829 //
1830 // Cache
1831 //
1832
1833 /**
1834  * Removes comment ID from the comment cache.
1835  *
1836  * @since 2.3.0
1837  * @package WordPress
1838  * @subpackage Cache
1839  *
1840  * @param int|array $id Comment ID or array of comment IDs to remove from cache
1841  */
1842 function clean_comment_cache($ids) {
1843         foreach ( (array) $ids as $id )
1844                 wp_cache_delete($id, 'comment');
1845 }
1846
1847 /**
1848  * Updates the comment cache of given comments.
1849  *
1850  * Will add the comments in $comments to the cache. If comment ID already exists
1851  * in the comment cache then it will not be updated. The comment is added to the
1852  * cache using the comment group with the key using the ID of the comments.
1853  *
1854  * @since 2.3.0
1855  * @package WordPress
1856  * @subpackage Cache
1857  *
1858  * @param array $comments Array of comment row objects
1859  */
1860 function update_comment_cache($comments) {
1861         foreach ( (array) $comments as $comment )
1862                 wp_cache_add($comment->comment_ID, $comment, 'comment');
1863 }
1864
1865 //
1866 // Internal
1867 //
1868
1869 /**
1870  * Close comments on old posts on the fly, without any extra DB queries.  Hooked to the_posts.
1871  *
1872  * @access private
1873  * @since 2.7.0
1874  *
1875  * @param object $posts Post data object.
1876  * @return object
1877  */
1878 function _close_comments_for_old_posts( $posts ) {
1879         if ( empty($posts) || !is_singular() || !get_option('close_comments_for_old_posts') )
1880                 return $posts;
1881
1882         $days_old = (int) get_option('close_comments_days_old');
1883         if ( !$days_old )
1884                 return $posts;
1885
1886         if ( time() - strtotime( $posts[0]->post_date_gmt ) > ( $days_old * 24 * 60 * 60 ) ) {
1887                 $posts[0]->comment_status = 'closed';
1888                 $posts[0]->ping_status = 'closed';
1889         }
1890
1891         return $posts;
1892 }
1893
1894 /**
1895  * Close comments on an old post.  Hooked to comments_open and pings_open.
1896  *
1897  * @access private
1898  * @since 2.7.0
1899  *
1900  * @param bool $open Comments open or closed
1901  * @param int $post_id Post ID
1902  * @return bool $open
1903  */
1904 function _close_comments_for_old_post( $open, $post_id ) {
1905         if ( ! $open )
1906                 return $open;
1907
1908         if ( !get_option('close_comments_for_old_posts') )
1909                 return $open;
1910
1911         $days_old = (int) get_option('close_comments_days_old');
1912         if ( !$days_old )
1913                 return $open;
1914
1915         $post = get_post($post_id);
1916
1917         if ( time() - strtotime( $post->post_date_gmt ) > ( $days_old * 24 * 60 * 60 ) )
1918                 return false;
1919
1920         return $open;
1921 }
1922
1923 ?>