: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
* @param WP_Post_Type $post_type Post type object.
* @param array $fields The subset of post fields to return.
* @return array The prepared post type data.
protected function _prepare_post_type( $post_type, $fields ) {
'name' => $post_type->name,
'label' => $post_type->label,
'hierarchical' => (bool) $post_type->hierarchical,
'public' => (bool) $post_type->public,
'show_ui' => (bool) $post_type->show_ui,
'_builtin' => (bool) $post_type->_builtin,
'has_archive' => (bool) $post_type->has_archive,
'supports' => get_all_post_type_supports( $post_type->name ),
if ( in_array( 'labels', $fields, true ) ) {
$_post_type['labels'] = (array) $post_type->labels;
if ( in_array( 'cap', $fields, true ) ) {
$_post_type['cap'] = (array) $post_type->cap;
$_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
if ( in_array( 'menu', $fields, true ) ) {
$_post_type['menu_position'] = (int) $post_type->menu_position;
$_post_type['menu_icon'] = $post_type->menu_icon;
$_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
if ( in_array( 'taxonomies', $fields, true ) ) {
$_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
* Filters XML-RPC-prepared date for the given post type.
* @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
* @param array $_post_type An array of post type data.
* @param WP_Post_Type $post_type Post type object.
return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
* Prepares media item data for return in an XML-RPC object.
* @param WP_Post $media_item The unprepared media item data.
* @param string $thumbnail_size The image size to use for the thumbnail URL.
* @return array The prepared media item data.
protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
'attachment_id' => (string) $media_item->ID,
'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
'parent' => $media_item->post_parent,
'link' => wp_get_attachment_url( $media_item->ID ),
'title' => $media_item->post_title,
'caption' => $media_item->post_excerpt,
'description' => $media_item->post_content,
'metadata' => wp_get_attachment_metadata( $media_item->ID ),
'type' => $media_item->post_mime_type,
'alt' => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
$thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
$_media_item['thumbnail'] = $thumbnail_src[0];
$_media_item['thumbnail'] = $_media_item['link'];
* Filters XML-RPC-prepared data for the given media item.
* @param array $_media_item An array of media item data.
* @param WP_Post $media_item Media item object.
* @param string $thumbnail_size Image size.
return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
* Prepares page data for return in an XML-RPC object.
* @param WP_Post $page The unprepared page data.
* @return array The prepared page data.
protected function _prepare_page( $page ) {
// Get all of the page content and link.
$full_page = get_extended( $page->post_content );
$link = get_permalink( $page->ID );
// Get info the page parent if there is one.
if ( ! empty( $page->post_parent ) ) {
$parent = get_post( $page->post_parent );
$parent_title = $parent->post_title;
// Determine comment and ping settings.
$allow_comments = comments_open( $page->ID ) ? 1 : 0;
$allow_pings = pings_open( $page->ID ) ? 1 : 0;
$page_date = $this->_convert_date( $page->post_date );
$page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
// Pull the categories info together.
if ( is_object_in_taxonomy( 'page', 'category' ) ) {
foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
$categories[] = get_cat_name( $cat_id );
$author = get_userdata( $page->post_author );
$page_template = get_page_template_slug( $page->ID );
if ( empty( $page_template ) ) {
$page_template = 'default';
'dateCreated' => $page_date,
'userid' => $page->post_author,
'page_status' => $page->post_status,
'description' => $full_page['main'],
'title' => $page->post_title,
'categories' => $categories,
'excerpt' => $page->post_excerpt,
'text_more' => $full_page['extended'],
'mt_allow_comments' => $allow_comments,
'mt_allow_pings' => $allow_pings,
'wp_slug' => $page->post_name,
'wp_password' => $page->post_password,
'wp_author' => $author->display_name,
'wp_page_parent_id' => $page->post_parent,
'wp_page_parent_title' => $parent_title,
'wp_page_order' => $page->menu_order,
'wp_author_id' => (string) $author->ID,
'wp_author_display_name' => $author->display_name,
'date_created_gmt' => $page_date_gmt,
'custom_fields' => $this->get_custom_fields( $page->ID ),
'wp_page_template' => $page_template,
* Filters XML-RPC-prepared data for the given page.
* @param array $_page An array of page data.
* @param WP_Post $page Page object.
return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
* Prepares comment data for return in an XML-RPC object.
* @param WP_Comment $comment The unprepared comment data.
* @return array The prepared comment data.
protected function _prepare_comment( $comment ) {
$comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
if ( '0' == $comment->comment_approved ) {
$comment_status = 'hold';
} elseif ( 'spam' === $comment->comment_approved ) {
$comment_status = 'spam';
} elseif ( '1' == $comment->comment_approved ) {
$comment_status = 'approve';
$comment_status = $comment->comment_approved;
'date_created_gmt' => $comment_date_gmt,
'user_id' => $comment->user_id,
'comment_id' => $comment->comment_ID,
'parent' => $comment->comment_parent,
'status' => $comment_status,
'content' => $comment->comment_content,
'link' => get_comment_link( $comment ),
'post_id' => $comment->comment_post_ID,
'post_title' => get_the_title( $comment->comment_post_ID ),
'author' => $comment->comment_author,
'author_url' => $comment->comment_author_url,
'author_email' => $comment->comment_author_email,
'author_ip' => $comment->comment_author_IP,
'type' => $comment->comment_type,
* Filters XML-RPC-prepared data for the given comment.
* @param array $_comment An array of prepared comment data.
* @param WP_Comment $comment Comment object.
return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
* Prepares user data for return in an XML-RPC object.
* @param WP_User $user The unprepared user object.
* @param array $fields The subset of user fields to return.
* @return array The prepared user data.
protected function _prepare_user( $user, $fields ) {
$_user = array( 'user_id' => (string) $user->ID );
'username' => $user->user_login,
'first_name' => $user->user_firstname,
'last_name' => $user->user_lastname,
'registered' => $this->_convert_date( $user->user_registered ),
'bio' => $user->user_description,
'email' => $user->user_email,
'nickname' => $user->nickname,
'nicename' => $user->user_nicename,
'url' => $user->user_url,
'display_name' => $user->display_name,
if ( in_array( 'all', $fields, true ) ) {
$_user = array_merge( $_user, $user_fields );
if ( in_array( 'basic', $fields, true ) ) {
$basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
$fields = array_merge( $fields, $basic_fields );
$requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
$_user = array_merge( $_user, $requested_fields );
* Filters XML-RPC-prepared data for the given user.
* @param array $_user An array of user data.
* @param WP_User $user User object.
* @param array $fields An array of user fields.
return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
* Creates a new post for any registered post type.
* @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
* Method arguments. Note: top-level arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* Content struct for adding a new post. See wp_insert_post() for information on
* @type string $post_type Post type. Default 'post'.
* @type string $post_status Post status. Default 'draft'
* @type string $post_title Post title.
* @type int $post_author Post author ID.
* @type string $post_excerpt Post excerpt.
* @type string $post_content Post content.
* @type string $post_date_gmt Post date in GMT.
* @type string $post_date Post date.
* @type string $post_password Post password (20-character limit).
* @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
* @type string $ping_status Post ping status. Accepts 'open' or 'closed'.
* @type bool $sticky Whether the post should be sticky. Automatically false if
* `$post_status` is 'private'.
* @type int $post_thumbnail ID of an image to use as the post thumbnail/featured image.
* @type array $custom_fields Array of meta key/value pairs to add to the post.
* @type array $terms Associative array with taxonomy names as keys and arrays
* @type array $terms_names Associative array with taxonomy names as keys and arrays
* of term names as values.
* @type array $enclosure {
* Array of feed enclosure data to add to post meta.
* @type string $url URL for the feed enclosure.
* @type int $length Size in bytes of the enclosure.
* @type string $type Mime-type for the enclosure.
* @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
public function wp_newPost( $args ) {
if ( ! $this->minimum_args( $args, 4 ) ) {
$content_struct = $args[3];
$user = $this->login( $username, $password );
// Convert the date field back to IXR form.
if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
$content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
* Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
* since _insert_post() will ignore the non-GMT date if the GMT date is set.
if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
unset( $content_struct['post_date_gmt'] );
$content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'wp.newPost', $args, $this );
unset( $content_struct['ID'] );
return $this->_insert_post( $user, $content_struct );
* Helper method for filtering out elements from an array.
* @param int $count Number to compare to one.
* @return bool True if the number is greater than one, false otherwise.
private function _is_greater_than_one( $count ) {
* Encapsulates the logic for sticking a post and determining if
* the user has permission to do so.
* @param array $post_data
private function _toggle_sticky( $post_data, $update = false ) {
$post_type = get_post_type_object( $post_data['post_type'] );
// Private and password-protected posts cannot be stickied.
if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
// Error if the client tried to stick the post, otherwise, silently unstick.
if ( ! empty( $post_data['sticky'] ) ) {
return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
unstick_post( $post_data['ID'] );
} elseif ( isset( $post_data['sticky'] ) ) {
if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
$sticky = wp_validate_boolean( $post_data['sticky'] );
stick_post( $post_data['ID'] );
unstick_post( $post_data['ID'] );
* Helper method for wp_newPost() and wp_editPost(), containing shared logic.
* @param WP_User $user The post author if post_author isn't set in $content_struct.
* @param array|IXR_Error $content_struct Post data to insert.
* @return IXR_Error|string
protected function _insert_post( $user, $content_struct ) {
'post_status' => 'draft',
'post_thumbnail' => null,
$post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
$post_type = get_post_type_object( $post_data['post_type'] );
return new IXR_Error( 403, __( 'Invalid post type.' ) );
$update = ! empty( $post_data['ID'] );
if ( ! get_post( $post_data['ID'] ) ) {
return new IXR_Error( 401, __( 'Invalid post ID.' ) );
if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) {
return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
switch ( $post_data['post_status'] ) {
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
if ( ! get_post_status_object( $post_data['post_status'] ) ) {
$post_data['post_status'] = 'draft';
if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
$post_data['post_author'] = absint( $post_data['post_author'] );
if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
$author = get_userdata( $post_data['post_author'] );
return new IXR_Error( 404, __( 'Invalid author ID.' ) );