Retrieves a pingback and registers it.
Parameters
$args
arrayrequired- Method arguments. Note: arguments must be ordered as documented.
0
stringURL of page linked from.1
stringURL of page linked to.
Source
public function pingback_ping( $args ) {
global $wpdb;
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'pingback.ping', $args, $this );
$this->escape( $args );
$pagelinkedfrom = str_replace( '&', '&', $args[0] );
$pagelinkedto = str_replace( '&', '&', $args[1] );
$pagelinkedto = str_replace( '&', '&', $pagelinkedto );
/**
* Filters the pingback source URI.
*
* @since 3.6.0
*
* @param string $pagelinkedfrom URI of the page linked from.
* @param string $pagelinkedto URI of the page linked to.
*/
$pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
if ( ! $pagelinkedfrom ) {
return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
}
// Check if the page linked to is on our site.
$pos1 = strpos( $pagelinkedto, str_replace( array( 'http://www.', 'http://', 'https://www.', 'https://' ), '', get_option( 'home' ) ) );
if ( ! $pos1 ) {
return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
}
/*
* Let's find which post is linked to.
* FIXME: Does url_to_postid() cover all these cases already?
* If so, then let's use it and drop the old code.
*/
$urltest = parse_url( $pagelinkedto );
$post_id = url_to_postid( $pagelinkedto );
if ( $post_id ) {
// $way
} elseif ( isset( $urltest['path'] ) && preg_match( '#p/[0-9]{1,}#', $urltest['path'], $match ) ) {
// The path defines the post_ID (archives/p/XXXX).
$blah = explode( '/', $match[0] );
$post_id = (int) $blah[1];
} elseif ( isset( $urltest['query'] ) && preg_match( '#p=[0-9]{1,}#', $urltest['query'], $match ) ) {
// The query string defines the post_ID (?p=XXXX).
$blah = explode( '=', $match[0] );
$post_id = (int) $blah[1];
} elseif ( isset( $urltest['fragment'] ) ) {
// An #anchor is there, it's either...
if ( (int) $urltest['fragment'] ) {
// ...an integer #XXXX (simplest case),
$post_id = (int) $urltest['fragment'];
} elseif ( preg_match( '/post-[0-9]+/', $urltest['fragment'] ) ) {
// ...a post ID in the form 'post-###',
$post_id = preg_replace( '/[^0-9]+/', '', $urltest['fragment'] );
} elseif ( is_string( $urltest['fragment'] ) ) {
// ...or a string #title, a little more complicated.
$title = preg_replace( '/[^a-z0-9]/i', '.', $urltest['fragment'] );
$sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
$post_id = $wpdb->get_var( $sql );
if ( ! $post_id ) {
// Returning unknown error '0' is better than die()'ing.
return $this->pingback_error( 0, '' );
}
}
} else {
// TODO: Attempt to extract a post ID from the given URL.
return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
}
$post_id = (int) $post_id;
$post = get_post( $post_id );
if ( ! $post ) { // Post not found.
return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
}
if ( url_to_postid( $pagelinkedfrom ) == $post_id ) {
return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
}
// Check if pings are on.
if ( ! pings_open( $post ) ) {
return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
}
// Let's check that the remote site didn't already pingback this entry.
if ( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_id, $pagelinkedfrom ) ) ) {
return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
}
/*
* The remote site may have sent the pingback before it finished publishing its own content
* containing this pingback URL. If that happens then it won't be immediately possible to fetch
* the pinging post; adding a small delay reduces the likelihood of this happening.
*
* While there are more robust methods than calling `sleep()` here (because `sleep()` merely
* mitigates the risk of requesting the remote post before it's available), this is effective
* enough for most cases and avoids introducing more complexity into this code.
*
* One way to improve the reliability of this code might be to add failure-handling to the remote
* fetch and retry up to a set number of times if it receives a 404. This could also handle 401 and
* 403 responses to differentiate the "does not exist" failure from the "may not access" failure.
*/
sleep( 1 );
$remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
/** This filter is documented in wp-includes/class-wp-http.php */
$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $pagelinkedfrom );
// Let's check the remote site.
$http_api_args = array(
'timeout' => 10,
'redirection' => 0,
'limit_response_size' => 153600, // 150 KB
'user-agent' => "$user_agent; verifying pingback from $remote_ip",
'headers' => array(
'X-Pingback-Forwarded-For' => $remote_ip,
),
);
$request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
$remote_source = wp_remote_retrieve_body( $request );
$remote_source_original = $remote_source;
if ( ! $remote_source ) {
return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
}
/**
* Filters the pingback remote source.
*
* @since 2.5.0
*
* @param string $remote_source Response source for the page linked from.
* @param string $pagelinkedto URL of the page linked to.
*/
$remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
// Work around bug in strip_tags():
$remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
$remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
$remote_source = preg_replace( '/<\/*(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/', "\n\n", $remote_source );
preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
$title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
if ( empty( $title ) ) {
return $this->pingback_error( 32, __( 'A title on that page cannot be found.' ) );
}
// Remove all script and style tags including their content.
$remote_source = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $remote_source );
// Just keep the tag we need.
$remote_source = strip_tags( $remote_source, '<a>' );
$p = explode( "\n\n", $remote_source );
$preg_target = preg_quote( $pagelinkedto, '|' );
foreach ( $p as $para ) {
if ( str_contains( $para, $pagelinkedto ) ) { // It exists, but is it a link?
preg_match( '|<a[^>]+?' . $preg_target . '[^>]*>([^>]+?)</a>|', $para, $context );
// If the URL isn't in a link context, keep looking.
if ( empty( $context ) ) {
continue;
}
/*
* We're going to use this fake tag to mark the context in a bit.
* The marker is needed in case the link text appears more than once in the paragraph.
*/
$excerpt = preg_replace( '|\</?wpcontext\>|', '', $para );
// prevent really long link text
if ( strlen( $context[1] ) > 100 ) {
$context[1] = substr( $context[1], 0, 100 ) . '…';
}
$marker = '<wpcontext>' . $context[1] . '</wpcontext>'; // Set up our marker.
$excerpt = str_replace( $context[0], $marker, $excerpt ); // Swap out the link for our marker.
$excerpt = strip_tags( $excerpt, '<wpcontext>' ); // Strip all tags but our context marker.
$excerpt = trim( $excerpt );
$preg_marker = preg_quote( $marker, '|' );
$excerpt = preg_replace( "|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt );
$excerpt = strip_tags( $excerpt ); // YES, again, to remove the marker wrapper.
break;
}
}
if ( empty( $context ) ) { // Link to target not found.
return $this->pingback_error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) );
}
$pagelinkedfrom = str_replace( '&', '&', $pagelinkedfrom );
$context = '[…] ' . esc_html( $excerpt ) . ' […]';
$pagelinkedfrom = $this->escape( $pagelinkedfrom );
$comment_post_id = (int) $post_id;
$comment_author = $title;
$comment_author_email = '';
$this->escape( $comment_author );
$comment_author_url = $pagelinkedfrom;
$comment_content = $context;
$this->escape( $comment_content );
$comment_type = 'pingback';
$commentdata = array(
'comment_post_ID' => $comment_post_id,
);
$commentdata += compact(
'comment_author',
'comment_author_url',
'comment_author_email',
'comment_content',
'comment_type',
'remote_source',
'remote_source_original'
);
$comment_id = wp_new_comment( $commentdata );
if ( is_wp_error( $comment_id ) ) {
return $this->pingback_error( 0, $comment_id->get_error_message() );
}
/**
* Fires after a post pingback has been sent.
*
* @since 0.71
*
* @param int $comment_id Comment ID.
*/
do_action( 'pingback_post', $comment_id );
/* translators: 1: URL of the page linked from, 2: URL of the page linked to. */
return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
}
Hooks
- apply_filters( ‘http_headers_useragent’,
string $user_agent ,string $url ) Filters the user agent value sent with an HTTP request.
- apply_filters( ‘pingback_ping_source_uri’,
string $pagelinkedfrom ,string $pagelinkedto ) Filters the pingback source URI.
- do_action( ‘pingback_post’,
int $comment_id ) Fires after a post pingback has been sent.
- apply_filters( ‘pre_remote_source’,
string $remote_source ,string $pagelinkedto ) Filters the pingback remote source.
- do_action( ‘xmlrpc_call’,
string $name ,array|string $args ,wp_xmlrpc_server $server ) Fires after the XML-RPC user has been authenticated but before the rest of the method logic begins.
Changelog
Version | Description |
---|---|
1.5.0 | Introduced. |
User Contributed Notes
You must log in before being able to contribute a note or feedback.