: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
public function mt_getRecentPostTitles( $args ) {
if ( isset( $args[3] ) ) {
$query = array( 'numberposts' => absint( $args[3] ) );
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.getRecentPostTitles', $args, $this );
$posts_list = wp_get_recent_posts( $query );
$this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
foreach ( $posts_list as $entry ) {
if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
$post_date = $this->_convert_date( $entry['post_date'] );
$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
'dateCreated' => $post_date,
'userid' => $entry['post_author'],
'postid' => (string) $entry['ID'],
'title' => $entry['post_title'],
'post_status' => $entry['post_status'],
'date_created_gmt' => $post_date_gmt,
* Retrieves the list of all categories on a blog.
* Method arguments. Note: arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* @return array|IXR_Error
public function mt_getCategoryList( $args ) {
$user = $this->login( $username, $password );
if ( ! current_user_can( 'edit_posts' ) ) {
return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.getCategoryList', $args, $this );
$categories_struct = array();
foreach ( $cats as $cat ) {
$struct['categoryId'] = $cat->term_id;
$struct['categoryName'] = $cat->name;
$categories_struct[] = $struct;
return $categories_struct;
* Retrieves post categories.
* Method arguments. Note: arguments must be ordered as documented.
* @type string $1 Username.
* @type string $2 Password.
* @return array|IXR_Error
public function mt_getPostCategories( $args ) {
$post_id = (int) $args[0];
$user = $this->login( $username, $password );
if ( ! get_post( $post_id ) ) {
return new IXR_Error( 404, __( 'Invalid post ID.' ) );
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.getPostCategories', $args, $this );
$catids = wp_get_post_categories( (int) $post_id );
// First listed category will be the primary category.
foreach ( $catids as $catid ) {
'categoryName' => get_cat_name( $catid ),
'categoryId' => (string) $catid,
'isPrimary' => $isPrimary,
* Sets categories for a post.
* Method arguments. Note: arguments must be ordered as documented.
* @type string $1 Username.
* @type string $2 Password.
* @type array $3 Categories.
* @return true|IXR_Error True on success.
public function mt_setPostCategories( $args ) {
$post_id = (int) $args[0];
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.setPostCategories', $args, $this );
if ( ! get_post( $post_id ) ) {
return new IXR_Error( 404, __( 'Invalid post ID.' ) );
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
foreach ( $categories as $cat ) {
$catids[] = $cat['categoryId'];
wp_set_post_categories( $post_id, $catids );
* Retrieves an array of methods supported by this server.
public function mt_supportedMethods() {
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.supportedMethods', array(), $this );
return array_keys( $this->methods );
* Retrieves an empty array because we don't support per-post text filters.
public function mt_supportedTextFilters() {
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.supportedTextFilters', array(), $this );
* Filters the MoveableType text filters list for XML-RPC.
* @param array $filters An array of text filters.
return apply_filters( 'xmlrpc_text_filters', array() );
* Retrieves trackbacks sent to a given post.
* @global wpdb $wpdb WordPress database abstraction object.
* @return array|IXR_Error
public function mt_getTrackbackPings( $post_id ) {
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.getTrackbackPings', $post_id, $this );
$actual_post = get_post( $post_id, ARRAY_A );
return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
$comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
$trackback_pings = array();
foreach ( $comments as $comment ) {
if ( 'trackback' === $comment->comment_type ) {
$content = $comment->comment_content;
$title = substr( $content, 8, ( strpos( $content, '</strong>' ) - 8 ) );
$trackback_pings[] = array(
'pingURL' => $comment->comment_author_url,
'pingIP' => $comment->comment_author_IP,
* Sets a post's publish status to 'publish'.
* Method arguments. Note: arguments must be ordered as documented.
* @type string $1 Username.
* @type string $2 Password.
public function mt_publishPost( $args ) {
$post_id = (int) $args[0];
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'mt.publishPost', $args, $this );
$postdata = get_post( $post_id, ARRAY_A );
return new IXR_Error( 404, __( 'Invalid post ID.' ) );
if ( ! current_user_can( 'publish_posts' ) || ! current_user_can( 'edit_post', $post_id ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
$postdata['post_status'] = 'publish';
// Retain old categories.
$postdata['post_category'] = wp_get_post_categories( $post_id );
$this->escape( $postdata );
return wp_update_post( $postdata );
* Specs on www.hixie.ch/specs/pingback/pingback
* Retrieves a pingback and registers it.
* @global wpdb $wpdb WordPress database abstraction object.
* Method arguments. Note: arguments must be ordered as documented.
* @type string $0 URL of page linked from.
* @type string $1 URL of page linked to.
* @return string|IXR_Error
public function pingback_ping( $args ) {
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'pingback.ping', $args, $this );
$pagelinkedfrom = str_replace( '&', '&', $args[0] );
$pagelinkedto = str_replace( '&', '&', $args[1] );
$pagelinkedto = str_replace( '&', '&', $pagelinkedto );
* Filters the pingback source URI.
* @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' ) ) );
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 );
} 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 );
// Returning unknown error '0' is better than die()'ing.
return $this->pingback_error( 0, '' );
// 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.
$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.
'limit_response_size' => 153600, // 150 KB
'user-agent' => "$user_agent; verifying pingback from $remote_ip",
'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.
* @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] : '';
return $this->pingback_error( 32, __( 'A title on that page cannot be found.' ) );