: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param int $comment_post_id Post ID.
* @param int $comment_parent Parent comment ID.
do_action( 'comment_reply_to_unapproved_comment', $comment_post_id, $comment_parent );
return new WP_Error( 'comment_reply_to_unapproved_comment', __( 'Sorry, replies to unapproved comments are not allowed.' ), 403 );
$post = get_post( $comment_post_id );
if ( empty( $post->comment_status ) ) {
* Fires when a comment is attempted on a post that does not exist.
* @param int $comment_post_id Post ID.
do_action( 'comment_id_not_found', $comment_post_id );
return new WP_Error( 'comment_id_not_found' );
// get_post_status() will get the parent status for attachments.
$status = get_post_status( $post );
if ( ( 'private' === $status ) && ! current_user_can( 'read_post', $comment_post_id ) ) {
return new WP_Error( 'comment_id_not_found' );
$status_obj = get_post_status_object( $status );
if ( ! comments_open( $comment_post_id ) ) {
* Fires when a comment is attempted on a post that has comments closed.
* @param int $comment_post_id Post ID.
do_action( 'comment_closed', $comment_post_id );
return new WP_Error( 'comment_closed', __( 'Sorry, comments are closed for this item.' ), 403 );
} elseif ( 'trash' === $status ) {
* Fires when a comment is attempted on a trashed post.
* @param int $comment_post_id Post ID.
do_action( 'comment_on_trash', $comment_post_id );
return new WP_Error( 'comment_on_trash' );
} elseif ( ! $status_obj->public && ! $status_obj->private ) {
* Fires when a comment is attempted on a post in draft mode.
* @param int $comment_post_id Post ID.
do_action( 'comment_on_draft', $comment_post_id );
if ( current_user_can( 'read_post', $comment_post_id ) ) {
return new WP_Error( 'comment_on_draft', __( 'Sorry, comments are not allowed for this item.' ), 403 );
return new WP_Error( 'comment_on_draft' );
} elseif ( post_password_required( $comment_post_id ) ) {
* Fires when a comment is attempted on a password-protected post.
* @param int $comment_post_id Post ID.
do_action( 'comment_on_password_protected', $comment_post_id );
return new WP_Error( 'comment_on_password_protected' );
* Fires before a comment is posted.
* @param int $comment_post_id Post ID.
do_action( 'pre_comment_on_post', $comment_post_id );
// If the user is logged in.
$user = wp_get_current_user();
if ( empty( $user->display_name ) ) {
$user->display_name = $user->user_login;
$comment_author = $user->display_name;
$comment_author_email = $user->user_email;
$comment_author_url = $user->user_url;
if ( current_user_can( 'unfiltered_html' ) ) {
if ( ! isset( $comment_data['_wp_unfiltered_html_comment'] )
|| ! wp_verify_nonce( $comment_data['_wp_unfiltered_html_comment'], 'unfiltered-html-comment_' . $comment_post_id )
kses_remove_filters(); // Start with a clean slate.
kses_init_filters(); // Set up the filters.
remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
add_filter( 'pre_comment_content', 'wp_filter_kses' );
if ( get_option( 'comment_registration' ) ) {
return new WP_Error( 'not_logged_in', __( 'Sorry, you must be logged in to comment.' ), 403 );
$comment_type = 'comment';
if ( get_option( 'require_name_email' ) && ! $user->exists() ) {
if ( '' == $comment_author_email || '' == $comment_author ) {
return new WP_Error( 'require_name_email', __( '<strong>Error:</strong> Please fill the required fields.' ), 200 );
} elseif ( ! is_email( $comment_author_email ) ) {
return new WP_Error( 'require_valid_email', __( '<strong>Error:</strong> Please enter a valid email address.' ), 200 );
'comment_post_ID' => $comment_post_id,
* Filters whether an empty comment should be allowed.
* @param bool $allow_empty_comment Whether to allow empty comments. Default false.
* @param array $commentdata Array of comment data to be sent to wp_insert_comment().
$allow_empty_comment = apply_filters( 'allow_empty_comment', false, $commentdata );
if ( '' === $comment_content && ! $allow_empty_comment ) {
return new WP_Error( 'require_valid_comment', __( '<strong>Error:</strong> Please type your comment text.' ), 200 );
$check_max_lengths = wp_check_comment_data_max_lengths( $commentdata );
if ( is_wp_error( $check_max_lengths ) ) {
return $check_max_lengths;
$comment_id = wp_new_comment( wp_slash( $commentdata ), true );
if ( is_wp_error( $comment_id ) ) {
return new WP_Error( 'comment_save_error', __( '<strong>Error:</strong> The comment could not be saved. Please try again later.' ), 500 );
return get_comment( $comment_id );
* Registers the personal data exporter for comments.
* @param array[] $exporters An array of personal data exporters.
* @return array[] An array of personal data exporters.
function wp_register_comment_personal_data_exporter( $exporters ) {
$exporters['wordpress-comments'] = array(
'exporter_friendly_name' => __( 'WordPress Comments' ),
'callback' => 'wp_comments_personal_data_exporter',
* Finds and exports personal data associated with an email address from the comments table.
* @param string $email_address The comment author email address.
* @param int $page Comment page number.
* An array of personal data.
* @type array[] $data An array of personal data arrays.
* @type bool $done Whether the exporter is finished.
function wp_comments_personal_data_exporter( $email_address, $page = 1 ) {
// Limit us to 500 comments at a time to avoid timing out.
$data_to_export = array();
$comments = get_comments(
'author_email' => $email_address,
'orderby' => 'comment_ID',
'update_comment_meta_cache' => false,
$comment_prop_to_export = array(
'comment_author' => __( 'Comment Author' ),
'comment_author_email' => __( 'Comment Author Email' ),
'comment_author_url' => __( 'Comment Author URL' ),
'comment_author_IP' => __( 'Comment Author IP' ),
'comment_agent' => __( 'Comment Author User Agent' ),
'comment_date' => __( 'Comment Date' ),
'comment_content' => __( 'Comment Content' ),
'comment_link' => __( 'Comment URL' ),
foreach ( (array) $comments as $comment ) {
$comment_data_to_export = array();
foreach ( $comment_prop_to_export as $key => $name ) {
case 'comment_author_email':
case 'comment_author_url':
case 'comment_author_IP':
$value = $comment->{$key};
$value = get_comment_text( $comment->comment_ID );
$value = get_comment_link( $comment->comment_ID );
'<a href="%s" target="_blank" rel="noopener">%s</a>',
if ( ! empty( $value ) ) {
$comment_data_to_export[] = array(
$data_to_export[] = array(
'group_id' => 'comments',
'group_label' => __( 'Comments' ),
'group_description' => __( 'User’s comment data.' ),
'item_id' => "comment-{$comment->comment_ID}",
'data' => $comment_data_to_export,
$done = count( $comments ) < $number;
'data' => $data_to_export,
* Registers the personal data eraser for comments.
* @param array $erasers An array of personal data erasers.
* @return array An array of personal data erasers.
function wp_register_comment_personal_data_eraser( $erasers ) {
$erasers['wordpress-comments'] = array(
'eraser_friendly_name' => __( 'WordPress Comments' ),
'callback' => 'wp_comments_personal_data_eraser',
* Erases personal data associated with an email address from the comments table.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $email_address The comment author email address.
* @param int $page Comment page number.
* @type bool $items_removed Whether items were actually removed.
* @type bool $items_retained Whether items were retained.
* @type string[] $messages An array of messages to add to the personal data export file.
* @type bool $done Whether the eraser is finished.
function wp_comments_personal_data_eraser( $email_address, $page = 1 ) {
if ( empty( $email_address ) ) {
'items_removed' => false,
'items_retained' => false,
// Limit us to 500 comments at a time to avoid timing out.
$comments = get_comments(
'author_email' => $email_address,
'orderby' => 'comment_ID',
'include_unapproved' => true,
/* translators: Name of a comment's author after being anonymized. */
$anon_author = __( 'Anonymous' );
foreach ( (array) $comments as $comment ) {
$anonymized_comment = array();
$anonymized_comment['comment_agent'] = '';
$anonymized_comment['comment_author'] = $anon_author;
$anonymized_comment['comment_author_email'] = '';
$anonymized_comment['comment_author_IP'] = wp_privacy_anonymize_data( 'ip', $comment->comment_author_IP );
$anonymized_comment['comment_author_url'] = '';
$anonymized_comment['user_id'] = 0;
$comment_id = (int) $comment->comment_ID;
* Filters whether to anonymize the comment.
* @param bool|string $anon_message Whether to apply the comment anonymization (bool) or a custom
* message (string). Default true.
* @param WP_Comment $comment WP_Comment object.
* @param array $anonymized_comment Anonymized comment data.
$anon_message = apply_filters( 'wp_anonymize_comment', true, $comment, $anonymized_comment );
if ( true !== $anon_message ) {
if ( $anon_message && is_string( $anon_message ) ) {
$messages[] = esc_html( $anon_message );
/* translators: %d: Comment ID. */
$messages[] = sprintf( __( 'Comment %d contains personal data but could not be anonymized.' ), $comment_id );
'comment_ID' => $comment_id,
$updated = $wpdb->update( $wpdb->comments, $anonymized_comment, $args );
clean_comment_cache( $comment_id );
$done = count( $comments ) < $number;
'items_removed' => $items_removed,
'items_retained' => $items_retained,
* Sets the last changed time for the 'comment' cache group.
function wp_cache_set_comments_last_changed() {
wp_cache_set_last_changed( 'comment' );
* Updates the comment type for a batch of comments.
* @global wpdb $wpdb WordPress database abstraction object.
function _wp_batch_update_comment_type() {
$lock_name = 'update_comment_type.lock';
$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
$lock_result = get_option( $lock_name );
// Bail if we were unable to create a lock, or if the existing lock is still valid.
if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_update_comment_type_batch' );
// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
update_option( $lock_name, time() );
// Check if there's still an empty comment type.
$empty_comment_type = $wpdb->get_var(
"SELECT comment_ID FROM $wpdb->comments
// No empty comment type, we're done here.
if ( ! $empty_comment_type ) {
update_option( 'finished_updating_comment_type', true );
delete_option( $lock_name );
// Empty comment type found? We'll need to run this script again.
wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_update_comment_type_batch' );
* Filters the comment batch size for updating the comment type.
* @param int $comment_batch_size The comment batch size. Default 100.
$comment_batch_size = (int) apply_filters( 'wp_update_comment_type_batch_size', 100 );
// Get the IDs of the comments to update.
$comment_ids = $wpdb->get_col(