); } clean_comment_cache( $id ); $comment = get_comment( $id ); // If metadata is provided, store it. if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) { foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) { add_comment_meta( $comment->comment_ID, $meta_key, $meta_value, true ); } } /** * Fires immediately after a comment is inserted into the database. * * @since 2.8.0 * * @param int $id The comment ID. * @param WP_Comment $comment Comment object. */ do_action( 'wp_insert_comment', $id, $comment ); return $id; } /** * Filters and sanitizes comment data. * * Sets the comment data 'filtered' field to true when finished. This can be * checked as to whether the comment should be filtered and to keep from * filtering the same comment more than once. * * @since 2.0.0 * * @param array $commentdata Contains information on the comment. * @return array Parsed comment information. */ function wp_filter_comment( $commentdata ) { if ( isset( $commentdata['user_ID'] ) ) { /** * Filters the comment author's user ID before it is set. * * The first time this filter is evaluated, `user_ID` is checked * (for back-compat), followed by the standard `user_id` value. * * @since 1.5.0 * * @param int $user_id The comment author's user ID. */ $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_ID'] ); } elseif ( isset( $commentdata['user_id'] ) ) { /** This filter is documented in wp-includes/comment.php */ $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_id'] ); } /** * Filters the comment author's browser user agent before it is set. * * @since 1.5.0 * * @param string $comment_agent The comment author's browser user agent. */ $commentdata['comment_agent'] = apply_filters( 'pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) ); /** This filter is documented in wp-includes/comment.php */ $commentdata['comment_author'] = apply_filters( 'pre_comment_author_name', $commentdata['comment_author'] ); /** * Filters the comment content before it is set. * * @since 1.5.0 * * @param string $comment_content The comment content. */ $commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] ); /** * Filters the comment author's IP address before it is set. * * @since 1.5.0 * * @param string $comment_author_ip The comment author's IP address. */ $commentdata['comment_author_IP'] = apply_filters( 'pre_comment_user_ip', $commentdata['comment_author_IP'] ); /** This filter is documented in wp-includes/comment.php */ $commentdata['comment_author_url'] = apply_filters( 'pre_comment_author_url', $commentdata['comment_author_url'] ); /** This filter is documented in wp-includes/comment.php */ $commentdata['comment_author_email'] = apply_filters( 'pre_comment_author_email', $commentdata['comment_author_email'] ); $commentdata['filtered'] = true; return $commentdata; } /** * Determines whether a comment should be blocked because of comment flood. * * @since 2.1.0 * * @param bool $block Whether plugin has already blocked comment. * @param int $time_lastcomment Timestamp for last comment. * @param int $time_newcomment Timestamp for new comment. * @return bool Whether comment should be blocked. */ function wp_throttle_comment_flood( $block, $time_lastcomment, $time_newcomment ) { if ( $block ) { // A plugin has already blocked... we'll let that decision stand. return $block; } if ( ( $time_newcomment - $time_lastcomment ) < 15 ) { return true; } return false; } /** * Adds a new comment to the database. * * Filters new comment to ensure that the fields are sanitized and valid before * inserting comment into database. Calls {@see 'comment_post'} action with comment ID * and whether comment is approved by WordPress. Also has {@see 'preprocess_comment'} * filter for processing the comment data before the function handles it. * * We use `REMOTE_ADDR` here directly. If you are behind a proxy, you should ensure * that it is properly set, such as in wp-config.php, for your environment. * * See {@link https://core.trac.wordpress.org/ticket/9235} * * @since 1.5.0 * @since 4.3.0 Introduced the `comment_agent` and `comment_author_IP` arguments. * @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`. * @since 5.5.0 Introduced the `comment_type` argument. * * @see wp_insert_comment() * @global wpdb $wpdb WordPress database abstraction object. * * @param array $commentdata { * Comment data. * * @type string $comment_author The name of the comment author. * @type string $comment_author_email The comment author email address. * @type string $comment_author_url The comment author URL. * @type string $comment_content The content of the comment. * @type string $comment_date The date the comment was submitted. Default is the current time. * @type string $comment_date_gmt The date the comment was submitted in the GMT timezone. * Default is `$comment_date` in the GMT timezone. * @type string $comment_type Comment type. Default 'comment'. * @type int $comment_parent The ID of this comment's parent, if any. Default 0. * @type int $comment_post_ID The ID of the post that relates to the comment. * @type int $user_id The ID of the user who submitted the comment. Default 0. * @type int $user_ID Kept for backward-compatibility. Use `$user_id` instead. * @type string $comment_agent Comment author user agent. Default is the value of 'HTTP_USER_AGENT' * in the `$_SERVER` superglobal sent in the original request. * @type string $comment_author_IP Comment author IP address in IPv4 format. Default is the value of * 'REMOTE_ADDR' in the `$_SERVER` superglobal sent in the original request. * } * @param bool $wp_error Should errors be returned as WP_Error objects instead of * executing wp_die()? Default false. * @return int|false|WP_Error The ID of the comment on success, false or WP_Error on failure. */ function wp_new_comment( $commentdata, $wp_error = false ) { global $wpdb; /* * Normalize `user_ID` to `user_id`, but pass the old key * to the `preprocess_comment` filter for backward compatibility. */ if ( isset( $commentdata['user_ID'] ) ) { $commentdata['user_ID'] = (int) $commentdata['user_ID']; $commentdata['user_id'] = $commentdata['user_ID']; } elseif ( isset( $commentdata['user_id'] ) ) { $commentdata['user_id'] = (int) $commentdata['user_id']; $commentdata['user_ID'] = $commentdata['user_id']; } $prefiltered_user_id = ( isset( $commentdata['user_id'] ) ) ? (int) $commentdata['user_id'] : 0; if ( ! isset( $commentdata['comment_author_IP'] ) ) { $commentdata['comment_author_IP'] = $_SERVER['REMOTE_ADDR']; } if ( ! isset( $commentdata['comment_agent'] ) ) { $commentdata['comment_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; } /** * Filters a comment's data before it is sanitized and inserted into the database. * * @since 1.5.0 * @since 5.6.0 Comment data includes the `comment_agent` and `comment_author_IP` values. * * @param array $commentdata Comment data. */ $commentdata = apply_filters( 'preprocess_comment', $commentdata ); $commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID']; // Normalize `user_ID` to `user_id` again, after the filter. if ( isset( $commentdata['user_ID'] ) && $prefiltered_user_id !== (int) $commentdata['user_ID'] ) { $commentdata['user_ID'] = (int) $commentdata['user_ID']; $commentdata['user_id'] = $commentdata['user_ID']; } elseif ( isset( $commentdata['user_id'] ) ) { $commentdata['user_id'] = (int) $commentdata['user_id']; $commentdata['user_ID'] = $commentdata['user_id']; } $commentdata['comment_parent'] = isset( $commentdata['comment_parent'] ) ? absint( $commentdata['comment_parent'] ) : 0; $parent_status = ( $commentdata['comment_parent'] > 0 ) ? wp_get_comment_status( $commentdata['comment_parent'] ) : ''; $commentdata['comment_parent'] = ( 'approved' === $parent_status || 'unapproved' === $parent_status ) ? $commentdata['comment_parent'] : 0; $commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '', $commentdata['comment_author_IP'] ); $commentdata['comment_agent'] = substr( $commentdata['comment_agent'], 0, 254 ); if ( empty( $commentdata['comment_date'] ) ) { $commentdata['comment_date'] = current_time( 'mysql' ); } if ( empty( $commentdata['comment_date_gmt'] ) ) { $commentdata['comment_date_gmt'] = current_time( 'mysql', 1 ); } if ( empty( $commentdata['comment_type'] ) ) { $commentdata['comment_type'] = 'comment'; } $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error ); if ( is_wp_error( $commentdata['comment_approved'] ) ) { return $commentdata['comment_approved']; } $commentdata = wp_filter_comment( $commentdata ); if ( ! in_array( $commentdata['comment_approved'], array( 'trash', 'spam' ), true ) ) { // Validate the comment again after filters are applied to comment data. $commentdata['comment_approved'] = wp_check_comment_data( $commentdata ); } if ( is_wp_error( $commentdata['comment_approved'] ) ) { return $commentdata['comment_approved']; } $comment_id = wp_insert_comment( $commentdata ); if ( ! $comment_id ) { $fields = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content' ); foreach ( $fields as $field ) { if ( isset( $commentdata[ $field ] ) ) { $commentdata[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->comments, $field, $commentdata[ $field ] ); } } $commentdata = wp_filter_comment( $commentdata ); $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error ); if ( is_wp_error( $commentdata['comment_approved'] ) ) { return $commentdata['comment_approved']; } $comment_id = wp_insert_comment( $commentdata ); if ( ! $comment_id ) { return false; } } /** * Fires immediately after a comment is inserted into the database. * * @since 1.2.0 * @since 4.5.0 The `$commentdata` parameter was added. * * @param int $comment_id The comment ID. * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam. * @param array $commentdata Comment data. */ do_action( 'comment_post', $comment_id, $commentdata['comment_approved'], $commentdata ); return $comment_id; } /** * Sends a comment moderation notification to the comment moderator. * * @since 4.4.0 * * @param int $comment_id ID of the comment. * @return bool True on success, false on failure. */ function wp_new_comment_notify_moderator( $comment_id ) { $comment = get_comment( $comment_id ); // Only send notifications for pending comments. $maybe_notify = ( '0' == $comment->comment_approved ); /** This filter is documented in wp-includes/pluggable.php */ $maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_id ); if ( ! $maybe_notify ) { return false; } return wp_notify_moderator( $comment_id ); } /** * Sends a notification of a new comment to the post author. * * @since 4.4.0 * * Uses the {@see 'notify_post_author'} filter to determine whether the post author * should be notified when a new comment is added, overriding site setting. * * @param int $comment_id Comment ID. * @return bool True on success, false on failure. */ function wp_new_comment_notify_postauthor( $comment_id ) { $comment = get_comment( $comment_id ); $maybe_notify = get_option( 'comments_notify' ); /** * Filters whether to send the post author new comment notification emails, * overriding the site setting. * * @since 4.4.0 * * @param bool $maybe_notify Whether to notify the post author about the new comment. * @param int $comment_id The ID of the comment for the notification. */ $maybe_notify = apply_filters( 'notify_post_author', $maybe_notify, $comment_id ); /* * wp_notify_postauthor() checks if notifying the author of their own comment. * By default, it won't, but filters can override this. */ if ( ! $maybe_notify ) { return false; } // Only send notifications for approved comments. if ( ! isset( $comment->comment_approved ) || '1' != $comment->comment_approved ) { return false; } return wp_notify_postauthor( $comment_id ); } /** * Sets the status of a comment. * * The {@see 'wp_set_comment_status'} action is called after the comment is handled. * If the comment status is not in the list, then false is returned. * * @since 1.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. * @param string $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'. * @param bool $wp_error Whether to return a WP_Error object if there is a failure. Default false. * @return bool|WP_Error True on success, false or WP_Error on failure. */ function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false ) { global $wpdb; switch ( $comment_status ) { case 'hold': case '0': $status = '0'; break; case 'approve': case '1': $status = '1'; add_action( 'wp_set_comment_status', 'wp_new_comment_notify_postauthor' ); break; case 'spam': $status = 'spam'; break; case 'trash': $status = 'trash'; break; default: return false; } $comment_old = clone get_comment( $comment_id ); if ( ! $wpdb->update( $wpdb->comments, array( 'comment_approved' => $status ), array( 'comment_ID' => $comment_old->comment_ID ) ) ) { if ( $wp_error ) { return new WP_Error( 'db_update_error', __( 'Could not update comment status.' ), $wpdb->last_error ); } else { return false; } } clean_comment_cache( $comment_old->comment_ID ); $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. * * @since 1.5.0 * * @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 ); return true; } /** * Updates an existing comment in the database. * * Filters the comment and makes sure certain fields are valid before updating. * * @since 2.0.0 * @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 ) { global $wpdb; // First, get all of the original fields. $comment = get_comment( $commentarr['comment_ID'], ARRAY_A ); if ( empty( $comment ) ) { if ( $wp_error ) { return new WP_Error( 'invalid_comment_id', __( 'Invalid comment ID.' ) ); } else { return false; } } // Make sure that the comment post ID is valid (if specified). if ( ! empty( $commentarr['comment_post_ID'] ) && ! get_post( $commentarr['comment_post_ID'] ) ) { if ( $wp_error ) { return new WP_Error( 'invalid_post_id', __( 'Invalid post ID.' ) ); } else { return false; } } $filter_comment = false; if ( ! has_filter( 'pre_comment_content', 'wp_filter_kses' ) ) { $filter_comment = ! user_can( isset( $comment['user_id'] ) ? $comment['user_id'] : 0, 'unfiltered_html' ); } if ( $filter_comment ) { 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 ); if ( $filter_comment ) { 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. * * @since 1.5.0 * * @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 4.7.0 * @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 ) ) { if ( $wp_error ) { return $data; } else { return false; } } $keys = array( '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', ); $data = wp_array_slice_assoc( $data, $keys ); $result = $wpdb->update( $wpdb->comments, $data, array( 'comment_ID' => $comment_id ) ); if ( false === $result ) { if ( $wp_error ) { return new WP_Error( 'db_update_error', __( 'Could not update comment in the database.' ), $wpdb->last_error ); } else { return false; } } // 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 1.2.0 * @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 ); return $result; } /** * 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. * * @since 2.5.0 * * @param bool $defer * @return bool */ function wp_defer_comment_counting( $defer = null ) { static $_defer = false; if ( is_bool( $defer ) ) { $_defer = $defer; // Flush any deferred counts. if ( ! $defer ) { wp_update_comment_count( null, true ); } } return $_defer; } /** * 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. * * @since 2.1.0 * * @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 * not exist. */ function wp_update_comment_count( $post_id, $do_deferred = false ) { static $_deferred = array(); if ( empty( $post_id ) && ! $do_deferred ) { return false; } if ( $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() ) { $_deferred[] = $post_id; return true; } elseif ( $post_id ) { return wp_update_comment_count_now( $post_id ); } } /** * Updates the comment count for the post. * * @since 2.5.0 * * @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 ) { global $wpdb; $post_id = (int) $post_id; if ( ! $post_id ) { return false; } wp_cache_delete( 'comments-0', 'counts' ); wp_cache_delete( "comments-{$post_id}", 'counts' ); $post = get_post( $post_id ); if ( ! $post ) { return false; } $old = (int) $post->comment_count; /** * Filters a post's comment count before it is updated in the database. * * @since 4.5.0 * * @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 ); if ( is_null( $new ) ) { $new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id ) ); } else { $new = (int) $new; } $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. * * @since 2.3.0 * * @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 ); return true; } // // 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. * * @since 1.5.0 * * @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. return false; } // 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'] ) ) { return false; } $response = wp_safe_remote_head( $url, array( 'timeout' => 2, 'httpversion' => '1.0', ) ); if ( is_wp_error( $response ) ) { return false; } 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' ) ) ) { return false; } // 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( $url, array( 'timeout' => 2, 'httpversion' => '1.0', ) ); if ( is_wp_error( $response ) ) { return false; } $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; } } return false; } /** * Performs all pingbacks, enclosures, trackbacks, and sends to pingback services. * * @since 2.1.0 * @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. * * @since 5.6.0 */ do_action( 'do_all_pings' ); } /** * Performs all pingbacks. * * @since 5.6.0 */ function do_all_pingbacks() { $pings = get_posts( array( 'post_type' => get_post_types(), 'suppress_filters' => false, 'nopaging' => true, 'meta_key' => '_pingme', 'fields' => 'ids', ) ); foreach ( $pings as $ping ) { delete_post_meta( $ping, '_pingme' ); pingback( null, $ping ); } } /** * Performs all enclosures. * * @since 5.6.0 */ function do_all_enclosures() { $enclosures = get_posts( array( 'post_type' => get_post_types(), 'suppress_filters' => false, 'nopaging' => true, 'meta_key' => '_encloseme', 'fields' => 'ids', ) ); foreach ( $enclosures as $enclosure ) { delete_post_meta( $enclosure, '_encloseme' ); do_enclose( null, $enclosure ); } } /** * Performs all trackbacks. * * @since 5.6.0 */ function do_all_trackbacks() { $trackbacks = get_posts( array( 'post_type' => get_post_types(), 'suppress_filters' => false, 'nopaging' => true, 'meta_key' => '_trackbackme', 'fields' => 'ids', ) ); foreach ( $trackbacks as $trackback ) { delete_post_meta( $trackback, '_trackbackme' ); do_trackbacks( $trackback ); } } /** * Performs trackbacks. * * @since 1.5.0 * @since 4.7.0 `$post` can be a WP_Post object. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|WP_Post $post Post ID or object to do trackbacks on. * @return void|false Returns false on failure. */ function do_trackbacks( $post ) { global $wpdb; $post = get_post( $post ); if ( ! $post ) { return false; } $to_ping = get_to_ping( $post ); $pinged = get_pung( $post ); if ( empty( $to_ping ) ) { $wpdb->update( $wpdb->posts, array( 'to_ping' => '' ), array( 'ID' => $post->ID ) ); return; } if ( empty( $post->post_excerpt ) ) { /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_content', $post->post_content, $post->ID ); } else { /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_excerpt', $post->post_excerpt ); } $excerpt = str_replace( ']]>', ']]>', $excerpt ); $excerpt = wp_html_excerpt( $excerpt, 252, '…' ); /** This filter is documented in wp-includes/post-template.php */ $post_title = apply_filters( 'the_title', $post->post_title, $post->ID ); $post_title = strip_tags( $post_title ); if ( $to_ping ) { foreach ( (array) $to_ping as $tb_ping ) { $tb_ping = trim( $tb_ping ); if ( ! in_array( $tb_ping, $pinged, true ) ) { trackback( $tb_ping, $post_title, $excerpt, $post->ID ); $pinged[] = $tb_ping; } else { $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $tb_ping, $post->ID ) ); } } } } /** * Sends pings to all of the ping site services. * * @since 1.2.0 * * @param int $post_id Post ID. * @return int Same post ID as provided. */ function generic_ping( $post_id = 0 ) { $services = get_option( 'ping_sites' ); $services = explode( "\n", $services ); foreach ( (array) $services as $service ) { $service = trim( $service ); if ( '' !== $service ) { weblog_ping( $service ); } } return $post_id; } /** * Pings back the links found in a post. * * @since 0.71 * @since 4.7.0 `$post` can be a WP_Post object. * * @param string $content Post content to check for links. If empty will retrieve from post. * @param int|WP_Post $post Post ID or object. */ function pingback( $content, $post ) { require_once ABSPATH . WPINC . '/class-IXR.php'; require_once ABSPATH . WPINC . '/class-wp-http-ixr-client.php'; // Original code by Mort (http://mort.mine.nu:8080). $post_links = array(); $post = get_post( $post ); if ( ! $post ) { return; } $pung = get_pung( $post ); if ( empty( $content ) ) { $content = $post->post_content; } /* * Step 1. * Parsing the post, external links (if any) are stored in the $post_links array. */ $post_links_temp = wp_extract_urls( $content ); /* * Step 2. * Walking through the links array. * First we get rid of links pointing to sites, not to specific files. * Example: * http://dummy-weblog.org * http://dummy-weblog.org/ * http://dummy-weblog.org/post.php * We don't wanna ping first and second types, even if they have a valid . */ foreach ( (array) $post_links_temp as $link_test ) { // If we haven't pung it already and it isn't a link to itself. if ( ! in_array( $link_test, $pung, true ) && ( url_to_postid( $link_test ) != $post->ID ) // Also, let's never ping local attachments. && ! is_local_attachment( $link_test ) ) { $test = parse_url( $link_test ); if ( $test ) { if ( isset( $test['query'] ) ) { $post_links[] = $link_test; } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) { $post_links[] = $link_test; } } } } $post_links = array_unique( $post_links ); /** * Fires just before pinging back links found in a post. * * @since 2.0.0 * * @param string[] $post_links Array of link URLs to be checked (passed by reference). * @param string[] $pung Array of link URLs already pinged (passed by reference). * @param int $post_id The post ID. */ do_action_ref_array( 'pre_ping', array( &$post_links, &$pung, $post->ID ) ); foreach ( (array) $post_links as $pagelinkedto ) { $pingback_server_url = discover_pingback_server_uri( $pagelinkedto ); if ( $pingback_server_url ) { if ( function_exists( 'set_time_limit' ) ) { // Allows an additional 60 seconds for each pingback to complete. set_time_limit( 60 ); } // Now, the RPC call. $pagelinkedfrom = get_permalink( $post ); // Using a timeout of 3 seconds should be enough to cover slow servers. $client = new WP_HTTP_IXR_Client( $pingback_server_url ); $client->timeout = 3; /** * Filters the user agent sent when pinging-back a URL. * * @since 2.9.0 * * @param string $concat_useragent The user agent concatenated with ' -- WordPress/' * and the WordPress version. * @param string $useragent The useragent. * @param string $pingback_server_url The server URL being linked to. * @param string $pagelinkedto URL of page linked to. * @param string $pagelinkedfrom URL of page linked from. */ $client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . get_bloginfo( 'version' ), $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom ); // When set to true, this outputs debug messages by itself. $client->debug = false; if ( $client->query( 'pingback.ping', $pagelinkedfrom, $pagelinkedto ) || ( isset( $client->error->code ) && 48 == $client->error->code ) ) { // Already registered. add_ping( $post, $pagelinkedto ); } } } } /** * Checks whether blog is public before returning sites. * * @since 2.1.0 * * @param mixed $sites Will return if blog is public, will not return if not public. * @return mixed Empty string if blog is not public, returns $sites, if site is public. */ function privacy_ping_filter( $sites ) { if ( '0' != get_option( 'blog_public' ) ) { return $sites; } else { return ''; } } /** * Sends a Trackback. * * Updates database when sending trackback to prevent duplicates. * * @since 0.71 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $trackback_url URL to send trackbacks. * @param string $title Title of post. * @param string $excerpt Excerpt of post. * @param int $post_id Post ID. * @return int|false|void Database query from update. */ function trackback( $trackback_url, $title, $excerpt, $post_id ) { global $wpdb; if ( empty( $trackback_url ) ) { return; } $options = array(); $options['timeout'] = 10; $options['body'] = array( 'title' => $title, 'url' => get_permalink( $post_id ), 'blog_name' => get_option( 'blogname' ), 'excerpt' => $excerpt, ); $response = wp_safe_remote_post( $trackback_url, $options ); if ( is_wp_error( $response ) ) { return; } $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $post_id ) ); return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $post_id ) ); } /** * Sends a pingback. * * @since 1.2.0 * * @param string $server Host of blog to connect to. * @param string $path Path to send the ping. */ function weblog_ping( $server = '', $path = '' ) { require_once ABSPATH . WPINC . '/class-IXR.php'; require_once ABSPATH . WPINC . '/class-wp-http-ixr-client.php'; // Using a timeout of 3 seconds should be enough to cover slow servers. $client = new WP_HTTP_IXR_Client( $server, ( ( ! strlen( trim( $path ) ) || ( '/' === $path ) ) ? false : $path ) ); $client->timeout = 3; $client->useragent .= ' -- WordPress/' . get_bloginfo( 'version' ); // When set to true, this outputs debug messages by itself. $client->debug = false; $home = trailingslashit( home_url() ); if ( ! $client->query( 'weblogUpdates.extendedPing', get_option( 'blogname' ), $home, get_bloginfo( 'rss2_url' ) ) ) { // Then try a normal ping. $client->query( 'weblogUpdates.ping', get_option( 'blogname' ), $home ); } } /** * Default filter attached to pingback_ping_source_uri to validate the pingback's Source URI. * * @since 3.5.1 * * @see wp_http_validate_url() * * @param string $source_uri * @return string */ function pingback_ping_source_uri( $source_uri ) { return (string) wp_http_validate_url( $source_uri ); } /** * Default filter attached to xmlrpc_pingback_error. * * Returns a generic pingback error code unless the error code is 48, * which reports that the pingback is already registered. * * @since 3.5.1 * * @link https://www.hixie.ch/specs/pingback/pingback#TOC3 * * @param IXR_Error $ixr_error * @return IXR_Error */ function xmlrpc_pingback_error( $ixr_error ) { if ( 48 === $ixr_error->code ) { return $ixr_error; } return new IXR_Error( 0, '' ); } // // Cache. // /** * Removes a comment from the object cache. * * @since 2.3.0 * * @param int|array $ids Comment ID or an array of comment IDs to remove from cache. */ function clean_comment_cache( $ids ) { $comment_ids = (array) $ids; wp_cache_delete_multiple( $comment_ids, 'comment' ); foreach ( $comment_ids as $id ) { /** * Fires immediately after a comment has been removed from the object cache. * * @since 4.5.0 * * @param int $id Comment ID. */ do_action( 'clean_comment_cache', $id ); } wp_cache_set_comments_last_changed(); } /** * Updates the comment cache of given comments. * * Will add the comments in $comments to the cache. If comment ID already exists * in the comment cache then it will not be updated. The comment is added to the * cache using the comment group with the key using the ID of the comments. * * @since 2.3.0 * @since 4.4.0 Introduced the `$update_meta_cache` parameter. * * @param WP_Comment[] $comments Array of comment objects * @param bool $update_meta_cache Whether to update commentmeta cache. Default true. */ function update_comment_cache( $comments, $update_meta_cache = true ) { $data = array(); foreach ( (array) $comments as $comment ) { $data[ $comment->comment_ID ] = $comment; } wp_cache_add_multiple( $data, 'comment' ); if ( $update_meta_cache ) { // Avoid `wp_list_pluck()` in case `$comments` is passed by reference. $comment_ids = array(); foreach ( $comments as $comment ) { $comment_ids[] = $comment->comment_ID; } update_meta_cache( 'comment', $comment_ids ); } } /** * Adds any comments from the given IDs to the cache that do not already exist in cache. * * @since 4.4.0 * @since 6.1.0 This function is no longer marked as "private". * @since 6.3.0 Use wp_lazyload_comment_meta() for lazy-loading of comment meta. * * @see update_comment_cache() * @global wpdb $wpdb WordPress database abstraction object. * * @param int[] $comment_ids Array of comment IDs. * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true. */ function _prime_comment_caches( $comment_ids, $update_meta_cache = true ) { global $wpdb; $non_cached_ids = _get_non_cached_ids( $comment_ids, 'comment' ); if ( ! empty( $non_cached_ids ) ) { $fresh_comments = $wpdb->get_results( sprintf( "SELECT $wpdb->comments.* FROM $wpdb->comments WHERE comment_ID IN (%s)", implode( ',', array_map( 'intval', $non_cached_ids ) ) ) ); update_comment_cache( $fresh_comments, false ); } if ( $update_meta_cache ) { wp_lazyload_comment_meta( $comment_ids ); } } // // Internal. // /** * Closes comments on old posts on the fly, without any extra DB queries. Hooked to the_posts. * * @since 2.7.0 * @access private * * @param WP_Post $posts Post data object. * @param WP_Query $query Query object. * @return array */ function _close_comments_for_old_posts( $posts, $query ) { if ( empty( $posts ) || ! $query->is_singular() || ! get_option( 'close_comments_for_old_posts' ) ) { return $posts; } /** * Filters the list of post types to automatically close comments for. * * @since 3.2.0 * * @param string[] $post_types An array of post type names. */ $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) ); if ( ! in_array( $posts[0]->post_type, $post_types, true ) ) { return $posts; } $days_old = (int) get_option( 'close_comments_days_old' ); if ( ! $days_old ) { return $posts; } if ( time() - strtotime( $posts[0]->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) { $posts[0]->comment_status = 'closed'; $posts[0]->ping_status = 'closed'; } return $posts; } /** * Closes comments on an old post. Hooked to comments_open and pings_open. * * @since 2.7.0 * @access private * * @param bool $open Comments open or closed. * @param int $post_id Post ID. * @return bool $open */ function _close_comments_for_old_post( $open, $post_id ) { if ( ! $open ) { return $open; } if ( ! get_option( 'close_comments_for_old_posts' ) ) { return $open; } $days_old = (int) get_option( 'close_comments_days_old' ); if ( ! $days_old ) { return $open; } $post = get_post( $post_id ); /** This filter is documented in wp-includes/comment.php */ $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) ); if ( ! in_array( $post->post_type, $post_types, true ) ) { return $open; } // Undated drafts should not show up as comments closed. if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { return $open; } if ( time() - strtotime( $post->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) { return false; } return $open; } /** * Handles the submission of a comment, usually posted to wp-comments-post.php via a comment form. * * This function expects unslashed data, as opposed to functions such as `wp_new_comment()` which * expect slashed data. * * @since 4.4.0 * * @param array $comment_data { * Comment data. * * @type string|int $comment_post_ID The ID of the post that relates to the comment. * @type string $author The name of the comment author. * @type string $email The comment author email address. * @type string $url The comment author URL. * @type string $comment The content of the comment. * @type string|int $comment_parent The ID of this comment's parent, if any. Default 0. * @type string $_wp_unfiltered_html_comment The nonce value for allowing unfiltered HTML. * } * @return WP_Comment|WP_Error A WP_Comment object on success, a WP_Error object on failure. */ function wp_handle_comment_submission( $comment_data ) { $comment_post_id = 0; $comment_author = ''; $comment_author_email = ''; $comment_author_url = ''; $comment_content = ''; $comment_parent = 0; $user_id = 0; if ( isset( $comment_data['comment_post_ID'] ) ) { $comment_post_id = (int) $comment_data['comment_post_ID']; } if ( isset( $comment_data['author'] ) && is_string( $comment_data['author'] ) ) { $comment_author = trim( strip_tags( $comment_data['author'] ) ); } if ( isset( $comment_data['email'] ) && is_string( $comment_data['email'] ) ) { $comment_author_email = trim( $comment_data['email'] ); } if ( isset( $comment_data['url'] ) && is_string( $comment_data['url'] ) ) { $comment_author_url = trim( $comment_data['url'] ); } if ( isset( $comment_data['comment'] ) && is_string( $comment_data['comment'] ) ) { $comment_content = trim( $comment_data['comment'] ); } if ( isset( $comment_data['comment_parent'] ) ) { $comment_parent = absint( $comment_data['comment_parent'] ); $comment_parent_object = get_comment( $comment_parent ); if ( 0 !== $comment_parent && ( ! $comment_parent_object instanceof WP_Comment || 0 === (int) $comment_parent_object->comment_approved ) ) { /** * Fires when a comment reply is attempted to an unapproved comment. * * @since 6.2.0 * * @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. * * @since 1.5.0 * * @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. * * @since 1.5.0 * * @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. * * @since 2.9.0 * * @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. * * @since 1.5.1 * * @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 ); } else { 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. * * @since 2.9.0 * * @param int $comment_post_id Post ID. */ do_action( 'comment_on_password_protected', $comment_post_id ); return new WP_Error( 'comment_on_password_protected' ); } else { /** * Fires before a comment is posted. * * @since 2.8.0 * * @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 ( $user->exists() ) { 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; $user_id = $user->ID; 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' ); } } } else { 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', __( 'Error: Please fill the required fields.' ), 200 ); } elseif ( ! is_email( $comment_author_email ) ) { return new WP_Error( 'require_valid_email', __( 'Error: Please enter a valid email address.' ), 200 ); } } $commentdata = array( 'comment_post_ID' => $comment_post_id, ); $commentdata += compact( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_id' ); /** * Filters whether an empty comment should be allowed. * * @since 5.1.0 * * @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', __( 'Error: 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 $comment_id; } if ( ! $comment_id ) { return new WP_Error( 'comment_save_error', __( 'Error: The comment could not be saved. Please try again later.' ), 500 ); } return get_comment( $comment_id ); } /** * Registers the personal data exporter for comments. * * @since 4.9.6 * * @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', ); return $exporters; } /** * Finds and exports personal data associated with an email address from the comments table. * * @since 4.9.6 * * @param string $email_address The comment author email address. * @param int $page Comment page number. * @return array { * 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. $number = 500; $page = (int) $page; $data_to_export = array(); $comments = get_comments( array( 'author_email' => $email_address, 'number' => $number, 'paged' => $page, 'orderby' => 'comment_ID', 'order' => 'ASC', '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 ) { $value = ''; switch ( $key ) { case 'comment_author': case 'comment_author_email': case 'comment_author_url': case 'comment_author_IP': case 'comment_agent': case 'comment_date': $value = $comment->{$key}; break; case 'comment_content': $value = get_comment_text( $comment->comment_ID ); break; case 'comment_link': $value = get_comment_link( $comment->comment_ID ); $value = sprintf( '%s', esc_url( $value ), esc_html( $value ) ); break; } if ( ! empty( $value ) ) { $comment_data_to_export[] = array( 'name' => $name, 'value' => $value, ); } } $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; return array( 'data' => $data_to_export, 'done' => $done, ); } /** * Registers the personal data eraser for comments. * * @since 4.9.6 * * @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', ); return $erasers; } /** * Erases personal data associated with an email address from the comments table. * * @since 4.9.6 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $email_address The comment author email address. * @param int $page Comment page number. * @return array { * Data removal results. * * @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 ) { global $wpdb; if ( empty( $email_address ) ) { return array( 'items_removed' => false, 'items_retained' => false, 'messages' => array(), 'done' => true, ); } // Limit us to 500 comments at a time to avoid timing out. $number = 500; $page = (int) $page; $items_removed = false; $items_retained = false; $comments = get_comments( array( 'author_email' => $email_address, 'number' => $number, 'paged' => $page, 'orderby' => 'comment_ID', 'order' => 'ASC', 'include_unapproved' => true, ) ); /* translators: Name of a comment's author after being anonymized. */ $anon_author = __( 'Anonymous' ); $messages = array(); 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. * * @since 4.9.6 * * @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 ); } else { /* translators: %d: Comment ID. */ $messages[] = sprintf( __( 'Comment %d contains personal data but could not be anonymized.' ), $comment_id ); } $items_retained = true; continue; } $args = array( 'comment_ID' => $comment_id, ); $updated = $wpdb->update( $wpdb->comments, $anonymized_comment, $args ); if ( $updated ) { $items_removed = true; clean_comment_cache( $comment_id ); } else { $items_retained = true; } } $done = count( $comments ) < $number; return array( 'items_removed' => $items_removed, 'items_retained' => $items_retained, 'messages' => $messages, 'done' => $done, ); } /** * Sets the last changed time for the 'comment' cache group. * * @since 5.0.0 */ function wp_cache_set_comments_last_changed() { wp_cache_set_last_changed( 'comment' ); } /** * Updates the comment type for a batch of comments. * * @since 5.5.0 * * @global wpdb $wpdb WordPress database abstraction object. */ function _wp_batch_update_comment_type() { global $wpdb; $lock_name = 'update_comment_type.lock'; // Try to 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() ) ); if ( ! $lock_result ) { $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' ); return; } } // 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 WHERE comment_type = '' LIMIT 1" ); // No empty comment type, we're done here. if ( ! $empty_comment_type ) { update_option( 'finished_updating_comment_type', true ); delete_option( $lock_name ); return; } // 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. * * @since 5.5.0 * * @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( $wpdb->prepare( "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_type = '' ORDER BY comment_ID DESC LIMIT %d", $comment_batch_size ) ); if ( $comment_ids ) { $comment_id_list = implode( ',', $comment_ids ); // Update the `comment_type` field value to be `comment` for the next batch of comments. $wpdb->query( "UPDATE {$wpdb->comments} SET comment_type = 'comment' WHERE comment_type = '' AND comment_ID IN ({$comment_id_list})" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ); // Make sure to clean the comment cache. clean_comment_cache( $comment_ids ); } delete_option( $lock_name ); } /** * In order to avoid the _wp_batch_update_comment_type() job being accidentally removed, * check that it's still scheduled while we haven't finished updating comment types. * * @ignore * @since 5.5.0 */ function _wp_check_for_scheduled_update_comment_type() { if ( ! get_option( 'finished_updating_comment_type' ) && ! wp_next_scheduled( 'wp_update_comment_type_batch' ) ) { wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_update_comment_type_batch' ); } }