You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

3102 lines
100 KiB

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