home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / comment.php < prev    next >
Encoding:
PHP Script  |  2017-10-23  |  101.9 KB  |  3,148 lines

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