$comment = get_comment( $comment_old->comment_ID );
* Fires immediately after transitioning a comment's status from one to another in the database
* and removing the comment from the object cache, but prior to all status transition hooks.
* @param string $comment_id Comment ID as a numeric string.
* @param string $comment_status Current comment status. Possible values include
* 'hold', '0', 'approve', '1', 'spam', and 'trash'.
do_action( 'wp_set_comment_status', $comment->comment_ID, $comment_status );
wp_transition_comment_status( $comment_status, $comment_old->comment_approved, $comment );
wp_update_comment_count( $comment->comment_post_ID );
* Updates an existing comment in the database.
* Filters the comment and makes sure certain fields are valid before updating.
* @since 4.9.0 Add updating comment meta during comment update.
* @since 5.5.0 The `$wp_error` parameter was added.
* @since 5.5.0 The return values for an invalid comment or post ID
* were changed to false instead of 0.
* @global wpdb $wpdb WordPress database abstraction object.
* @param array $commentarr Contains information on the comment.
* @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
* @return int|false|WP_Error The value 1 if the comment was updated, 0 if not updated.
* False or a WP_Error object on failure.
function wp_update_comment( $commentarr, $wp_error = false ) {
// First, get all of the original fields.
$comment = get_comment( $commentarr['comment_ID'], ARRAY_A );
if ( empty( $comment ) ) {
return new WP_Error( 'invalid_comment_id', __( 'Invalid comment ID.' ) );
// Make sure that the comment post ID is valid (if specified).
if ( ! empty( $commentarr['comment_post_ID'] ) && ! get_post( $commentarr['comment_post_ID'] ) ) {
return new WP_Error( 'invalid_post_id', __( 'Invalid post ID.' ) );
if ( ! has_filter( 'pre_comment_content', 'wp_filter_kses' ) ) {
$filter_comment = ! user_can( isset( $comment['user_id'] ) ? $comment['user_id'] : 0, 'unfiltered_html' );
add_filter( 'pre_comment_content', 'wp_filter_kses' );
// Escape data pulled from DB.
$comment = wp_slash( $comment );
$old_status = $comment['comment_approved'];
// Merge old and new fields with new fields overwriting old ones.
$commentarr = array_merge( $comment, $commentarr );
$commentarr = wp_filter_comment( $commentarr );
remove_filter( 'pre_comment_content', 'wp_filter_kses' );
// Now extract the merged array.
$data = wp_unslash( $commentarr );
* Filters the comment content before it is updated in the database.
* @param string $comment_content The comment data.
$data['comment_content'] = apply_filters( 'comment_save_pre', $data['comment_content'] );
$data['comment_date_gmt'] = get_gmt_from_date( $data['comment_date'] );
if ( ! isset( $data['comment_approved'] ) ) {
$data['comment_approved'] = 1;
} elseif ( 'hold' === $data['comment_approved'] ) {
$data['comment_approved'] = 0;
} elseif ( 'approve' === $data['comment_approved'] ) {
$data['comment_approved'] = 1;
$comment_id = $data['comment_ID'];
$comment_post_id = $data['comment_post_ID'];
* Filters the comment data immediately before it is updated in the database.
* Note: data being passed to the filter is already unslashed.
* @since 5.5.0 Returning a WP_Error value from the filter will short-circuit comment update
* and allow skipping further processing.
* @param array|WP_Error $data The new, processed comment data, or WP_Error.
* @param array $comment The old, unslashed comment data.
* @param array $commentarr The new, raw comment data.
$data = apply_filters( 'wp_update_comment_data', $data, $comment, $commentarr );
// Do not carry on on failure.
if ( is_wp_error( $data ) ) {
$data = wp_array_slice_assoc( $data, $keys );
$result = $wpdb->update( $wpdb->comments, $data, array( 'comment_ID' => $comment_id ) );
if ( false === $result ) {
return new WP_Error( 'db_update_error', __( 'Could not update comment in the database.' ), $wpdb->last_error );
// If metadata is provided, store it.
if ( isset( $commentarr['comment_meta'] ) && is_array( $commentarr['comment_meta'] ) ) {
foreach ( $commentarr['comment_meta'] as $meta_key => $meta_value ) {
update_comment_meta( $comment_id, $meta_key, $meta_value );
clean_comment_cache( $comment_id );
wp_update_comment_count( $comment_post_id );
* Fires immediately after a comment is updated in the database.
* The hook also fires immediately before comment status transition hooks are fired.
* @since 4.6.0 Added the `$data` parameter.
* @param int $comment_id The comment ID.
* @param array $data Comment data.
do_action( 'edit_comment', $comment_id, $data );
$comment = get_comment( $comment_id );
wp_transition_comment_status( $comment->comment_approved, $old_status, $comment );
* Determines whether to defer comment counting.
* When setting $defer to true, all post comment counts will not be updated
* until $defer is set to false. When $defer is set to false, then all
* previously deferred updated post comment counts will then be automatically
* updated without having to call wp_update_comment_count() after.
function wp_defer_comment_counting( $defer = null ) {
if ( is_bool( $defer ) ) {
// Flush any deferred counts.
wp_update_comment_count( null, true );
* Updates the comment count for post(s).
* When $do_deferred is false (is by default) and the comments have been set to
* be deferred, the post_id will be added to a queue, which will be updated at a
* later date and only updated once per post ID.
* If the comments have not be set up to be deferred, then the post will be
* updated. When $do_deferred is set to true, then all previous deferred post
* IDs will be updated along with the current $post_id.
* @see wp_update_comment_count_now() For what could cause a false return value
* @param int|null $post_id Post ID.
* @param bool $do_deferred Optional. Whether to process previously deferred
* post comment counts. Default false.
* @return bool|void True on success, false on failure or if post with ID does
function wp_update_comment_count( $post_id, $do_deferred = false ) {
static $_deferred = array();
if ( empty( $post_id ) && ! $do_deferred ) {
$_deferred = array_unique( $_deferred );
foreach ( $_deferred as $i => $_post_id ) {
wp_update_comment_count_now( $_post_id );
unset( $_deferred[ $i ] );
/** @todo Move this outside of the foreach and reset $_deferred to an array instead */
if ( wp_defer_comment_counting() ) {
return wp_update_comment_count_now( $post_id );
* Updates the comment count for the post.
* @global wpdb $wpdb WordPress database abstraction object.
* @param int $post_id Post ID
* @return bool True on success, false if the post does not exist.
function wp_update_comment_count_now( $post_id ) {
$post_id = (int) $post_id;
wp_cache_delete( 'comments-0', 'counts' );
wp_cache_delete( "comments-{$post_id}", 'counts' );
$post = get_post( $post_id );
$old = (int) $post->comment_count;
* Filters a post's comment count before it is updated in the database.
* @param int|null $new The new comment count. Default null.
* @param int $old The old comment count.
* @param int $post_id Post ID.
$new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );
$new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id ) );
$wpdb->update( $wpdb->posts, array( 'comment_count' => $new ), array( 'ID' => $post_id ) );
clean_post_cache( $post );
* Fires immediately after a post's comment count is updated in the database.
* @param int $post_id Post ID.
* @param int $new The new comment count.
* @param int $old The old comment count.
do_action( 'wp_update_comment_count', $post_id, $new, $old );
/** This action is documented in wp-includes/post.php */
do_action( "edit_post_{$post->post_type}", $post_id, $post );
/** This action is documented in wp-includes/post.php */
do_action( 'edit_post', $post_id, $post );
// Ping and trackback functions.
* Finds a pingback server URI based on the given URL.
* Checks the HTML for the rel="pingback" link and X-Pingback headers. It does
* a check for the X-Pingback headers first and returns that, if available.
* The check for the rel="pingback" has more overhead than just the header.
* @param string $url URL to ping.
* @param string $deprecated Not Used.
* @return string|false String containing URI on success, false on failure.
function discover_pingback_server_uri( $url, $deprecated = '' ) {
if ( ! empty( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.7.0' );
$pingback_str_dquote = 'rel="pingback"';
$pingback_str_squote = 'rel=\'pingback\'';
/** @todo Should use Filter Extension or custom preg_match instead. */
$parsed_url = parse_url( $url );
if ( ! isset( $parsed_url['host'] ) ) { // Not a URL. This should never happen.
// Do not search for a pingback server on our own uploads.
$uploads_dir = wp_get_upload_dir();
if ( str_starts_with( $url, $uploads_dir['baseurl'] ) ) {
$response = wp_safe_remote_head(
if ( is_wp_error( $response ) ) {
if ( wp_remote_retrieve_header( $response, 'X-Pingback' ) ) {
return wp_remote_retrieve_header( $response, 'X-Pingback' );
// Not an (x)html, sgml, or xml page, no use going further.
if ( preg_match( '#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'Content-Type' ) ) ) {
// Now do a GET since we're going to look in the HTML headers (and we're sure it's not a binary file).
$response = wp_safe_remote_get(
if ( is_wp_error( $response ) ) {
$contents = wp_remote_retrieve_body( $response );
$pingback_link_offset_dquote = strpos( $contents, $pingback_str_dquote );
$pingback_link_offset_squote = strpos( $contents, $pingback_str_squote );
if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
$quote = ( $pingback_link_offset_dquote ) ? '"' : '\'';
$pingback_link_offset = ( '"' === $quote ) ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
$pingback_href_pos = strpos( $contents, 'href=', $pingback_link_offset );
$pingback_href_start = $pingback_href_pos + 6;
$pingback_href_end = strpos( $contents, $quote, $pingback_href_start );
$pingback_server_url_len = $pingback_href_end - $pingback_href_start;
$pingback_server_url = substr( $contents, $pingback_href_start, $pingback_server_url_len );
// We may find rel="pingback" but an incomplete pingback URL.
if ( $pingback_server_url_len > 0 ) { // We got it!
return $pingback_server_url;
* Performs all pingbacks, enclosures, trackbacks, and sends to pingback services.
* @since 5.6.0 Introduced `do_all_pings` action hook for individual services.
function do_all_pings() {
* Fires immediately after the `do_pings` event to hook services individually.
do_action( 'do_all_pings' );
* Performs all pingbacks.
function do_all_pingbacks() {
'post_type' => get_post_types(),
'suppress_filters' => false,
foreach ( $pings as $ping ) {
delete_post_meta( $ping, '_pingme' );
* Performs all enclosures.
function do_all_enclosures() {
'post_type' => get_post_types(),
'suppress_filters' => false,
'meta_key' => '_encloseme',
foreach ( $enclosures as $enclosure ) {
delete_post_meta( $enclosure, '_encloseme' );
do_enclose( null, $enclosure );
* Performs all trackbacks.
function do_all_trackbacks() {
'post_type' => get_post_types(),