[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> comment.php (source)

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


Generated: Tue Sep 17 01:00:03 2019 Cross-referenced by PHPXref 0.7.1