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' );
}
}