PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/comment.php

https://gitlab.com/plusplusminus/aevitas
PHP | 1496 lines | 725 code | 168 blank | 603 comment | 160 complexity | 4a08346dce7da9e37b5f288620a4f1d2 MD5 | raw file
  1. <?php
  2. /**
  3. * Manages WordPress comments
  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. * Filter the maximum number of links allowed in a comment.
  50. *
  51. * @since 3.0.0
  52. *
  53. * @param int $num_links The number of links allowed.
  54. * @param string $url Comment author's URL. Included in allowed links total.
  55. */
  56. $num_links = apply_filters( 'comment_max_links_url', $num_links, $url );
  57. /*
  58. * If the number of links in the comment exceeds the allowed amount,
  59. * fail the check by returning false.
  60. */
  61. if ( $num_links >= $max_links )
  62. return false;
  63. }
  64. $mod_keys = trim(get_option('moderation_keys'));
  65. // If moderation 'keys' (keywords) are set, process them.
  66. if ( !empty($mod_keys) ) {
  67. $words = explode("\n", $mod_keys );
  68. foreach ( (array) $words as $word) {
  69. $word = trim($word);
  70. // Skip empty lines.
  71. if ( empty($word) )
  72. continue;
  73. /*
  74. * Do some escaping magic so that '#' (number of) characters in the spam
  75. * words don't break things:
  76. */
  77. $word = preg_quote($word, '#');
  78. /*
  79. * Check the comment fields for moderation keywords. If any are found,
  80. * fail the check for the given field by returning false.
  81. */
  82. $pattern = "#$word#i";
  83. if ( preg_match($pattern, $author) ) return false;
  84. if ( preg_match($pattern, $email) ) return false;
  85. if ( preg_match($pattern, $url) ) return false;
  86. if ( preg_match($pattern, $comment) ) return false;
  87. if ( preg_match($pattern, $user_ip) ) return false;
  88. if ( preg_match($pattern, $user_agent) ) return false;
  89. }
  90. }
  91. /*
  92. * Check if the option to approve comments by previously-approved authors is enabled.
  93. *
  94. * If it is enabled, check whether the comment author has a previously-approved comment,
  95. * as well as whether there are any moderation keywords (if set) present in the author
  96. * email address. If both checks pass, return true. Otherwise, return false.
  97. */
  98. if ( 1 == get_option('comment_whitelist')) {
  99. if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
  100. // expected_slashed ($author, $email)
  101. $ok_to_comment = $wpdb->get_var("SELECT comment_approved FROM $wpdb->comments WHERE comment_author = '$author' AND comment_author_email = '$email' and comment_approved = '1' LIMIT 1");
  102. if ( ( 1 == $ok_to_comment ) &&
  103. ( empty($mod_keys) || false === strpos( $email, $mod_keys) ) )
  104. return true;
  105. else
  106. return false;
  107. } else {
  108. return false;
  109. }
  110. }
  111. return true;
  112. }
  113. /**
  114. * Retrieve the approved comments for post $post_id.
  115. *
  116. * @since 2.0.0
  117. * @since 4.1.0 Refactored to leverage {@see WP_Comment_Query} over a direct query.
  118. *
  119. * @param int $post_id The ID of the post.
  120. * @param array $args Optional. See {@see WP_Comment_Query::query()} for information
  121. * on accepted arguments.
  122. * @return int|array $comments The approved comments, or number of comments if `$count`
  123. * argument is true.
  124. */
  125. function get_approved_comments( $post_id, $args = array() ) {
  126. if ( ! $post_id ) {
  127. return array();
  128. }
  129. $defaults = array(
  130. 'status' => 1,
  131. 'post_id' => $post_id,
  132. 'order' => 'ASC',
  133. );
  134. $r = wp_parse_args( $args, $defaults );
  135. $query = new WP_Comment_Query;
  136. return $query->query( $r );
  137. }
  138. /**
  139. * Retrieves comment data given a comment ID or comment object.
  140. *
  141. * If an object is passed then the comment data will be cached and then returned
  142. * after being passed through a filter. If the comment is empty, then the global
  143. * comment variable will be used, if it is set.
  144. *
  145. * @since 2.0.0
  146. *
  147. * @global wpdb $wpdb WordPress database abstraction object.
  148. *
  149. * @param object|string|int $comment Comment to retrieve.
  150. * @param string $output Optional. OBJECT or ARRAY_A or ARRAY_N constants.
  151. * @return object|array|null Depends on $output value.
  152. */
  153. function get_comment(&$comment, $output = OBJECT) {
  154. global $wpdb;
  155. if ( empty($comment) ) {
  156. if ( isset($GLOBALS['comment']) )
  157. $_comment = & $GLOBALS['comment'];
  158. else
  159. $_comment = null;
  160. } elseif ( is_object($comment) ) {
  161. wp_cache_add($comment->comment_ID, $comment, 'comment');
  162. $_comment = $comment;
  163. } else {
  164. if ( isset($GLOBALS['comment']) && ($GLOBALS['comment']->comment_ID == $comment) ) {
  165. $_comment = & $GLOBALS['comment'];
  166. } elseif ( ! $_comment = wp_cache_get($comment, 'comment') ) {
  167. $_comment = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment));
  168. if ( ! $_comment )
  169. return null;
  170. wp_cache_add($_comment->comment_ID, $_comment, 'comment');
  171. }
  172. }
  173. /**
  174. * Fires after a comment is retrieved.
  175. *
  176. * @since 2.3.0
  177. *
  178. * @param mixed $_comment Comment data.
  179. */
  180. $_comment = apply_filters( 'get_comment', $_comment );
  181. if ( $output == OBJECT ) {
  182. return $_comment;
  183. } elseif ( $output == ARRAY_A ) {
  184. $__comment = get_object_vars($_comment);
  185. return $__comment;
  186. } elseif ( $output == ARRAY_N ) {
  187. $__comment = array_values(get_object_vars($_comment));
  188. return $__comment;
  189. } else {
  190. return $_comment;
  191. }
  192. }
  193. /**
  194. * Retrieve a list of comments.
  195. *
  196. * The comment list can be for the blog as a whole or for an individual post.
  197. *
  198. * @since 2.7.0
  199. *
  200. * @param string|array $args Optional. Array or string of arguments. See {@see WP_Comment_Query::parse_query()}
  201. * for information on accepted arguments. Default empty.
  202. * @return int|array List of comments or number of found comments if `$count` argument is true.
  203. */
  204. function get_comments( $args = '' ) {
  205. $query = new WP_Comment_Query;
  206. return $query->query( $args );
  207. }
  208. /**
  209. * WordPress Comment Query class.
  210. *
  211. * See WP_Comment_Query::__construct() for accepted arguments.
  212. *
  213. * @since 3.1.0
  214. */
  215. class WP_Comment_Query {
  216. /**
  217. * SQL for database query.
  218. *
  219. * @since 4.0.1
  220. * @access public
  221. * @var string
  222. */
  223. public $request;
  224. /**
  225. * Metadata query container
  226. *
  227. * @since 3.5.0
  228. * @access public
  229. * @var object WP_Meta_Query
  230. */
  231. public $meta_query = false;
  232. /**
  233. * Date query container
  234. *
  235. * @since 3.7.0
  236. * @access public
  237. * @var object WP_Date_Query
  238. */
  239. public $date_query = false;
  240. /**
  241. * Query vars set by the user.
  242. *
  243. * @since 3.1.0
  244. * @access public
  245. * @var array
  246. */
  247. public $query_vars;
  248. /**
  249. * Default values for query vars.
  250. *
  251. * @since 4.2.0
  252. * @access public
  253. * @var array
  254. */
  255. public $query_var_defaults;
  256. /**
  257. * List of comments located by the query.
  258. *
  259. * @since 4.0.0
  260. * @access public
  261. * @var array
  262. */
  263. public $comments;
  264. /**
  265. * Make private/protected methods readable for backwards compatibility.
  266. *
  267. * @since 4.0.0
  268. * @access public
  269. *
  270. * @param callable $name Method to call.
  271. * @param array $arguments Arguments to pass when calling.
  272. * @return mixed|bool Return value of the callback, false otherwise.
  273. */
  274. public function __call( $name, $arguments ) {
  275. if ( 'get_search_sql' === $name ) {
  276. return call_user_func_array( array( $this, $name ), $arguments );
  277. }
  278. return false;
  279. }
  280. /**
  281. * Constructor.
  282. *
  283. * Sets up the comment query, based on the query vars passed.
  284. *
  285. * @since 4.2.0
  286. * @access public
  287. *
  288. * @param string|array $query {
  289. * Optional. Array or query string of comment query parameters. Default empty.
  290. *
  291. * @type string $author_email Comment author email address. Default empty.
  292. * @type array $author__in Array of author IDs to include comments for. Default empty.
  293. * @type array $author__not_in Array of author IDs to exclude comments for. Default empty.
  294. * @type array $comment__in Array of comment IDs to include. Default empty.
  295. * @type array $comment__not_in Array of comment IDs to exclude. Default empty.
  296. * @type bool $count Whether to return a comment count (true) or array of comment
  297. * objects (false). Default false.
  298. * @type array $date_query Date query clauses to limit comments by. See WP_Date_Query.
  299. * Default null.
  300. * @type string $fields Comment fields to return. Accepts 'ids' for comment IDs only or
  301. * empty for all fields. Default empty.
  302. * @type int $ID Currently unused.
  303. * @type array $include_unapproved Array of IDs or email addresses of users whose unapproved comments
  304. * will be returned by the query regardless of `$status`. Default empty.
  305. * @type int $karma Karma score to retrieve matching comments for. Default empty.
  306. * @type string $meta_key Include comments with a matching comment meta key. Default empty.
  307. * @type string $meta_value Include comments with a matching comment meta value. Requires
  308. * `$meta_key` to be set. Default empty.
  309. * @type array $meta_query Meta query clauses to limit retrieved comments by.
  310. * See WP_Meta_Query. Default empty.
  311. * @type int $number Maximum number of comments to retrieve. Default null (no limit).
  312. * @type int $offset Number of comments to offset the query. Used to build LIMIT clause.
  313. * Default 0.
  314. * @type string|array $orderby Comment status or array of statuses. To use 'meta_value' or
  315. * 'meta_value_num', `$meta_key` must also be defined. To sort by
  316. * a specific `$meta_query` clause, use that clause's array key.
  317. * Accepts 'comment_agent', 'comment_approved', 'comment_author',
  318. * 'comment_author_email', 'comment_author_IP',
  319. * 'comment_author_url', 'comment_content', 'comment_date',
  320. * 'comment_date_gmt', 'comment_ID', 'comment_karma',
  321. * 'comment_parent', 'comment_post_ID', 'comment_type', 'user_id',
  322. * 'meta_value', 'meta_value_num', the value of $meta_key, and the
  323. * array keys of `$meta_query`. Also accepts false, an empty array,
  324. * or 'none' to disable `ORDER BY` clause.
  325. * Default: 'comment_date_gmt'.
  326. * @type string $order How to order retrieved comments. Accepts 'ASC', 'DESC'.
  327. * Default: 'DESC'.
  328. * @type int $parent Parent ID of comment to retrieve children of. Default empty.
  329. * @type array $post_author__in Array of author IDs to retrieve comments for. Default empty.
  330. * @type array $post_author__not_in Array of author IDs *not* to retrieve comments for. Default empty.
  331. * @type int $post_ID Currently unused.
  332. * @type int $post_id Limit results to those affiliated with a given post ID. Default 0.
  333. * @type array $post__in Array of post IDs to include affiliated comments for. Default empty.
  334. * @type array $post__not_in Array of post IDs to exclude affiliated comments for. Default empty.
  335. * @type int $post_author Comment author ID to limit results by. Default empty.
  336. * @type string $post_status Post status to retrieve affiliated comments for. Default empty.
  337. * @type string $post_type Post type to retrieve affiliated comments for. Default empty.
  338. * @type string $post_name Post name to retrieve affiliated comments for. Default empty.
  339. * @type int $post_parent Post parent ID to retrieve affiliated comments for. Default empty.
  340. * @type string $search Search term(s) to retrieve matching comments for. Default empty.
  341. * @type string $status Comment status to limit results by. Accepts 'hold'
  342. * (`comment_status=0`), 'approve' (`comment_status=1`), 'all', or a
  343. * custom comment status. Default 'all'.
  344. * @type string|array $type Include comments of a given type, or array of types. Accepts
  345. * 'comment', 'pings' (includes 'pingback' and 'trackback'), or any
  346. * custom type string. Default empty.
  347. * @type array $type__in Include comments from a given array of comment types. Default empty.
  348. * @type array $type__not_in Exclude comments from a given array of comment types. Default empty.
  349. * @type int $user_id Include comments for a specific user ID. Default empty.
  350. * }
  351. * @return WP_Comment_Query WP_Comment_Query instance.
  352. */
  353. public function __construct( $query = '' ) {
  354. $this->query_var_defaults = array(
  355. 'author_email' => '',
  356. 'author__in' => '',
  357. 'author__not_in' => '',
  358. 'include_unapproved' => '',
  359. 'fields' => '',
  360. 'ID' => '',
  361. 'comment__in' => '',
  362. 'comment__not_in' => '',
  363. 'karma' => '',
  364. 'number' => '',
  365. 'offset' => '',
  366. 'orderby' => '',
  367. 'order' => 'DESC',
  368. 'parent' => '',
  369. 'post_author__in' => '',
  370. 'post_author__not_in' => '',
  371. 'post_ID' => '',
  372. 'post_id' => 0,
  373. 'post__in' => '',
  374. 'post__not_in' => '',
  375. 'post_author' => '',
  376. 'post_name' => '',
  377. 'post_parent' => '',
  378. 'post_status' => '',
  379. 'post_type' => '',
  380. 'status' => 'all',
  381. 'type' => '',
  382. 'type__in' => '',
  383. 'type__not_in' => '',
  384. 'user_id' => '',
  385. 'search' => '',
  386. 'count' => false,
  387. 'meta_key' => '',
  388. 'meta_value' => '',
  389. 'meta_query' => '',
  390. 'date_query' => null, // See WP_Date_Query
  391. );
  392. if ( ! empty( $query ) ) {
  393. $this->query( $query );
  394. }
  395. }
  396. /**
  397. * Parse arguments passed to the comment query with default query parameters.
  398. *
  399. * @since 4.2.0 Extracted from WP_Comment_Query::query().
  400. *
  401. * @access public
  402. *
  403. * @param string|array $query WP_Comment_Query arguments. See WP_Comment_Query::__construct()
  404. */
  405. public function parse_query( $query = '' ) {
  406. if ( empty( $query ) ) {
  407. $query = $this->query_vars;
  408. }
  409. $this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
  410. do_action_ref_array( 'parse_comment_query', array( &$this ) );
  411. }
  412. /**
  413. * Sets up the WordPress query for retrieving comments.
  414. *
  415. * @since 3.1.0
  416. * @since 4.1.0 Introduced 'comment__in', 'comment__not_in', 'post_author__in',
  417. * 'post_author__not_in', 'author__in', 'author__not_in', 'post__in',
  418. * 'post__not_in', 'include_unapproved', 'type__in', and 'type__not_in'
  419. * arguments to $query_vars.
  420. * @since 4.2.0 Moved parsing to WP_Comment_Query::parse_query().
  421. * @access public
  422. *
  423. * @param string|array $query Array or URL query string of parameters.
  424. * @return array List of comments.
  425. */
  426. public function query( $query ) {
  427. $this->query_vars = wp_parse_args( $query );
  428. return $this->get_comments();
  429. }
  430. /**
  431. * Get a list of comments matching the query vars.
  432. *
  433. * @since 4.2.0
  434. * @access public
  435. *
  436. * @global wpdb $wpdb WordPress database abstraction object.
  437. *
  438. * @return array The list of comments.
  439. */
  440. public function get_comments() {
  441. global $wpdb;
  442. $groupby = '';
  443. $this->parse_query();
  444. // Parse meta query
  445. $this->meta_query = new WP_Meta_Query();
  446. $this->meta_query->parse_query_vars( $this->query_vars );
  447. if ( ! empty( $this->meta_query->queries ) ) {
  448. $meta_query_clauses = $this->meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $this );
  449. }
  450. /**
  451. * Fires before comments are retrieved.
  452. *
  453. * @since 3.1.0
  454. *
  455. * @param WP_Comment_Query &$this Current instance of WP_Comment_Query, passed by reference.
  456. */
  457. do_action_ref_array( 'pre_get_comments', array( &$this ) );
  458. // $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
  459. $key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
  460. $last_changed = wp_cache_get( 'last_changed', 'comment' );
  461. if ( ! $last_changed ) {
  462. $last_changed = microtime();
  463. wp_cache_set( 'last_changed', $last_changed, 'comment' );
  464. }
  465. $cache_key = "get_comments:$key:$last_changed";
  466. if ( $cache = wp_cache_get( $cache_key, 'comment' ) ) {
  467. $this->comments = $cache;
  468. return $this->comments;
  469. }
  470. $where = array();
  471. // Assemble clauses related to 'comment_approved'.
  472. $approved_clauses = array();
  473. // 'status' accepts an array or a comma-separated string.
  474. $status_clauses = array();
  475. $statuses = $this->query_vars['status'];
  476. if ( ! is_array( $statuses ) ) {
  477. $statuses = preg_split( '/[\s,]+/', $statuses );
  478. }
  479. // 'any' overrides other statuses.
  480. if ( ! in_array( 'any', $statuses ) ) {
  481. foreach ( $statuses as $status ) {
  482. switch ( $status ) {
  483. case 'hold' :
  484. $status_clauses[] = "comment_approved = '0'";
  485. break;
  486. case 'approve' :
  487. $status_clauses[] = "comment_approved = '1'";
  488. break;
  489. case 'all' :
  490. case '' :
  491. $status_clauses[] = "( comment_approved = '0' OR comment_approved = '1' )";
  492. break;
  493. default :
  494. $status_clauses[] = $wpdb->prepare( "comment_approved = %s", $status );
  495. break;
  496. }
  497. }
  498. if ( ! empty( $status_clauses ) ) {
  499. $approved_clauses[] = '( ' . implode( ' OR ', $status_clauses ) . ' )';
  500. }
  501. }
  502. // User IDs or emails whose unapproved comments are included, regardless of $status.
  503. if ( ! empty( $this->query_vars['include_unapproved'] ) ) {
  504. $include_unapproved = $this->query_vars['include_unapproved'];
  505. // Accepts arrays or comma-separated strings.
  506. if ( ! is_array( $include_unapproved ) ) {
  507. $include_unapproved = preg_split( '/[\s,]+/', $include_unapproved );
  508. }
  509. $unapproved_ids = $unapproved_emails = array();
  510. foreach ( $include_unapproved as $unapproved_identifier ) {
  511. // Numeric values are assumed to be user ids.
  512. if ( is_numeric( $unapproved_identifier ) ) {
  513. $approved_clauses[] = $wpdb->prepare( "( user_id = %d AND comment_approved = '0' )", $unapproved_identifier );
  514. // Otherwise we match against email addresses.
  515. } else {
  516. $approved_clauses[] = $wpdb->prepare( "( comment_author_email = %s AND comment_approved = '0' )", $unapproved_identifier );
  517. }
  518. }
  519. }
  520. // Collapse comment_approved clauses into a single OR-separated clause.
  521. if ( ! empty( $approved_clauses ) ) {
  522. if ( 1 === count( $approved_clauses ) ) {
  523. $where[] = $approved_clauses[0];
  524. } else {
  525. $where[] = '( ' . implode( ' OR ', $approved_clauses ) . ' )';
  526. }
  527. }
  528. $order = ( 'ASC' == strtoupper( $this->query_vars['order'] ) ) ? 'ASC' : 'DESC';
  529. // Disable ORDER BY with 'none', an empty array, or boolean false.
  530. if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
  531. $orderby = '';
  532. } elseif ( ! empty( $this->query_vars['orderby'] ) ) {
  533. $ordersby = is_array( $this->query_vars['orderby'] ) ?
  534. $this->query_vars['orderby'] :
  535. preg_split( '/[,\s]/', $this->query_vars['orderby'] );
  536. $orderby_array = array();
  537. $found_orderby_comment_ID = false;
  538. foreach ( $ordersby as $_key => $_value ) {
  539. if ( ! $_value ) {
  540. continue;
  541. }
  542. if ( is_int( $_key ) ) {
  543. $_orderby = $_value;
  544. $_order = $order;
  545. } else {
  546. $_orderby = $_key;
  547. $_order = $_value;
  548. }
  549. if ( ! $found_orderby_comment_ID && 'comment_ID' === $_orderby ) {
  550. $found_orderby_comment_ID = true;
  551. }
  552. $parsed = $this->parse_orderby( $_orderby );
  553. if ( ! $parsed ) {
  554. continue;
  555. }
  556. $orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
  557. }
  558. // If no valid clauses were found, order by comment_date_gmt.
  559. if ( empty( $orderby_array ) ) {
  560. $orderby_array[] = "$wpdb->comments.comment_date_gmt $order";
  561. }
  562. // To ensure determinate sorting, always include a comment_ID clause.
  563. if ( ! $found_orderby_comment_ID ) {
  564. $comment_ID_order = '';
  565. // Inherit order from comment_date or comment_date_gmt, if available.
  566. foreach ( $orderby_array as $orderby_clause ) {
  567. if ( preg_match( '/comment_date(?:_gmt)*\ (ASC|DESC)/', $orderby_clause, $match ) ) {
  568. $comment_ID_order = $match[1];
  569. break;
  570. }
  571. }
  572. // If no date-related order is available, use the date from the first available clause.
  573. if ( ! $comment_ID_order ) {
  574. foreach ( $orderby_array as $orderby_clause ) {
  575. if ( false !== strpos( 'ASC', $orderby_clause ) ) {
  576. $comment_ID_order = 'ASC';
  577. } else {
  578. $comment_ID_order = 'DESC';
  579. }
  580. break;
  581. }
  582. }
  583. // Default to DESC.
  584. if ( ! $comment_ID_order ) {
  585. $comment_ID_order = 'DESC';
  586. }
  587. $orderby_array[] = "$wpdb->comments.comment_ID $comment_ID_order";
  588. }
  589. $orderby = implode( ', ', $orderby_array );
  590. } else {
  591. $orderby = "$wpdb->comments.comment_date_gmt $order";
  592. }
  593. $number = absint( $this->query_vars['number'] );
  594. $offset = absint( $this->query_vars['offset'] );
  595. if ( ! empty( $number ) ) {
  596. if ( $offset ) {
  597. $limits = 'LIMIT ' . $offset . ',' . $number;
  598. } else {
  599. $limits = 'LIMIT ' . $number;
  600. }
  601. } else {
  602. $limits = '';
  603. }
  604. if ( $this->query_vars['count'] ) {
  605. $fields = 'COUNT(*)';
  606. } else {
  607. switch ( strtolower( $this->query_vars['fields'] ) ) {
  608. case 'ids':
  609. $fields = "$wpdb->comments.comment_ID";
  610. break;
  611. default:
  612. $fields = "*";
  613. break;
  614. }
  615. }
  616. $join = '';
  617. $post_id = absint( $this->query_vars['post_id'] );
  618. if ( ! empty( $post_id ) ) {
  619. $where[] = $wpdb->prepare( 'comment_post_ID = %d', $post_id );
  620. }
  621. // Parse comment IDs for an IN clause.
  622. if ( ! empty( $this->query_vars['comment__in'] ) ) {
  623. $where[] = 'comment_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['comment__in'] ) ) . ' )';
  624. }
  625. // Parse comment IDs for a NOT IN clause.
  626. if ( ! empty( $this->query_vars['comment__not_in'] ) ) {
  627. $where[] = 'comment_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['comment__not_in'] ) ) . ' )';
  628. }
  629. // Parse comment post IDs for an IN clause.
  630. if ( ! empty( $this->query_vars['post__in'] ) ) {
  631. $where[] = 'comment_post_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__in'] ) ) . ' )';
  632. }
  633. // Parse comment post IDs for a NOT IN clause.
  634. if ( ! empty( $this->query_vars['post__not_in'] ) ) {
  635. $where[] = 'comment_post_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__not_in'] ) ) . ' )';
  636. }
  637. if ( '' !== $this->query_vars['author_email'] ) {
  638. $where[] = $wpdb->prepare( 'comment_author_email = %s', $this->query_vars['author_email'] );
  639. }
  640. if ( '' !== $this->query_vars['karma'] ) {
  641. $where[] = $wpdb->prepare( 'comment_karma = %d', $this->query_vars['karma'] );
  642. }
  643. // Filtering by comment_type: 'type', 'type__in', 'type__not_in'.
  644. $raw_types = array(
  645. 'IN' => array_merge( (array) $this->query_vars['type'], (array) $this->query_vars['type__in'] ),
  646. 'NOT IN' => (array) $this->query_vars['type__not_in'],
  647. );
  648. $comment_types = array();
  649. foreach ( $raw_types as $operator => $_raw_types ) {
  650. $_raw_types = array_unique( $_raw_types );
  651. foreach ( $_raw_types as $type ) {
  652. switch ( $type ) {
  653. // An empty translates to 'all', for backward compatibility
  654. case '':
  655. case 'all' :
  656. break;
  657. case 'comment':
  658. case 'comments':
  659. $comment_types[ $operator ][] = "''";
  660. break;
  661. case 'pings':
  662. $comment_types[ $operator ][] = "'pingback'";
  663. $comment_types[ $operator ][] = "'trackback'";
  664. break;
  665. default:
  666. $comment_types[ $operator ][] = $wpdb->prepare( '%s', $type );
  667. break;
  668. }
  669. }
  670. if ( ! empty( $comment_types[ $operator ] ) ) {
  671. $types_sql = implode( ', ', $comment_types[ $operator ] );
  672. $where[] = "comment_type $operator ($types_sql)";
  673. }
  674. }
  675. if ( '' !== $this->query_vars['parent'] ) {
  676. $where[] = $wpdb->prepare( 'comment_parent = %d', $this->query_vars['parent'] );
  677. }
  678. if ( is_array( $this->query_vars['user_id'] ) ) {
  679. $where[] = 'user_id IN (' . implode( ',', array_map( 'absint', $this->query_vars['user_id'] ) ) . ')';
  680. } elseif ( '' !== $this->query_vars['user_id'] ) {
  681. $where[] = $wpdb->prepare( 'user_id = %d', $this->query_vars['user_id'] );
  682. }
  683. if ( '' !== $this->query_vars['search'] ) {
  684. $search_sql = $this->get_search_sql(
  685. $this->query_vars['search'],
  686. array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' )
  687. );
  688. // Strip leading 'AND'.
  689. $where[] = preg_replace( '/^\s*AND\s*/', '', $search_sql );
  690. }
  691. // If any post-related query vars are passed, join the posts table.
  692. $join_posts_table = false;
  693. $plucked = wp_array_slice_assoc( $this->query_vars, array( 'post_author', 'post_name', 'post_parent', 'post_status', 'post_type' ) );
  694. $post_fields = array_filter( $plucked );
  695. if ( ! empty( $post_fields ) ) {
  696. $join_posts_table = true;
  697. foreach ( $post_fields as $field_name => $field_value ) {
  698. // $field_value may be an array.
  699. $esses = array_fill( 0, count( (array) $field_value ), '%s' );
  700. $where[] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} IN (" . implode( ',', $esses ) . ')', $field_value );
  701. }
  702. }
  703. // Comment author IDs for an IN clause.
  704. if ( ! empty( $this->query_vars['author__in'] ) ) {
  705. $where[] = 'user_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__in'] ) ) . ' )';
  706. }
  707. // Comment author IDs for a NOT IN clause.
  708. if ( ! empty( $this->query_vars['author__not_in'] ) ) {
  709. $where[] = 'user_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__not_in'] ) ) . ' )';
  710. }
  711. // Post author IDs for an IN clause.
  712. if ( ! empty( $this->query_vars['post_author__in'] ) ) {
  713. $join_posts_table = true;
  714. $where[] = 'post_author IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__in'] ) ) . ' )';
  715. }
  716. // Post author IDs for a NOT IN clause.
  717. if ( ! empty( $this->query_vars['post_author__not_in'] ) ) {
  718. $join_posts_table = true;
  719. $where[] = 'post_author NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__not_in'] ) ) . ' )';
  720. }
  721. if ( $join_posts_table ) {
  722. $join = "JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
  723. }
  724. if ( ! empty( $meta_query_clauses ) ) {
  725. $join .= $meta_query_clauses['join'];
  726. // Strip leading 'AND'.
  727. $where[] = preg_replace( '/^\s*AND\s*/', '', $meta_query_clauses['where'] );
  728. if ( ! $this->query_vars['count'] ) {
  729. $groupby = "{$wpdb->comments}.comment_ID";
  730. }
  731. }
  732. $date_query = $this->query_vars['date_query'];
  733. if ( ! empty( $date_query ) && is_array( $date_query ) ) {
  734. $date_query_object = new WP_Date_Query( $date_query, 'comment_date' );
  735. $where[] = preg_replace( '/^\s*AND\s*/', '', $date_query_object->get_sql() );
  736. }
  737. $where = implode( ' AND ', $where );
  738. $pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
  739. /**
  740. * Filter the comment query clauses.
  741. *
  742. * @since 3.1.0
  743. *
  744. * @param array $pieces A compacted array of comment query clauses.
  745. * @param WP_Comment_Query &$this Current instance of WP_Comment_Query, passed by reference.
  746. */
  747. $clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) );
  748. $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
  749. $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
  750. $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
  751. $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
  752. $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
  753. $groupby = isset( $clauses[ 'groupby' ] ) ? $clauses[ 'groupby' ] : '';
  754. if ( $where ) {
  755. $where = 'WHERE ' . $where;
  756. }
  757. if ( $groupby ) {
  758. $groupby = 'GROUP BY ' . $groupby;
  759. }
  760. if ( $orderby ) {
  761. $orderby = "ORDER BY $orderby";
  762. }
  763. $this->request = "SELECT $fields FROM $wpdb->comments $join $where $groupby $orderby $limits";
  764. if ( $this->query_vars['count'] ) {
  765. return $wpdb->get_var( $this->request );
  766. }
  767. if ( 'ids' == $this->query_vars['fields'] ) {
  768. $this->comments = $wpdb->get_col( $this->request );
  769. return array_map( 'intval', $this->comments );
  770. }
  771. $results = $wpdb->get_results( $this->request );
  772. /**
  773. * Filter the comment query results.
  774. *
  775. * @since 3.1.0
  776. *
  777. * @param array $results An array of comments.
  778. * @param WP_Comment_Query &$this Current instance of WP_Comment_Query, passed by reference.
  779. */
  780. $comments = apply_filters_ref_array( 'the_comments', array( $results, &$this ) );
  781. wp_cache_add( $cache_key, $comments, 'comment' );
  782. $this->comments = $comments;
  783. return $this->comments;
  784. }
  785. /**
  786. * Used internally to generate an SQL string for searching across multiple columns
  787. *
  788. * @access protected
  789. * @since 3.1.0
  790. *
  791. * @param string $string
  792. * @param array $cols
  793. * @return string
  794. */
  795. protected function get_search_sql( $string, $cols ) {
  796. global $wpdb;
  797. $like = '%' . $wpdb->esc_like( $string ) . '%';
  798. $searches = array();
  799. foreach ( $cols as $col ) {
  800. $searches[] = $wpdb->prepare( "$col LIKE %s", $like );
  801. }
  802. return ' AND (' . implode(' OR ', $searches) . ')';
  803. }
  804. /**
  805. * Parse and sanitize 'orderby' keys passed to the comment query.
  806. *
  807. * @since 4.2.0
  808. * @access protected
  809. *
  810. * @global wpdb $wpdb WordPress database abstraction object.
  811. *
  812. * @param string $orderby Alias for the field to order by.
  813. * @return string|bool Value to used in the ORDER clause. False otherwise.
  814. */
  815. protected function parse_orderby( $orderby ) {
  816. global $wpdb;
  817. $allowed_keys = array(
  818. 'comment_agent',
  819. 'comment_approved',
  820. 'comment_author',
  821. 'comment_author_email',
  822. 'comment_author_IP',
  823. 'comment_author_url',
  824. 'comment_content',
  825. 'comment_date',
  826. 'comment_date_gmt',
  827. 'comment_ID',
  828. 'comment_karma',
  829. 'comment_parent',
  830. 'comment_post_ID',
  831. 'comment_type',
  832. 'user_id',
  833. );
  834. if ( ! empty( $this->query_vars['meta_key'] ) ) {
  835. $allowed_keys[] = $this->query_vars['meta_key'];
  836. $allowed_keys[] = 'meta_value';
  837. $allowed_keys[] = 'meta_value_num';
  838. }
  839. $meta_query_clauses = $this->meta_query->get_clauses();
  840. if ( $meta_query_clauses ) {
  841. $allowed_keys = array_merge( $allowed_keys, array_keys( $meta_query_clauses ) );
  842. }
  843. $parsed = false;
  844. if ( $orderby == $this->query_vars['meta_key'] || $orderby == 'meta_value' ) {
  845. $parsed = "$wpdb->commentmeta.meta_value";
  846. } else if ( $orderby == 'meta_value_num' ) {
  847. $parsed = "$wpdb->commentmeta.meta_value+0";
  848. } else if ( in_array( $orderby, $allowed_keys ) ) {
  849. if ( isset( $meta_query_clauses[ $orderby ] ) ) {
  850. $meta_clause = $meta_query_clauses[ $orderby ];
  851. $parsed = sprintf( "CAST(%s.meta_value AS %s)", esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) );
  852. } else {
  853. $parsed = "$wpdb->comments.$orderby";
  854. }
  855. }
  856. return $parsed;
  857. }
  858. /**
  859. * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
  860. *
  861. * @since 4.2.0
  862. * @access protected
  863. *
  864. * @param string $order The 'order' query variable.
  865. * @return string The sanitized 'order' query variable.
  866. */
  867. protected function parse_order( $order ) {
  868. if ( ! is_string( $order ) || empty( $order ) ) {
  869. return 'DESC';
  870. }
  871. if ( 'ASC' === strtoupper( $order ) ) {
  872. return 'ASC';
  873. } else {
  874. return 'DESC';
  875. }
  876. }
  877. }
  878. /**
  879. * Retrieve all of the WordPress supported comment statuses.
  880. *
  881. * Comments have a limited set of valid status values, this provides the comment
  882. * status values and descriptions.
  883. *
  884. * @since 2.7.0
  885. *
  886. * @return array List of comment statuses.
  887. */
  888. function get_comment_statuses() {
  889. $status = array(
  890. 'hold' => __('Unapproved'),
  891. /* translators: comment status */
  892. 'approve' => _x('Approved', 'adjective'),
  893. /* translators: comment status */
  894. 'spam' => _x('Spam', 'adjective'),
  895. );
  896. return $status;
  897. }
  898. /**
  899. * The date the last comment was modified.
  900. *
  901. * @since 1.5.0
  902. *
  903. * @global wpdb $wpdb WordPress database abstraction object.
  904. *
  905. * @param string $timezone Which timezone to use in reference to 'gmt', 'blog',
  906. * or 'server' locations.
  907. * @return string Last comment modified date.
  908. */
  909. function get_lastcommentmodified($timezone = 'server') {
  910. global $wpdb;
  911. static $cache_lastcommentmodified = array();
  912. if ( isset($cache_lastcommentmodified[$timezone]) )
  913. return $cache_lastcommentmodified[$timezone];
  914. $add_seconds_server = date('Z');
  915. switch ( strtolower($timezone)) {
  916. case 'gmt':
  917. $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
  918. break;
  919. case 'blog':
  920. $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1");
  921. break;
  922. case 'server':
  923. $lastcommentmodified = $wpdb->get_var($wpdb->prepare("SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server));
  924. break;
  925. }
  926. $cache_lastcommentmodified[$timezone] = $lastcommentmodified;
  927. return $lastcommentmodified;
  928. }
  929. /**
  930. * The amount of comments in a post or total comments.
  931. *
  932. * A lot like {@link wp_count_comments()}, in that they both return comment
  933. * stats (albeit with different types). The {@link wp_count_comments()} actual
  934. * caches, but this function does not.
  935. *
  936. * @since 2.0.0
  937. *
  938. * @global wpdb $wpdb WordPress database abstraction object.
  939. *
  940. * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide.
  941. * @return array The amount of spam, approved, awaiting moderation, and total comments.
  942. */
  943. function get_comment_count( $post_id = 0 ) {
  944. global $wpdb;
  945. $post_id = (int) $post_id;
  946. $where = '';
  947. if ( $post_id > 0 ) {
  948. $where = $wpdb->prepare("WHERE comment_post_ID = %d", $post_id);
  949. }
  950. $totals = (array) $wpdb->get_results("
  951. SELECT comment_approved, COUNT( * ) AS total
  952. FROM {$wpdb->comments}
  953. {$where}
  954. GROUP BY comment_approved
  955. ", ARRAY_A);
  956. $comment_count = array(
  957. "approved" => 0,
  958. "awaiting_moderation" => 0,
  959. "spam" => 0,
  960. "total_comments" => 0
  961. );
  962. foreach ( $totals as $row ) {
  963. switch ( $row['comment_approved'] ) {
  964. case 'spam':
  965. $comment_count['spam'] = $row['total'];
  966. $comment_count["total_comments"] += $row['total'];
  967. break;
  968. case 1:
  969. $comment_count['approved'] = $row['total'];
  970. $comment_count['total_comments'] += $row['total'];
  971. break;
  972. case 0:
  973. $comment_count['awaiting_moderation'] = $row['total'];
  974. $comment_count['total_comments'] += $row['total'];
  975. break;
  976. default:
  977. break;
  978. }
  979. }
  980. return $comment_count;
  981. }
  982. //
  983. // Comment meta functions
  984. //
  985. /**
  986. * Add meta data field to a comment.
  987. *
  988. * @since 2.9.0
  989. * @link https://codex.wordpress.org/Function_Reference/add_comment_meta
  990. *
  991. * @param int $comment_id Comment ID.
  992. * @param string $meta_key Metadata name.
  993. * @param mixed $meta_value Metadata value.
  994. * @param bool $unique Optional, default is false. Whether the same key should not be added.
  995. * @return int|bool Meta ID on success, false on failure.
  996. */
  997. function add_comment_meta($comment_id, $meta_key, $meta_value, $unique = false) {
  998. return add_metadata('comment', $comment_id, $meta_key, $meta_value, $unique);
  999. }
  1000. /**
  1001. * Remove metadata matching criteria from a comment.
  1002. *
  1003. * You can match based on the key, or key and value. Removing based on key and
  1004. * value, will keep from removing duplicate metadata with the same key. It also
  1005. * allows removing all metadata matching key, if needed.
  1006. *
  1007. * @since 2.9.0
  1008. * @link https://codex.wordpress.org/Function_Reference/delete_comment_meta
  1009. *
  1010. * @param int $comment_id comment ID
  1011. * @param string $meta_key Metadata name.
  1012. * @param mixed $meta_value Optional. Metadata value.
  1013. * @return bool True on success, false on failure.
  1014. */
  1015. function delete_comment_meta($comment_id, $meta_key, $meta_value = '') {
  1016. return delete_metadata('comment', $comment_id, $meta_key, $meta_value);
  1017. }
  1018. /**
  1019. * Retrieve comment meta field for a comment.
  1020. *
  1021. * @since 2.9.0
  1022. * @link https://codex.wordpress.org/Function_Reference/get_comment_meta
  1023. *
  1024. * @param int $comment_id Comment ID.
  1025. * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
  1026. * @param bool $single Whether to return a single value.
  1027. * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
  1028. * is true.
  1029. */
  1030. function get_comment_meta($comment_id, $key = '', $single = false) {
  1031. return get_metadata('comment', $comment_id, $key, $single);
  1032. }
  1033. /**
  1034. * Update comment meta field based on comment ID.
  1035. *
  1036. * Use the $prev_value parameter to differentiate between meta fields with the
  1037. * same key and comment ID.
  1038. *
  1039. * If the meta field for the comment does not exist, it will be added.
  1040. *
  1041. * @since 2.9.0
  1042. * @link https://codex.wordpress.org/Function_Reference/update_comment_meta
  1043. *
  1044. * @param int $comment_id Comment ID.
  1045. * @param string $meta_key Metadata key.
  1046. * @param mixed $meta_value Metadata value.
  1047. * @param mixed $prev_value Optional. Previous value to check before removing.
  1048. * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
  1049. */
  1050. function update_comment_meta($comment_id, $meta_key, $meta_value, $prev_value = '') {
  1051. return update_metadata('comment', $comment_id, $meta_key, $meta_value, $prev_value);
  1052. }
  1053. /**
  1054. * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
  1055. * to recall previous comments by this commentator that are still held in moderation.
  1056. *
  1057. * @param object $comment Comment object.
  1058. * @param object $user Comment author's object.
  1059. *
  1060. * @since 3.4.0
  1061. */
  1062. function wp_set_comment_cookies($comment, $user) {
  1063. if ( $user->exists() )
  1064. return;
  1065. /**
  1066. * Filter the lifetime of the comment cookie in seconds.
  1067. *
  1068. * @since 2.8.0
  1069. *
  1070. * @param int $seconds Comment cookie lifetime. Default 30000000.
  1071. */
  1072. $comment_cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 );
  1073. $secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
  1074. setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
  1075. setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
  1076. setcookie( 'comment_author_url_' . COOKIEHASH, esc_url($comment->comment_author_url), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
  1077. }
  1078. /**
  1079. * Sanitizes the cookies sent to the user already.
  1080. *
  1081. * Will only do anything if the cookies have already been created for the user.
  1082. * Mostly used after cookies had been sent to use elsewhere.
  1083. *
  1084. * @since 2.0.4
  1085. */
  1086. function sanitize_comment_cookies() {
  1087. if ( isset( $_COOKIE['comment_author_' . COOKIEHASH] ) ) {
  1088. /**
  1089. * Filter the comment author's name cookie before it is set.
  1090. *
  1091. * When this filter hook is evaluated in wp_filter_comment(),
  1092. * the comment author's name string is passed.
  1093. *
  1094. * @since 1.5.0
  1095. *
  1096. * @param string $author_cookie The comment author name cookie.
  1097. */
  1098. $comment_author = apply_filters( 'pre_comment_author_name', $_COOKIE['comment_author_' . COOKIEHASH] );
  1099. $comment_author = wp_unslash($comment_author);
  1100. $comment_author = esc_attr($comment_author);
  1101. $_COOKIE['comment_author_' . COOKIEHASH] = $comment_author;
  1102. }
  1103. if ( isset( $_COOKIE['comment_author_email_' . COOKIEHASH] ) ) {
  1104. /**
  1105. * Filter the comment author's email cookie before it is set.
  1106. *
  1107. * When this filter hook is evaluated in wp_filter_comment(),
  1108. * the comment author's email string is passed.
  1109. *
  1110. * @since 1.5.0
  1111. *
  1112. * @param string $author_email_cookie The comment author email cookie.
  1113. */
  1114. $comment_author_email = apply_filters( 'pre_comment_author_email', $_COOKIE['comment_author_email_' . COOKIEHASH] );
  1115. $comment_author_email = wp_unslash($comment_author_email);
  1116. $comment_author_email = esc_attr($comment_author_email);
  1117. $_COOKIE['comment_author_email_'.COOKIEHASH] = $comment_author_email;
  1118. }
  1119. if ( isset( $_COOKIE['comment_author_url_' . COOKIEHASH] ) ) {
  1120. /**
  1121. * Filter the comment author's URL cookie before it is set.
  1122. *
  1123. * When this filter hook is evaluated in wp_filter_comment(),
  1124. * the comment author's URL string is passed.
  1125. *
  1126. * @since 1.5.0
  1127. *
  1128. * @param string $author_url_cookie The comment author URL cookie.
  1129. */
  1130. $comment_author_url = apply_filters( 'pre_comment_author_url', $_COOKIE['comment_author_url_' . COOKIEHASH] );
  1131. $comment_author_url = wp_unslash($comment_author_url);
  1132. $_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
  1133. }
  1134. }
  1135. /**
  1136. * Validates whether this comment is allowed to be made.
  1137. *
  1138. * @since 2.0.0
  1139. *
  1140. * @global wpdb $wpdb WordPress database abstraction object.
  1141. *
  1142. * @param array $commentdata Contains information on the comment
  1143. * @return mixed Signifies the approval status (0|1|'spam')
  1144. */
  1145. function wp_allow_comment( $commentdata ) {
  1146. global $wpdb;
  1147. // Simple duplicate check
  1148. // expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
  1149. $dupe = $wpdb->prepare(
  1150. "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
  1151. wp_unslash( $commentdata['comment_post_ID'] ),
  1152. wp_unslash( $commentdata['comment_parent'] ),
  1153. wp_unslash( $commentdata['comment_author'] )
  1154. );
  1155. if ( $commentdata['comment_author_email'] ) {
  1156. $dupe .= $wpdb->prepare(
  1157. "OR comment_author_email = %s ",
  1158. wp_unslash( $commentdata['comment_author_email'] )
  1159. );
  1160. }
  1161. $dupe .= $wpdb->prepare(
  1162. ") AND comment_content = %s LIMIT 1",
  1163. wp_unslash( $commentdata['comment_content'] )
  1164. );
  1165. if ( $wpdb->get_var( $dupe ) ) {
  1166. /**
  1167. * Fires immediately after a duplicate comment is detected.
  1168. *
  1169. * @since 3.0.0
  1170. *
  1171. * @param array $commentdata Comment data.
  1172. */
  1173. do_action( 'comment_duplicate_trigger', $commentdata );
  1174. if ( defined( 'DOING_AJAX' ) ) {
  1175. die( __('Duplicate comment detected; it looks as though you&#8217;ve already said that!') );
  1176. }
  1177. wp_die( __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ), 409 );
  1178. }
  1179. /**
  1180. * Fires immediately before a comment is marked approved.
  1181. *
  1182. * Allows checking for comment flooding.
  1183. *
  1184. * @since 2.3.0
  1185. *
  1186. * @param string $comment_author_IP Comment author's IP address.
  1187. * @param string $comment_author_email Comment author's email.
  1188. * @param string $comment_date_gmt GMT date the comment was posted.
  1189. */
  1190. do_action(
  1191. 'check_comment_flood',
  1192. $commentdata['comment_author_IP'],
  1193. $commentdata['comment_author_email'],
  1194. $commentdata['comment_date_gmt']
  1195. );
  1196. if ( ! empty( $commentdata['user_id'] ) ) {
  1197. $user = get_userdata( $commentdata['user_id'] );
  1198. $post_author = $wpdb->get_var( $wpdb->prepare(
  1199. "SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
  1200. $commentdata['comment_post_ID']
  1201. ) );
  1202. }
  1203. if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
  1204. // The author and the admins get respect.
  1205. $approved = 1;
  1206. } else {
  1207. // Everyone else's comments will be checked.
  1208. if ( check_comment(
  1209. $commentdata['comment_author'],
  1210. $commentdata['comment_author_email'],
  1211. $commentdata['comment_author_url'],
  1212. $commentdata['comment_content'],
  1213. $commentdata['comment_author_IP'],
  1214. $commentdata['comment_agent'],
  1215. $commentdata['comment_type']
  1216. ) ) {
  1217. $approved = 1;
  1218. } else {
  1219. $approved = 0;
  1220. }
  1221. if ( wp_blacklist_check(
  1222. $commentdata['comment_author'],
  1223. $commentdata['comment_author_email'],
  1224. $commentdata['comment_author_url'],
  1225. $commentdata['comment_content'],
  1226. $commentdata['comment_author_IP'],
  1227. $commentdata['comment_agent']
  1228. ) ) {
  1229. $approved = 'spam';
  1230. }
  1231. }
  1232. /**
  1233. * Filter a comment's approval status before it is set.
  1234. *
  1235. * @since 2.1.0
  1236. *
  1237. * @param bool|string $approved The approval status. Accepts 1, 0, or 'spam'.
  1238. * @param array $commentdata Comment data.
  1239. */
  1240. $approved = apply_filters( 'pre_comment_approved', $approved, $commentdata );
  1241. return $approved;
  1242. }
  1243. /**
  1244. * Check whether comment flooding is occurring.
  1245. *
  1246. * Won't run, if current user can manage options, so to not block
  1247. * administrators.
  1248. *
  1249. * @since 2.3.0
  1250. *
  1251. * @global wpdb $wpdb WordPress database abstraction object.
  1252. *
  1253. * @param string $ip Comment IP.
  1254. * @param string $email Comment author email address.
  1255. * @param string $date MySQL time string.
  1256. */
  1257. function check_comment_flood_db( $ip, $email, $date ) {
  1258. global $wpdb;
  1259. if ( current_user_can( 'manage_options' ) )
  1260. return; // don't throttle admins
  1261. $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
  1262. if ( $lasttime = $wpdb->get_var( $wpdb->prepare( "SELECT `comment_date_gmt` FROM `$wpdb->comments` WHERE `comment_date_gmt` >= %s AND ( `comment_author_IP` = %s OR `comment_author_email` = %s ) ORDER BY `comment_date_gmt` DESC LIMIT 1", $hour_ago, $ip, $email ) ) ) {
  1263. $time_lastcomment = mysql2date('U', $lasttime, false);
  1264. $time_newcomment = mysql2date('U', $date, false);
  1265. /**
  1266. * Filter the comment flood status.
  1267. *
  1268. * @since 2.1.0
  1269. *
  1270. * @param bool $bool Whether a comment flood is occurring. Default false.
  1271. * @param int $time_lastcomment Timestamp of when the last comment was posted.
  1272. * @param int $time_newcomment Timestamp of when the new comment was posted.
  1273. */
  1274. $flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
  1275. if ( $flood_die ) {
  1276. /**
  1277. * Fires before the comment flood message is triggered.
  1278. *
  1279. * @since 1.5.0
  1280. *
  1281. * @param int $time_lastcomment Timestamp of when the last comment was posted.
  1282. * @param int $time_newcomment Timestamp of when the new comment was posted.
  1283. */
  1284. do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
  1285. if ( defined('DOING_AJAX') )
  1286. die( __('You are posting comments too quickly. Slow down.') );
  1287. wp_die( __( 'You are posting comments too quickly. Slow down.' ), 429 );
  1288. }
  1289. }
  1290. }
  1291. /**
  1292. * Separates an array of comments into an array keyed by comment_type.
  1293. *
  1294. * @since 2.7.0
  1295. *
  1296. * @param array $comments Array of comments
  1297. * @return array Array of comments keyed by comment_type.
  1298. */
  1299. function separate_comments(&$comments) {
  1300. $comments_by_type = array('comment' => array(), 'trackback' => array(), 'pingback' => array(), 'pings' => array());
  1301. $count = count($comments);
  1302. for ( $i = 0; $i < $count; $i++ ) {
  1303. $type = $comments[$i]->comment_type;
  1304. if ( empty($type) )
  1305. $type = 'comment';
  1306. $comments_by_type[$type][] = &$comments[$i];
  1307. if ( 'trackback' == $type || 'pingback' == $type )
  1308. $comments_by_type['pings'][] = &$comments[$i];
  1309. }
  1310. return $comments_by_type;
  1311. }
  1312. /**
  1313. * Calculate the total number of comment pages.
  1314. *
  1315. * @since 2.7.0
  1316. *
  1317. * @uses Walker_Comment
  1318. *
  1319. * @param array $comments Optional array of comment objects. Defaults to $wp_query->comments
  1320. * @param int $per_page Optional comments per page.
  1321. * @param boolean $threaded Optional control over flat or threaded comments.
  1322. * @return int Number of comment pages.
  1323. */
  1324. function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
  1325. global $wp_query;
  1326. if ( null === $comments && null === $per_page && null === $threaded && !empty($wp_query->max_num_comment_pages) )
  1327. return $wp_query->max_num_comment_pages;
  1328. if ( ( ! $comm