function get_comment_meta( $comment_id, $key = '', $single = false ) {
return get_metadata( 'comment', $comment_id, $key, $single );
* Update comment meta field based on comment ID.
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and comment ID.
* If the meta field for the comment does not exist, it will be added.
* @link https://developer.wordpress.org/reference/functions/update_comment_meta/
* @param int $comment_id Comment ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before updating.
* If specified, only update existing metadata entries with
* this value. Otherwise, update all entries. Default empty.
* @return int|bool Meta ID if the key didn't exist, true on successful update,
* false on failure or if the value passed to the function
* is the same as the one that is already in the database.
function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
* Queues comments for metadata lazy-loading.
* @param WP_Comment[] $comments Array of comment objects.
function wp_queue_comments_for_comment_meta_lazyload( $comments ) {
// Don't use `wp_list_pluck()` to avoid by-reference manipulation.
if ( is_array( $comments ) ) {
foreach ( $comments as $comment ) {
if ( $comment instanceof WP_Comment ) {
$comment_ids[] = $comment->comment_ID;
$lazyloader = wp_metadata_lazyloader();
$lazyloader->queue_objects( 'comment', $comment_ids );
* Sets the cookies used to store an unauthenticated commentator's identity. Typically used
* to recall previous comments by this commentator that are still held in moderation.
* @since 4.9.6 The `$cookies_consent` parameter was added.
* @param WP_Comment $comment Comment object.
* @param WP_User $user Comment author's user object. The user may not exist.
* @param bool $cookies_consent Optional. Comment author's consent to store cookies. Default true.
function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) {
// If the user already exists, or the user opted out of cookies, don't set cookies.
if ( false === $cookies_consent ) {
// Remove any existing cookies.
$past = time() - YEAR_IN_SECONDS;
setcookie( 'comment_author_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'comment_author_email_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
setcookie( 'comment_author_url_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
* Filters the lifetime of the comment cookie in seconds.
* @param int $seconds Comment cookie lifetime. Default 30000000.
$comment_cookie_lifetime = time() + apply_filters( 'comment_cookie_lifetime', 30000000 );
$secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
* Sanitizes the cookies sent to the user already.
* Will only do anything if the cookies have already been created for the user.
* Mostly used after cookies had been sent to use elsewhere.
function sanitize_comment_cookies() {
if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
* Filters the comment author's name cookie before it is set.
* When this filter hook is evaluated in wp_filter_comment(),
* the comment author's name string is passed.
* @param string $author_cookie The comment author name cookie.
$comment_author = apply_filters( 'pre_comment_author_name', $_COOKIE[ 'comment_author_' . COOKIEHASH ] );
$comment_author = wp_unslash( $comment_author );
$comment_author = esc_attr( $comment_author );
$_COOKIE[ 'comment_author_' . COOKIEHASH ] = $comment_author;
if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
* Filters the comment author's email cookie before it is set.
* When this filter hook is evaluated in wp_filter_comment(),
* the comment author's email string is passed.
* @param string $author_email_cookie The comment author email cookie.
$comment_author_email = apply_filters( 'pre_comment_author_email', $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] );
$comment_author_email = wp_unslash( $comment_author_email );
$comment_author_email = esc_attr( $comment_author_email );
$_COOKIE[ 'comment_author_email_' . COOKIEHASH ] = $comment_author_email;
if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
* Filters the comment author's URL cookie before it is set.
* When this filter hook is evaluated in wp_filter_comment(),
* the comment author's URL string is passed.
* @param string $author_url_cookie The comment author URL cookie.
$comment_author_url = apply_filters( 'pre_comment_author_url', $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] );
$comment_author_url = wp_unslash( $comment_author_url );
$_COOKIE[ 'comment_author_url_' . COOKIEHASH ] = $comment_author_url;
* Validates whether this comment is allowed to be made.
* @since 4.7.0 The `$avoid_die` parameter was added, allowing the function
* to return a WP_Error object instead of dying.
* @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
* @global wpdb $wpdb WordPress database abstraction object.
* @param array $commentdata Contains information on the comment.
* @param bool $wp_error When true, a disallowed comment will result in the function
* returning a WP_Error object, rather than executing wp_die().
* @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam'|'trash').
* If `$wp_error` is true, disallowed comments return a WP_Error.
function wp_allow_comment( $commentdata, $wp_error = false ) {
// Simple duplicate check.
// expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
"SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
wp_unslash( $commentdata['comment_post_ID'] ),
wp_unslash( $commentdata['comment_parent'] ),
wp_unslash( $commentdata['comment_author'] )
if ( $commentdata['comment_author_email'] ) {
'AND comment_author_email = %s ',
wp_unslash( $commentdata['comment_author_email'] )
') AND comment_content = %s LIMIT 1',
wp_unslash( $commentdata['comment_content'] )
$dupe_id = $wpdb->get_var( $dupe );
* Filters the ID, if any, of the duplicate comment found when creating a new comment.
* Return an empty value from this filter to allow what WP considers a duplicate comment.
* @param int $dupe_id ID of the comment identified as a duplicate.
* @param array $commentdata Data for the comment being created.
$dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata );
* Fires immediately after a duplicate comment is detected.
* @param array $commentdata Comment data.
do_action( 'comment_duplicate_trigger', $commentdata );
* Filters duplicate comment error message.
* @param string $comment_duplicate_message Duplicate comment error message.
$comment_duplicate_message = apply_filters( 'comment_duplicate_message', __( 'Duplicate comment detected; it looks as though you’ve already said that!' ) );
return new WP_Error( 'comment_duplicate', $comment_duplicate_message, 409 );
die( $comment_duplicate_message );
wp_die( $comment_duplicate_message, 409 );
* Fires immediately before a comment is marked approved.
* Allows checking for comment flooding.
* @since 4.7.0 The `$avoid_die` parameter was added.
* @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
* @param string $comment_author_IP Comment author's IP address.
* @param string $comment_author_email Comment author's email.
* @param string $comment_date_gmt GMT date the comment was posted.
* @param bool $wp_error Whether to return a WP_Error object instead of executing
* wp_die() or die() if a comment flood is occurring.
$commentdata['comment_author_IP'],
$commentdata['comment_author_email'],
$commentdata['comment_date_gmt'],
* Filters whether a comment is part of a comment flood.
* The default check is wp_check_comment_flood(). See check_comment_flood_db().
* @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
* @param bool $is_flood Is a comment flooding occurring? Default false.
* @param string $comment_author_IP Comment author's IP address.
* @param string $comment_author_email Comment author's email.
* @param string $comment_date_gmt GMT date the comment was posted.
* @param bool $wp_error Whether to return a WP_Error object instead of executing
* wp_die() or die() if a comment flood is occurring.
$is_flood = apply_filters(
$commentdata['comment_author_IP'],
$commentdata['comment_author_email'],
$commentdata['comment_date_gmt'],
/** This filter is documented in wp-includes/comment-template.php */
$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
return new WP_Error( 'comment_flood', $comment_flood_message, 429 );
if ( ! empty( $commentdata['user_id'] ) ) {
$user = get_userdata( $commentdata['user_id'] );
$post_author = $wpdb->get_var(
"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
$commentdata['comment_post_ID']
if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
// The author and the admins get respect.
// Everyone else's comments will be checked.
$commentdata['comment_author'],
$commentdata['comment_author_email'],
$commentdata['comment_author_url'],
$commentdata['comment_content'],
$commentdata['comment_author_IP'],
$commentdata['comment_agent'],
$commentdata['comment_type']
if ( wp_check_comment_disallowed_list(
$commentdata['comment_author'],
$commentdata['comment_author_email'],
$commentdata['comment_author_url'],
$commentdata['comment_content'],
$commentdata['comment_author_IP'],
$commentdata['comment_agent']
$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
* Filters a comment's approval status before it is set.
* @since 4.9.0 Returning a WP_Error value from the filter will short-circuit comment insertion
* and allow skipping further processing.
* @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
* @param array $commentdata Comment data.
return apply_filters( 'pre_comment_approved', $approved, $commentdata );
* Hooks WP's native database-based comment-flood check.
* This wrapper maintains backward compatibility with plugins that expect to
* be able to unhook the legacy check_comment_flood_db() function from
* 'check_comment_flood' using remove_action().
* @since 4.7.0 Converted to be an add_filter() wrapper.
function check_comment_flood_db() {
add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
* Checks whether comment flooding is occurring.
* Won't run, if current user can manage options, so to not block
* @global wpdb $wpdb WordPress database abstraction object.
* @param bool $is_flood Is a comment flooding occurring?
* @param string $ip Comment author's IP address.
* @param string $email Comment author's email address.
* @param string $date MySQL time string.
* @param bool $avoid_die When true, a disallowed comment will result in the function
* returning without executing wp_die() or die(). Default false.
* @return bool Whether comment flooding is occurring.
function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {
// Another callback has declared a flood. Trust it.
if ( true === $is_flood ) {
// Don't throttle admins or moderators.
if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
$hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
if ( is_user_logged_in() ) {
$user = get_current_user_id();
$check_column = '`user_id`';
$check_column = '`comment_author_IP`';
"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",
$lasttime = $wpdb->get_var( $sql );
$time_lastcomment = mysql2date( 'U', $lasttime, false );
$time_newcomment = mysql2date( 'U', $date, false );
* Filters the comment flood status.
* @param bool $bool Whether a comment flood is occurring. Default false.
* @param int $time_lastcomment Timestamp of when the last comment was posted.
* @param int $time_newcomment Timestamp of when the new comment was posted.
$flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
* Fires before the comment flood message is triggered.
* @param int $time_lastcomment Timestamp of when the last comment was posted.
* @param int $time_newcomment Timestamp of when the new comment was posted.
do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
* Filters the comment flood error message.
* @param string $comment_flood_message Comment flood error message.
$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
die( $comment_flood_message );
wp_die( $comment_flood_message, 429 );
* Separates an array of comments into an array keyed by comment_type.
* @param WP_Comment[] $comments Array of comments
* @return WP_Comment[] Array of comments keyed by comment_type.
function separate_comments( &$comments ) {
$comments_by_type = array(
$count = count( $comments );
for ( $i = 0; $i < $count; $i++ ) {
$type = $comments[ $i ]->comment_type;
$comments_by_type[ $type ][] = &$comments[ $i ];
if ( 'trackback' === $type || 'pingback' === $type ) {
$comments_by_type['pings'][] = &$comments[ $i ];
return $comments_by_type;