: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$post_data['post_author'] = $user->ID;
if ( 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) {
unset( $post_data['comment_status'] );
if ( 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) {
unset( $post_data['ping_status'] );
// Do some timestamp voodoo.
if ( ! empty( $post_data['post_date_gmt'] ) ) {
// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
$dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
} elseif ( ! empty( $post_data['post_date'] ) ) {
$dateCreated = $post_data['post_date']->getIso();
// Default to not flagging the post date to be edited unless it's intentional.
$post_data['edit_date'] = false;
if ( ! empty( $dateCreated ) ) {
$post_data['post_date'] = iso8601_to_datetime( $dateCreated );
$post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
// Flag the post date to be edited.
$post_data['edit_date'] = true;
if ( ! isset( $post_data['ID'] ) ) {
$post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
$post_id = $post_data['ID'];
if ( 'post' === $post_data['post_type'] ) {
$error = $this->_toggle_sticky( $post_data, $update );
if ( isset( $post_data['post_thumbnail'] ) ) {
// Empty value deletes, non-empty value adds/updates.
if ( ! $post_data['post_thumbnail'] ) {
delete_post_thumbnail( $post_id );
} elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) {
return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
set_post_thumbnail( $post_id, $post_data['post_thumbnail'] );
unset( $content_struct['post_thumbnail'] );
if ( isset( $post_data['custom_fields'] ) ) {
$this->set_custom_fields( $post_id, $post_data['custom_fields'] );
if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
$post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
// Accumulate term IDs from terms and terms_names.
// First validate the terms specified by ID.
if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
$taxonomies = array_keys( $post_data['terms'] );
foreach ( $taxonomies as $taxonomy ) {
if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
$term_ids = $post_data['terms'][ $taxonomy ];
$terms[ $taxonomy ] = array();
foreach ( $term_ids as $term_id ) {
$term = get_term_by( 'id', $term_id, $taxonomy );
return new IXR_Error( 403, __( 'Invalid term ID.' ) );
$terms[ $taxonomy ][] = (int) $term_id;
// Now validate terms specified by name.
if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
$taxonomies = array_keys( $post_data['terms_names'] );
foreach ( $taxonomies as $taxonomy ) {
if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
* For hierarchical taxonomies, we can't assign a term when multiple terms
* in the hierarchy share the same name.
$ambiguous_terms = array();
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
$tax_term_names = get_terms(
// Count the number of terms with the same name.
$tax_term_names_count = array_count_values( $tax_term_names );
// Filter out non-ambiguous term names.
$ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) );
$ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
$term_names = $post_data['terms_names'][ $taxonomy ];
foreach ( $term_names as $term_name ) {
if ( in_array( $term_name, $ambiguous_terms, true ) ) {
return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
$term = get_term_by( 'name', $term_name, $taxonomy );
// Term doesn't exist, so check that the user is allowed to create new terms.
if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
$term_info = wp_insert_term( $term_name, $taxonomy );
if ( is_wp_error( $term_info ) ) {
return new IXR_Error( 500, $term_info->get_error_message() );
$terms[ $taxonomy ][] = (int) $term_info['term_id'];
$terms[ $taxonomy ][] = (int) $term->term_id;
$post_data['tax_input'] = $terms;
unset( $post_data['terms'], $post_data['terms_names'] );
if ( isset( $post_data['post_format'] ) ) {
$format = set_post_format( $post_id, $post_data['post_format'] );
if ( is_wp_error( $format ) ) {
return new IXR_Error( 500, $format->get_error_message() );
unset( $post_data['post_format'] );
$enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
$this->add_enclosure_if_new( $post_id, $enclosure );
$this->attach_uploads( $post_id, $post_data['post_content'] );
* Filters post data array to be inserted via XML-RPC.
* @param array $post_data Parsed array of post data.
* @param array $content_struct Post data array.
$post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
// Remove all null values to allow for using the insert/update post default values for those keys instead.
$post_data = array_filter(
static function ( $value ) {
$post_id = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
if ( is_wp_error( $post_id ) ) {
return new IXR_Error( 500, $post_id->get_error_message() );
return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) );
return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) );
return (string) $post_id;
* Edits a post for any registered post type.
* The $content_struct parameter only needs to contain fields that
* should be changed. All other fields will retain their existing values.
* Method arguments. Note: arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* @type array $4 Extra content arguments.
* @return true|IXR_Error True on success, IXR_Error on failure.
public function wp_editPost( $args ) {
if ( ! $this->minimum_args( $args, 5 ) ) {
$post_id = (int) $args[3];
$content_struct = $args[4];
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'wp.editPost', $args, $this );
$post = get_post( $post_id, ARRAY_A );
if ( empty( $post['ID'] ) ) {
return new IXR_Error( 404, __( 'Invalid post ID.' ) );
if ( isset( $content_struct['if_not_modified_since'] ) ) {
// If the post has been modified since the date provided, return an error.
if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
// Convert the date field back to IXR form.
$post['post_date'] = $this->_convert_date( $post['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 ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
unset( $post['post_date_gmt'] );
$post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
* If the API client did not provide 'post_date', then we must not perpetuate the value that
* was stored in the database, or it will appear to be an intentional edit. Conveying it here
* as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt'
* to get set with the value that was originally stored in the database when the draft was created.
if ( ! isset( $content_struct['post_date'] ) ) {
unset( $post['post_date'] );
$merged_content_struct = array_merge( $post, $content_struct );
$retval = $this->_insert_post( $user, $merged_content_struct );
if ( $retval instanceof IXR_Error ) {
* Deletes a post for any registered post type.
* Method arguments. Note: arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* @return true|IXR_Error True on success, IXR_Error instance on failure.
public function wp_deletePost( $args ) {
if ( ! $this->minimum_args( $args, 4 ) ) {
$post_id = (int) $args[3];
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this );
$post = get_post( $post_id, ARRAY_A );
if ( empty( $post['ID'] ) ) {
return new IXR_Error( 404, __( 'Invalid post ID.' ) );
if ( ! current_user_can( 'delete_post', $post_id ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
$result = wp_delete_post( $post_id );
return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
* The optional $fields parameter specifies what fields will be included
* in the response array. This should be a list of field names. 'post_id' will
* always be included in the response regardless of the value of $fields.
* Instead of, or in addition to, individual field names, conceptual group
* names can be used to specify multiple fields. The available conceptual
* groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
* Method arguments. Note: arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* @type array $4 Optional. The subset of post type fields to return.
* @return array|IXR_Error Array contains (based on $fields parameter):
public function wp_getPost( $args ) {
if ( ! $this->minimum_args( $args, 4 ) ) {
$post_id = (int) $args[3];
if ( isset( $args[4] ) ) {
* Filters the default post query fields used by the given XML-RPC method.
* @param array $fields An array of post fields to retrieve. By default,
* contains 'post', 'terms', and 'custom_fields'.
* @param string $method Method name.
$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'wp.getPost', $args, $this );
$post = get_post( $post_id, ARRAY_A );
if ( empty( $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.' ) );
return $this->_prepare_post( $post, $fields );
* @see wp_get_recent_posts()
* @see wp_getPost() for more on `$fields`
* @see get_posts() for more on `$filter` values
* Method arguments. Note: arguments must be ordered as documented.
* @type int $0 Blog ID (unused).
* @type string $1 Username.
* @type string $2 Password.
* @type array $3 Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
* 'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
* @type array $4 Optional. The subset of post type fields to return in the response array.
* @return array|IXR_Error Array containing a collection of posts.
public function wp_getPosts( $args ) {
if ( ! $this->minimum_args( $args, 3 ) ) {
$filter = isset( $args[3] ) ? $args[3] : array();
if ( isset( $args[4] ) ) {
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
$user = $this->login( $username, $password );
/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this );
if ( isset( $filter['post_type'] ) ) {
$post_type = get_post_type_object( $filter['post_type'] );
if ( ! ( (bool) $post_type ) ) {
return new IXR_Error( 403, __( 'Invalid post type.' ) );