/wp-includes/comment.php

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