root/trunk/wp-includes/comment.php

Revision 8091, 43.3 kB (checked in by westi, 3 weeks ago)

PHPDoc updates for comment.php. See #5578 props jacobsantos.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2 /**
3  * Manages WordPress comments
4  *
5  * @package WordPress
6  */
7
8 /**
9  * Checks whether a comment passes internal checks to be allowed to add.
10  *
11  * If comment moderation is set in the administration, then all comments,
12  * regardless of their type and whitelist will be set to false.
13  *
14  * If the number of links exceeds the amount in the administration, then the
15  * check fails.
16  *
17  * If any of the parameter contents match the blacklist of words, then the check
18  * fails.
19  *
20  * If the comment is a trackback and part of the blogroll, then the trackback is
21  * automatically whitelisted. If the comment author was approved before, then
22  * the comment is 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
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     if ( preg_match_all("|(href\t*?=\t*?['\"]?)?(https?:)?//|i", $comment, $out) >= get_option('comment_max_links') )
48         return false; // Check # of external links
49
50     $mod_keys = trim(get_option('moderation_keys'));
51     if ( !empty($mod_keys) ) {
52         $words = explode("\n", $mod_keys );
53
54         foreach ($words as $word) {
55             $word = trim($word);
56
57             // Skip empty lines
58             if ( empty($word) )
59                 continue;
60
61             // Do some escaping magic so that '#' chars in the
62             // spam words don't break things:
63             $word = preg_quote($word, '#');
64
65             $pattern = "#$word#i";
66             if ( preg_match($pattern, $author) ) return false;
67             if ( preg_match($pattern, $email) ) return false;
68             if ( preg_match($pattern, $url) ) return false;
69             if ( preg_match($pattern, $comment) ) return false;
70             if ( preg_match($pattern, $user_ip) ) return false;
71             if ( preg_match($pattern, $user_agent) ) return false;
72         }
73     }
74
75     // Comment whitelisting:
76     if ( 1 == get_option('comment_whitelist')) {
77         if ( 'trackback' == $comment_type || 'pingback' == $comment_type ) { // check if domain is in blogroll
78             $uri = parse_url($url);
79             $domain = $uri['host'];
80             $uri = parse_url( get_option('home') );
81             $home_domain = $uri['host'];
82             if ( $wpdb->get_var($wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_url LIKE (%s) LIMIT 1", '%'.$domain.'%')) || $domain == $home_domain )
83                 return true;
84             else
85                 return false;
86         } elseif ( $author != '' && $email != '' ) {
87             // expected_slashed ($author, $email)
88             $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");
89             if ( ( 1 == $ok_to_comment ) &&
90                 ( empty($mod_keys) || false === strpos( $email, $mod_keys) ) )
91                     return true;
92             else
93                 return false;
94         } else {
95             return false;
96         }
97     }
98     return true;
99 }
100
101 /**
102  * Retrieve the approved comments for post $post_id.
103  *
104  * @since 2.0
105  * @uses $wpdb
106  *
107  * @param int $post_id The ID of the post
108  * @return array $comments The approved comments
109  */
110 function get_approved_comments($post_id) {
111     global $wpdb;
112     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));
113 }
114
115 /**
116  * Retrieves comment data given a comment ID or comment object.
117  *
118  * If an object is passed then the comment data will be cached and then returned
119  * after being passed through a filter.
120  *
121  * If the comment is empty, then the global comment variable will be used, if it
122  * is set.
123  *
124  * @since 2.0
125  * @uses $wpdb
126  *
127  * @param object|string|int $comment Comment to retrieve.
128  * @param string $output Optional. OBJECT or ARRAY_A or ARRAY_N constants
129  * @return object|array|null Depends on $output value.
130  */
131 function &get_comment(&$comment, $output = OBJECT) {
132     global $wpdb;
133
134     if ( empty($comment) ) {
135         if ( isset($GLOBALS['comment']) )
136             $_comment = & $GLOBALS['comment'];
137         else
138             $_comment = null;
139     } elseif ( is_object($comment) ) {
140         wp_cache_add($comment->comment_ID, $comment, 'comment');
141         $_comment = $comment;
142     } else {
143         if ( isset($GLOBALS['comment']) && ($GLOBALS['comment']->comment_ID == $comment) ) {
144             $_comment = & $GLOBALS['comment'];
145         } elseif ( ! $_comment = wp_cache_get($comment, 'comment') ) {
146             $_comment = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment));
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         return get_object_vars($_comment);
157     } elseif ( $output == ARRAY_N ) {
158         return array_values(get_object_vars($_comment));
159     } else {
160         return $_comment;
161     }
162 }
163
164 /**
165  * Retrieve an array of comment data about comment $comment_ID.
166  *
167  * get_comment() technically does the same thing as this function. This function
168  * also appears to reference variables and then not use them or not update them
169  * when needed. It is advised to switch to get_comment(), since this function
170  * might be deprecated in favor of using get_comment().
171  *
172  * @deprecated Use get_comment()
173  * @see get_comment()
174  * @since 0.71
175  *
176  * @uses $postc Comment cache, might not be used any more
177  * @uses $id
178  * @uses $wpdb Database Object
179  *
180  * @param int $comment_ID The ID of the comment
181  * @param int $no_cache Whether to use the cache or not (casted to bool)
182  * @param bool $include_unapproved Whether to include unapproved comments or not
183  * @return array The comment data
184  */
185 function get_commentdata( $comment_ID, $no_cache = 0, $include_unapproved = false ) {
186     global $postc, $wpdb;
187     if ( $no_cache ) {
188         $query = $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_ID = %d", $comment_ID);
189         if ( false == $include_unapproved )
190             $query .= " AND comment_approved = '1'";
191         $myrow = $wpdb->get_row($query, ARRAY_A);
192     } else {
193         $myrow['comment_ID']           = $postc->comment_ID;
194         $myrow['comment_post_ID']      = $postc->comment_post_ID;
195         $myrow['comment_author']       = $postc->comment_author;
196         $myrow['comment_author_email'] = $postc->comment_author_email;
197         $myrow['comment_author_url']   = $postc->comment_author_url;
198         $myrow['comment_author_IP']    = $postc->comment_author_IP;
199         $myrow['comment_date']         = $postc->comment_date;
200         $myrow['comment_content']      = $postc->comment_content;
201         $myrow['comment_karma']        = $postc->comment_karma;
202         $myrow['comment_approved']     = $postc->comment_approved;
203         $myrow['comment_type']         = $postc->comment_type;
204     }
205     return $myrow;
206 }
207
208 /**
209  * The date the last comment was modified.
210  *
211  * {@internal Missing Long Description}}
212  *
213  * @since 1.5.0
214  * @uses $wpdb
215  * @global array $cache_lastcommentmodified
216  *
217  * @param string $timezone Which timezone to use in reference to 'gmt', 'blog',
218  *        or 'server' locations
219  * @return string Last comment modified date
220  */
221 function get_lastcommentmodified($timezone = 'server') {
222     global $cache_lastcommentmodified, $wpdb;
223
224     if ( isset($cache_lastcommentmodified[$timezone]) )
225         return $cache_lastcommentmodified[$timezone];
226
227     $add_seconds_server = date('Z');
228
229     switch ( strtolower($timezone)) {
230         case 'gmt':
231             $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
232             break;
233         case 'blog':
234             $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
235             break;
236         case 'server':
237             $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));
238             break;
239     }
240
241     $cache_lastcommentmodified[$timezone] = $lastcommentmodified;
242
243     return $lastcommentmodified;
244 }
245
246 /**
247  * The amount of comments in a post or total comments.
248  *
249  * {@internal Missing Long Description}}
250  *
251  * @since 2.0.0
252  * @uses $wpdb
253  *
254  * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide
255  * @return array The amount of spam, approved, awaiting moderation, and total
256  */
257 function get_comment_count( $post_id = 0 ) {
258     global $wpdb;
259
260     $post_id = (int) $post_id;
261
262     $where = '';
263     if ( $post_id > 0 ) {
264         $where = $wpdb->prepare("WHERE comment_post_ID = %d", $post_id);
265     }
266
267     $totals = (array) $wpdb->get_results("
268         SELECT comment_approved, COUNT( * ) AS total
269         FROM {$wpdb->comments}
270         {$where}
271         GROUP BY comment_approved
272     ", ARRAY_A);
273
274     $comment_count = array(
275         "approved"              => 0,
276         "awaiting_moderation"   => 0,
277         "spam"                  => 0,
278         "total_comments"        => 0
279     );
280
281     foreach ( $totals as $row ) {
282         switch ( $row['comment_approved'] ) {
283             case 'spam':
284                 $comment_count['spam'] = $row['total'];
285                 $comment_count["total_comments"] += $row['total'];
286                 break;
287             case 1:
288                 $comment_count['approved'] = $row['total'];
289                 $comment_count['total_comments'] += $row['total'];
290                 break;
291             case 0:
292                 $comment_count['awaiting_moderation'] = $row['total'];
293                 $comment_count['total_comments'] += $row['total'];
294                 break;
295             default:
296                 break;
297         }
298     }
299
300     return $comment_count;
301 }
302
303 /**
304  * Sanitizes the cookies sent to the user already.
305  *
306  * Will only do anything if the cookies have already been created for the user.
307  * Mostly used after cookies had been sent to use elsewhere.
308  *
309  * @since 2.0.4
310  */
311 function sanitize_comment_cookies() {
312     if ( isset($_COOKIE['comment_author_'.COOKIEHASH]) ) {
313         $comment_author = apply_filters('pre_comment_author_name', $_COOKIE['comment_author_'.COOKIEHASH]);
314         $comment_author = stripslashes($comment_author);
315         $comment_author = attribute_escape($comment_author);
316         $_COOKIE['comment_author_'.COOKIEHASH] = $comment_author;
317     }
318
319     if ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) ) {
320         $comment_author_email = apply_filters('pre_comment_author_email', $_COOKIE['comment_author_email_'.COOKIEHASH]);
321         $comment_author_email = stripslashes($comment_author_email);
322         $comment_author_email = attribute_escape($comment_author_email);
323         $_COOKIE['comment_author_email_'.COOKIEHASH] = $comment_author_email;
324     }
325
326     if ( isset($_COOKIE['comment_author_url_'.COOKIEHASH]) ) {
327         $comment_author_url = apply_filters('pre_comment_author_url', $_COOKIE['comment_author_url_'.COOKIEHASH]);
328         $comment_author_url = stripslashes($comment_author_url);
329         $_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
330     }
331 }
332
333 /**
334  * Validates whether this comment is allowed to be made or not.
335  *
336  * {@internal Missing Long Description}}
337  *
338  * @since 2.0.0
339  * @uses $wpdb
340  * @uses apply_filters() Calls 'pre_comment_approved' hook on the type of comment
341  * @uses do_action() Calls 'check_comment_flood' hook on $comment_author_IP, $comment_author_email, and $comment_date_gmt
342  *
343  * @param array $commentdata Contains information on the comment
344  * @return mixed Signifies the approval status (0|1|'spam')
345  */
346 function wp_allow_comment($commentdata) {
347     global $wpdb;
348     extract($commentdata, EXTR_SKIP);
349
350     // Simple duplicate check
351     // expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
352     $dupe = "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = '$comment_post_ID' AND ( comment_author = '$comment_author' ";
353     if ( $comment_author_email )
354         $dupe .= "OR comment_author_email = '$comment_author_email' ";
355     $dupe .= ") AND comment_content = '$comment_content' LIMIT 1";
356     if ( $wpdb->get_var($dupe) )
357         wp_die( __('Duplicate comment detected; it looks as though you\'ve already said that!') );
358
359     do_action( 'check_comment_flood', $comment_author_IP, $comment_author_email, $comment_date_gmt );
360
361     if ( $user_id ) {
362         $userdata = get_userdata($user_id);
363         $user = new WP_User($user_id);
364         $post_author = $wpdb->get_var($wpdb->prepare("SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1", $comment_post_ID));
365     }
366
367     if ( $userdata && ( $user_id == $post_author || $user->has_cap('moderate_comments') ) ) {
368         // The author and the admins get respect.
369         $approved = 1;
370      } else {
371         // Everyone else's comments will be checked.
372         if ( check_comment($comment_author, $comment_author_email, $comment_author_url, $comment_content, $comment_author_IP, $comment_agent, $comment_type) )
373             $approved = 1;
374         else
375             $approved = 0;
376         if ( wp_blacklist_check($comment_author, $comment_author_email, $comment_author_url, $comment_content, $comment_author_IP, $comment_agent) )
377             $approved = 'spam';
378     }
379
380     $approved = apply_filters('pre_comment_approved', $approved);
381     return $approved;
382 }
383
384 /**
385  * {@internal Missing Short Description}}
386  *
387  * {@internal Missing Long Description}}
388  *
389  * @since 2.3.0
390  * @uses $wpdb
391  * @uses apply_filters() {@internal Missing Description}}
392  * @uses do_action() {@internal Missing Description}}
393  *
394  * @param string $ip {@internal Missing Description}}
395  * @param string $email {@internal Missing Description}}
396  * @param unknown_type $date {@internal Missing Description}}
397  */
398 function check_comment_flood_db( $ip, $email, $date ) {
399     global $wpdb;
400     if ( current_user_can( 'manage_options' ) )
401         return; // don't throttle admins
402     if ( $lasttime = $wpdb->get_var( $wpdb->prepare("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_author_IP = %s OR comment_author_email = %s ORDER BY comment_date DESC LIMIT 1", $ip, $email) ) ) {
403         $time_lastcomment = mysql2date('U', $lasttime);
404         $time_newcomment  = mysql2date('U', $date);
405         $flood_die = apply_filters('comment_flood_filter', false, $time_lastcomment, $time_newcomment);
406         if ( $flood_die ) {
407             do_action('comment_flood_trigger', $time_lastcomment, $time_newcomment);
408             wp_die( __('You are posting comments too quickly.  Slow down.') );
409         }
410     }
411 }
412
413 /**
414  * Does comment contain blacklisted characters or words.
415  *
416  * {@internal Missing Long Description}}
417  *
418  * @since 1.5.0
419  * @uses do_action() Calls 'wp_blacklist_check' hook for all parameters
420  *
421  * @param string $author The author of the comment
422  * @param string $email The email of the comment
423  * @param string $url The url used in the comment
424  * @param string $comment The comment content
425  * @param string $user_ip The comment author IP address
426  * @param string $user_agent The author's browser user agent
427  * @return bool True if comment contains blacklisted content, false if comment does not
428  */
429 function wp_blacklist_check($author, $email, $url, $comment, $user_ip, $user_agent) {
430     do_action('wp_blacklist_check', $author, $email, $url, $comment, $user_ip, $user_agent);
431
432     if ( preg_match_all('/&#(\d+);/', $comment . $author . $url, $chars) ) {
433         foreach ( (array) $chars[1] as $char ) {
434             // If it's an encoded char in the normal ASCII set, reject
435             if ( 38 == $char )
436                 continue; // Unless it's &
437             if ( $char < 128 )
438                 return true;
439         }
440     }
441
442     $mod_keys = trim( get_option('blacklist_keys') );
443     if ( '' == $mod_keys )
444         return false; // If moderation keys are empty
445     $words = explode("\n", $mod_keys );
446
447     foreach ( (array) $words as $word ) {
448         $word = trim($word);
449
450         // Skip empty lines
451         if ( empty($word) ) { continue; }
452
453         // Do some escaping magic so that '#' chars in the
454         // spam words don't break things:
455         $word = preg_quote($word, '#');
456
457         $pattern = "#$word#i";
458         if (
459                preg_match($pattern, $author)
460             || preg_match($pattern, $email)
461             || preg_match($pattern, $url)
462             || preg_match($pattern, $comment)
463             || preg_match($pattern, $user_ip)
464             || preg_match($pattern, $user_agent)
465          )
466             return true;
467     }
468     return false;
469 }
470
471 /**
472  * {@internal Missing Short Description}}
473  *
474  * {@internal Missing Long Description}}
475  *
476  * @param unknown_type $post_id
477  * @return unknown
478  */
479 function wp_count_comments( $post_id = 0 ) {
480     global $wpdb;
481
482     $post_id = (int) $post_id;
483
484     $count = wp_cache_get("comments-{$post_id}", 'counts');
485
486     if ( false !== $count )
487         return $count;
488
489     $where = '';
490     if( $post_id > 0 )
491         $where = $wpdb->prepare( "WHERE comment_post_ID = %d", $post_id );
492
493     $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} {$where} GROUP BY comment_approved", ARRAY_A );
494
495     $total = 0;
496     $stats = array( );
497     $approved = array('0' => 'moderated', '1' => 'approved', 'spam' => 'spam');
498     foreach( (array) $count as $row_num => $row ) {
499         $total += $row['num_comments'];
500         $stats[$approved[$row['comment_approved']]] = $row['num_comments'];
501     }
502
503     $stats['total_comments'] = $total;
504     foreach ( $approved as $key ) {
505         if ( empty($stats[$key]) )
506             $stats[$key] = 0;
507     }
508
509     $stats = (object) $stats;
510     wp_cache_set("comments-{$post_id}", $stats, 'counts');
511
512     return $stats;
513 }
514
515 /**
516  * Removes comment ID and maybe updates post comment count.
517  *
518  * The post comment count will be updated if the comment was approved and has a
519  * post ID available.
520  *
521  * @since 2.0.0
522  * @uses $wpdb
523  * @uses do_action() Calls 'delete_comment' hook on comment ID
524  * @uses do_action() Calls 'wp_set_comment_status' hook on comment ID with 'delete' set for the second parameter
525  *
526  * @param int $comment_id Comment ID
527  * @return bool False if delete comment query failure, true on success
528  */
529 function wp_delete_comment($comment_id) {
530     global $wpdb;
531     do_action('delete_comment', $comment_id);
532
533     $comment = get_comment($comment_id);
534
535     if ( ! $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment_id) ) )
536         return false;
537
538     $post_id = $comment->comment_post_ID;
539     if ( $post_id && $comment->comment_approved == 1 )
540         wp_update_comment_count($post_id);
541
542     clean_comment_cache($comment_id);
543
544     do_action('wp_set_comment_status', $comment_id, 'delete');
545     return true;
546 }
547
548 /**
549  * The status of a comment by ID.
550  *
551  * @since 1.0.0
552  *
553  * @param int $comment_id Comment ID
554  * @return string|bool Status might be 'deleted', 'approved', 'unapproved', 'spam'. False on failure
555  */
556 function wp_get_comment_status($comment_id) {
557