balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-xmlrpc-server.php (212923B)


      1 <?php
      2 /**
      3  * XML-RPC protocol support for WordPress
      4  *
      5  * @package WordPress
      6  * @subpackage Publishing
      7  */
      8 
      9 /**
     10  * WordPress XMLRPC server implementation.
     11  *
     12  * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and
     13  * pingback. Additional WordPress API for managing comments, pages, posts,
     14  * options, etc.
     15  *
     16  * As of WordPress 3.5.0, XML-RPC is enabled by default. It can be disabled
     17  * via the {@see 'xmlrpc_enabled'} filter found in wp_xmlrpc_server::set_is_enabled().
     18  *
     19  * @since 1.5.0
     20  *
     21  * @see IXR_Server
     22  */
     23 class wp_xmlrpc_server extends IXR_Server {
     24 	/**
     25 	 * Methods.
     26 	 *
     27 	 * @var array
     28 	 */
     29 	public $methods;
     30 
     31 	/**
     32 	 * Blog options.
     33 	 *
     34 	 * @var array
     35 	 */
     36 	public $blog_options;
     37 
     38 	/**
     39 	 * IXR_Error instance.
     40 	 *
     41 	 * @var IXR_Error
     42 	 */
     43 	public $error;
     44 
     45 	/**
     46 	 * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
     47 	 *
     48 	 * @var bool
     49 	 */
     50 	protected $auth_failed = false;
     51 
     52 	/**
     53 	 * Flags that XML-RPC is enabled
     54 	 *
     55 	 * @var bool
     56 	 */
     57 	private $is_enabled;
     58 
     59 	/**
     60 	 * Registers all of the XMLRPC methods that XMLRPC server understands.
     61 	 *
     62 	 * Sets up server and method property. Passes XMLRPC
     63 	 * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend
     64 	 * or replace XML-RPC methods.
     65 	 *
     66 	 * @since 1.5.0
     67 	 */
     68 	public function __construct() {
     69 		$this->methods = array(
     70 			// WordPress API.
     71 			'wp.getUsersBlogs'                 => 'this:wp_getUsersBlogs',
     72 			'wp.newPost'                       => 'this:wp_newPost',
     73 			'wp.editPost'                      => 'this:wp_editPost',
     74 			'wp.deletePost'                    => 'this:wp_deletePost',
     75 			'wp.getPost'                       => 'this:wp_getPost',
     76 			'wp.getPosts'                      => 'this:wp_getPosts',
     77 			'wp.newTerm'                       => 'this:wp_newTerm',
     78 			'wp.editTerm'                      => 'this:wp_editTerm',
     79 			'wp.deleteTerm'                    => 'this:wp_deleteTerm',
     80 			'wp.getTerm'                       => 'this:wp_getTerm',
     81 			'wp.getTerms'                      => 'this:wp_getTerms',
     82 			'wp.getTaxonomy'                   => 'this:wp_getTaxonomy',
     83 			'wp.getTaxonomies'                 => 'this:wp_getTaxonomies',
     84 			'wp.getUser'                       => 'this:wp_getUser',
     85 			'wp.getUsers'                      => 'this:wp_getUsers',
     86 			'wp.getProfile'                    => 'this:wp_getProfile',
     87 			'wp.editProfile'                   => 'this:wp_editProfile',
     88 			'wp.getPage'                       => 'this:wp_getPage',
     89 			'wp.getPages'                      => 'this:wp_getPages',
     90 			'wp.newPage'                       => 'this:wp_newPage',
     91 			'wp.deletePage'                    => 'this:wp_deletePage',
     92 			'wp.editPage'                      => 'this:wp_editPage',
     93 			'wp.getPageList'                   => 'this:wp_getPageList',
     94 			'wp.getAuthors'                    => 'this:wp_getAuthors',
     95 			'wp.getCategories'                 => 'this:mw_getCategories',     // Alias.
     96 			'wp.getTags'                       => 'this:wp_getTags',
     97 			'wp.newCategory'                   => 'this:wp_newCategory',
     98 			'wp.deleteCategory'                => 'this:wp_deleteCategory',
     99 			'wp.suggestCategories'             => 'this:wp_suggestCategories',
    100 			'wp.uploadFile'                    => 'this:mw_newMediaObject',    // Alias.
    101 			'wp.deleteFile'                    => 'this:wp_deletePost',        // Alias.
    102 			'wp.getCommentCount'               => 'this:wp_getCommentCount',
    103 			'wp.getPostStatusList'             => 'this:wp_getPostStatusList',
    104 			'wp.getPageStatusList'             => 'this:wp_getPageStatusList',
    105 			'wp.getPageTemplates'              => 'this:wp_getPageTemplates',
    106 			'wp.getOptions'                    => 'this:wp_getOptions',
    107 			'wp.setOptions'                    => 'this:wp_setOptions',
    108 			'wp.getComment'                    => 'this:wp_getComment',
    109 			'wp.getComments'                   => 'this:wp_getComments',
    110 			'wp.deleteComment'                 => 'this:wp_deleteComment',
    111 			'wp.editComment'                   => 'this:wp_editComment',
    112 			'wp.newComment'                    => 'this:wp_newComment',
    113 			'wp.getCommentStatusList'          => 'this:wp_getCommentStatusList',
    114 			'wp.getMediaItem'                  => 'this:wp_getMediaItem',
    115 			'wp.getMediaLibrary'               => 'this:wp_getMediaLibrary',
    116 			'wp.getPostFormats'                => 'this:wp_getPostFormats',
    117 			'wp.getPostType'                   => 'this:wp_getPostType',
    118 			'wp.getPostTypes'                  => 'this:wp_getPostTypes',
    119 			'wp.getRevisions'                  => 'this:wp_getRevisions',
    120 			'wp.restoreRevision'               => 'this:wp_restoreRevision',
    121 
    122 			// Blogger API.
    123 			'blogger.getUsersBlogs'            => 'this:blogger_getUsersBlogs',
    124 			'blogger.getUserInfo'              => 'this:blogger_getUserInfo',
    125 			'blogger.getPost'                  => 'this:blogger_getPost',
    126 			'blogger.getRecentPosts'           => 'this:blogger_getRecentPosts',
    127 			'blogger.newPost'                  => 'this:blogger_newPost',
    128 			'blogger.editPost'                 => 'this:blogger_editPost',
    129 			'blogger.deletePost'               => 'this:blogger_deletePost',
    130 
    131 			// MetaWeblog API (with MT extensions to structs).
    132 			'metaWeblog.newPost'               => 'this:mw_newPost',
    133 			'metaWeblog.editPost'              => 'this:mw_editPost',
    134 			'metaWeblog.getPost'               => 'this:mw_getPost',
    135 			'metaWeblog.getRecentPosts'        => 'this:mw_getRecentPosts',
    136 			'metaWeblog.getCategories'         => 'this:mw_getCategories',
    137 			'metaWeblog.newMediaObject'        => 'this:mw_newMediaObject',
    138 
    139 			// MetaWeblog API aliases for Blogger API.
    140 			// See http://www.xmlrpc.com/stories/storyReader$2460
    141 			'metaWeblog.deletePost'            => 'this:blogger_deletePost',
    142 			'metaWeblog.getUsersBlogs'         => 'this:blogger_getUsersBlogs',
    143 
    144 			// MovableType API.
    145 			'mt.getCategoryList'               => 'this:mt_getCategoryList',
    146 			'mt.getRecentPostTitles'           => 'this:mt_getRecentPostTitles',
    147 			'mt.getPostCategories'             => 'this:mt_getPostCategories',
    148 			'mt.setPostCategories'             => 'this:mt_setPostCategories',
    149 			'mt.supportedMethods'              => 'this:mt_supportedMethods',
    150 			'mt.supportedTextFilters'          => 'this:mt_supportedTextFilters',
    151 			'mt.getTrackbackPings'             => 'this:mt_getTrackbackPings',
    152 			'mt.publishPost'                   => 'this:mt_publishPost',
    153 
    154 			// Pingback.
    155 			'pingback.ping'                    => 'this:pingback_ping',
    156 			'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
    157 
    158 			'demo.sayHello'                    => 'this:sayHello',
    159 			'demo.addTwoNumbers'               => 'this:addTwoNumbers',
    160 		);
    161 
    162 		$this->initialise_blog_option_info();
    163 
    164 		/**
    165 		 * Filters the methods exposed by the XML-RPC server.
    166 		 *
    167 		 * This filter can be used to add new methods, and remove built-in methods.
    168 		 *
    169 		 * @since 1.5.0
    170 		 *
    171 		 * @param string[] $methods An array of XML-RPC methods, keyed by their methodName.
    172 		 */
    173 		$this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
    174 
    175 		$this->set_is_enabled();
    176 	}
    177 
    178 	/**
    179 	 * Set wp_xmlrpc_server::$is_enabled property.
    180 	 *
    181 	 * Determine whether the xmlrpc server is enabled on this WordPress install
    182 	 * and set the is_enabled property accordingly.
    183 	 *
    184 	 * @since 5.7.3
    185 	 */
    186 	private function set_is_enabled() {
    187 		/*
    188 		 * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
    189 		 * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
    190 		 */
    191 		$is_enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
    192 		if ( false === $is_enabled ) {
    193 			$is_enabled = apply_filters( 'option_enable_xmlrpc', true );
    194 		}
    195 
    196 		/**
    197 		 * Filters whether XML-RPC methods requiring authentication are enabled.
    198 		 *
    199 		 * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
    200 		 * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
    201 		 * as for publishing purposes - are enabled.
    202 		 *
    203 		 * Further, the filter does not control whether pingbacks or other custom endpoints that don't
    204 		 * require authentication are enabled. This behavior is expected, and due to how parity was matched
    205 		 * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
    206 		 *
    207 		 * To disable XML-RPC methods that require authentication, use:
    208 		 *
    209 		 *     add_filter( 'xmlrpc_enabled', '__return_false' );
    210 		 *
    211 		 * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
    212 		 * and {@see 'xmlrpc_element_limit'} hooks.
    213 		 *
    214 		 * @since 3.5.0
    215 		 *
    216 		 * @param bool $is_enabled Whether XML-RPC is enabled. Default true.
    217 		 */
    218 		$this->is_enabled = apply_filters( 'xmlrpc_enabled', $is_enabled );
    219 	}
    220 
    221 	/**
    222 	 * Make private/protected methods readable for backward compatibility.
    223 	 *
    224 	 * @since 4.0.0
    225 	 *
    226 	 * @param string $name      Method to call.
    227 	 * @param array  $arguments Arguments to pass when calling.
    228 	 * @return array|IXR_Error|false Return value of the callback, false otherwise.
    229 	 */
    230 	public function __call( $name, $arguments ) {
    231 		if ( '_multisite_getUsersBlogs' === $name ) {
    232 			return $this->_multisite_getUsersBlogs( ...$arguments );
    233 		}
    234 		return false;
    235 	}
    236 
    237 	/**
    238 	 * Serves the XML-RPC request.
    239 	 *
    240 	 * @since 2.9.0
    241 	 */
    242 	public function serve_request() {
    243 		$this->IXR_Server( $this->methods );
    244 	}
    245 
    246 	/**
    247 	 * Test XMLRPC API by saying, "Hello!" to client.
    248 	 *
    249 	 * @since 1.5.0
    250 	 *
    251 	 * @return string Hello string response.
    252 	 */
    253 	public function sayHello() {
    254 		return 'Hello!';
    255 	}
    256 
    257 	/**
    258 	 * Test XMLRPC API by adding two numbers for client.
    259 	 *
    260 	 * @since 1.5.0
    261 	 *
    262 	 * @param array $args {
    263 	 *     Method arguments. Note: arguments must be ordered as documented.
    264 	 *
    265 	 *     @type int $number1 A number to add.
    266 	 *     @type int $number2 A second number to add.
    267 	 * }
    268 	 * @return int Sum of the two given numbers.
    269 	 */
    270 	public function addTwoNumbers( $args ) {
    271 		$number1 = $args[0];
    272 		$number2 = $args[1];
    273 		return $number1 + $number2;
    274 	}
    275 
    276 	/**
    277 	 * Log user in.
    278 	 *
    279 	 * @since 2.8.0
    280 	 *
    281 	 * @param string $username User's username.
    282 	 * @param string $password User's password.
    283 	 * @return WP_User|false WP_User object if authentication passed, false otherwise
    284 	 */
    285 	public function login( $username, $password ) {
    286 		if ( ! $this->is_enabled ) {
    287 			$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
    288 			return false;
    289 		}
    290 
    291 		if ( $this->auth_failed ) {
    292 			$user = new WP_Error( 'login_prevented' );
    293 		} else {
    294 			$user = wp_authenticate( $username, $password );
    295 		}
    296 
    297 		if ( is_wp_error( $user ) ) {
    298 			$this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
    299 
    300 			// Flag that authentication has failed once on this wp_xmlrpc_server instance.
    301 			$this->auth_failed = true;
    302 
    303 			/**
    304 			 * Filters the XML-RPC user login error message.
    305 			 *
    306 			 * @since 3.5.0
    307 			 *
    308 			 * @param IXR_Error $error The XML-RPC error message.
    309 			 * @param WP_Error  $user  WP_Error object.
    310 			 */
    311 			$this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
    312 			return false;
    313 		}
    314 
    315 		wp_set_current_user( $user->ID );
    316 		return $user;
    317 	}
    318 
    319 	/**
    320 	 * Check user's credentials. Deprecated.
    321 	 *
    322 	 * @since 1.5.0
    323 	 * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
    324 	 * @see wp_xmlrpc_server::login()
    325 	 *
    326 	 * @param string $username User's username.
    327 	 * @param string $password User's password.
    328 	 * @return bool Whether authentication passed.
    329 	 */
    330 	public function login_pass_ok( $username, $password ) {
    331 		return (bool) $this->login( $username, $password );
    332 	}
    333 
    334 	/**
    335 	 * Escape string or array of strings for database.
    336 	 *
    337 	 * @since 1.5.2
    338 	 *
    339 	 * @param string|array $data Escape single string or array of strings.
    340 	 * @return string|void Returns with string is passed, alters by-reference
    341 	 *                     when array is passed.
    342 	 */
    343 	public function escape( &$data ) {
    344 		if ( ! is_array( $data ) ) {
    345 			return wp_slash( $data );
    346 		}
    347 
    348 		foreach ( $data as &$v ) {
    349 			if ( is_array( $v ) ) {
    350 				$this->escape( $v );
    351 			} elseif ( ! is_object( $v ) ) {
    352 				$v = wp_slash( $v );
    353 			}
    354 		}
    355 	}
    356 
    357 	/**
    358 	 * Send error response to client.
    359 	 *
    360 	 * Send an XML error response to the client. If the endpoint is enabled
    361 	 * an HTTP 200 response is always sent per the XML-RPC specification.
    362 	 *
    363 	 * @since 5.7.3
    364 	 *
    365 	 * @param IXR_Error|string $error   Error code or an error object.
    366 	 * @param false            $message Error message. Optional.
    367 	 */
    368 	public function error( $error, $message = false ) {
    369 		// Accepts either an error object or an error code and message
    370 		if ( $message && ! is_object( $error ) ) {
    371 			$error = new IXR_Error( $error, $message );
    372 		}
    373 
    374 		if ( ! $this->is_enabled ) {
    375 			status_header( $error->code );
    376 		}
    377 
    378 		$this->output( $error->getXml() );
    379 	}
    380 
    381 	/**
    382 	 * Retrieve custom fields for post.
    383 	 *
    384 	 * @since 2.5.0
    385 	 *
    386 	 * @param int $post_id Post ID.
    387 	 * @return array Custom fields, if exist.
    388 	 */
    389 	public function get_custom_fields( $post_id ) {
    390 		$post_id = (int) $post_id;
    391 
    392 		$custom_fields = array();
    393 
    394 		foreach ( (array) has_meta( $post_id ) as $meta ) {
    395 			// Don't expose protected fields.
    396 			if ( ! current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) {
    397 				continue;
    398 			}
    399 
    400 			$custom_fields[] = array(
    401 				'id'    => $meta['meta_id'],
    402 				'key'   => $meta['meta_key'],
    403 				'value' => $meta['meta_value'],
    404 			);
    405 		}
    406 
    407 		return $custom_fields;
    408 	}
    409 
    410 	/**
    411 	 * Set custom fields for post.
    412 	 *
    413 	 * @since 2.5.0
    414 	 *
    415 	 * @param int   $post_id Post ID.
    416 	 * @param array $fields  Custom fields.
    417 	 */
    418 	public function set_custom_fields( $post_id, $fields ) {
    419 		$post_id = (int) $post_id;
    420 
    421 		foreach ( (array) $fields as $meta ) {
    422 			if ( isset( $meta['id'] ) ) {
    423 				$meta['id'] = (int) $meta['id'];
    424 				$pmeta      = get_metadata_by_mid( 'post', $meta['id'] );
    425 
    426 				if ( ! $pmeta || $pmeta->post_id != $post_id ) {
    427 					continue;
    428 				}
    429 
    430 				if ( isset( $meta['key'] ) ) {
    431 					$meta['key'] = wp_unslash( $meta['key'] );
    432 					if ( $meta['key'] !== $pmeta->meta_key ) {
    433 						continue;
    434 					}
    435 					$meta['value'] = wp_unslash( $meta['value'] );
    436 					if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) ) {
    437 						update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
    438 					}
    439 				} elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
    440 					delete_metadata_by_mid( 'post', $meta['id'] );
    441 				}
    442 			} elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
    443 				add_post_meta( $post_id, $meta['key'], $meta['value'] );
    444 			}
    445 		}
    446 	}
    447 
    448 	/**
    449 	 * Retrieve custom fields for a term.
    450 	 *
    451 	 * @since 4.9.0
    452 	 *
    453 	 * @param int $term_id Term ID.
    454 	 * @return array Array of custom fields, if they exist.
    455 	 */
    456 	public function get_term_custom_fields( $term_id ) {
    457 		$term_id = (int) $term_id;
    458 
    459 		$custom_fields = array();
    460 
    461 		foreach ( (array) has_term_meta( $term_id ) as $meta ) {
    462 
    463 			if ( ! current_user_can( 'edit_term_meta', $term_id ) ) {
    464 				continue;
    465 			}
    466 
    467 			$custom_fields[] = array(
    468 				'id'    => $meta['meta_id'],
    469 				'key'   => $meta['meta_key'],
    470 				'value' => $meta['meta_value'],
    471 			);
    472 		}
    473 
    474 		return $custom_fields;
    475 	}
    476 
    477 	/**
    478 	 * Set custom fields for a term.
    479 	 *
    480 	 * @since 4.9.0
    481 	 *
    482 	 * @param int   $term_id Term ID.
    483 	 * @param array $fields  Custom fields.
    484 	 */
    485 	public function set_term_custom_fields( $term_id, $fields ) {
    486 		$term_id = (int) $term_id;
    487 
    488 		foreach ( (array) $fields as $meta ) {
    489 			if ( isset( $meta['id'] ) ) {
    490 				$meta['id'] = (int) $meta['id'];
    491 				$pmeta      = get_metadata_by_mid( 'term', $meta['id'] );
    492 				if ( isset( $meta['key'] ) ) {
    493 					$meta['key'] = wp_unslash( $meta['key'] );
    494 					if ( $meta['key'] !== $pmeta->meta_key ) {
    495 						continue;
    496 					}
    497 					$meta['value'] = wp_unslash( $meta['value'] );
    498 					if ( current_user_can( 'edit_term_meta', $term_id ) ) {
    499 						update_metadata_by_mid( 'term', $meta['id'], $meta['value'] );
    500 					}
    501 				} elseif ( current_user_can( 'delete_term_meta', $term_id ) ) {
    502 					delete_metadata_by_mid( 'term', $meta['id'] );
    503 				}
    504 			} elseif ( current_user_can( 'add_term_meta', $term_id ) ) {
    505 				add_term_meta( $term_id, $meta['key'], $meta['value'] );
    506 			}
    507 		}
    508 	}
    509 
    510 	/**
    511 	 * Set up blog options property.
    512 	 *
    513 	 * Passes property through {@see 'xmlrpc_blog_options'} filter.
    514 	 *
    515 	 * @since 2.6.0
    516 	 */
    517 	public function initialise_blog_option_info() {
    518 		$this->blog_options = array(
    519 			// Read-only options.
    520 			'software_name'           => array(
    521 				'desc'     => __( 'Software Name' ),
    522 				'readonly' => true,
    523 				'value'    => 'WordPress',
    524 			),
    525 			'software_version'        => array(
    526 				'desc'     => __( 'Software Version' ),
    527 				'readonly' => true,
    528 				'value'    => get_bloginfo( 'version' ),
    529 			),
    530 			'blog_url'                => array(
    531 				'desc'     => __( 'WordPress Address (URL)' ),
    532 				'readonly' => true,
    533 				'option'   => 'siteurl',
    534 			),
    535 			'home_url'                => array(
    536 				'desc'     => __( 'Site Address (URL)' ),
    537 				'readonly' => true,
    538 				'option'   => 'home',
    539 			),
    540 			'login_url'               => array(
    541 				'desc'     => __( 'Login Address (URL)' ),
    542 				'readonly' => true,
    543 				'value'    => wp_login_url(),
    544 			),
    545 			'admin_url'               => array(
    546 				'desc'     => __( 'The URL to the admin area' ),
    547 				'readonly' => true,
    548 				'value'    => get_admin_url(),
    549 			),
    550 			'image_default_link_type' => array(
    551 				'desc'     => __( 'Image default link type' ),
    552 				'readonly' => true,
    553 				'option'   => 'image_default_link_type',
    554 			),
    555 			'image_default_size'      => array(
    556 				'desc'     => __( 'Image default size' ),
    557 				'readonly' => true,
    558 				'option'   => 'image_default_size',
    559 			),
    560 			'image_default_align'     => array(
    561 				'desc'     => __( 'Image default align' ),
    562 				'readonly' => true,
    563 				'option'   => 'image_default_align',
    564 			),
    565 			'template'                => array(
    566 				'desc'     => __( 'Template' ),
    567 				'readonly' => true,
    568 				'option'   => 'template',
    569 			),
    570 			'stylesheet'              => array(
    571 				'desc'     => __( 'Stylesheet' ),
    572 				'readonly' => true,
    573 				'option'   => 'stylesheet',
    574 			),
    575 			'post_thumbnail'          => array(
    576 				'desc'     => __( 'Post Thumbnail' ),
    577 				'readonly' => true,
    578 				'value'    => current_theme_supports( 'post-thumbnails' ),
    579 			),
    580 
    581 			// Updatable options.
    582 			'time_zone'               => array(
    583 				'desc'     => __( 'Time Zone' ),
    584 				'readonly' => false,
    585 				'option'   => 'gmt_offset',
    586 			),
    587 			'blog_title'              => array(
    588 				'desc'     => __( 'Site Title' ),
    589 				'readonly' => false,
    590 				'option'   => 'blogname',
    591 			),
    592 			'blog_tagline'            => array(
    593 				'desc'     => __( 'Site Tagline' ),
    594 				'readonly' => false,
    595 				'option'   => 'blogdescription',
    596 			),
    597 			'date_format'             => array(
    598 				'desc'     => __( 'Date Format' ),
    599 				'readonly' => false,
    600 				'option'   => 'date_format',
    601 			),
    602 			'time_format'             => array(
    603 				'desc'     => __( 'Time Format' ),
    604 				'readonly' => false,
    605 				'option'   => 'time_format',
    606 			),
    607 			'users_can_register'      => array(
    608 				'desc'     => __( 'Allow new users to sign up' ),
    609 				'readonly' => false,
    610 				'option'   => 'users_can_register',
    611 			),
    612 			'thumbnail_size_w'        => array(
    613 				'desc'     => __( 'Thumbnail Width' ),
    614 				'readonly' => false,
    615 				'option'   => 'thumbnail_size_w',
    616 			),
    617 			'thumbnail_size_h'        => array(
    618 				'desc'     => __( 'Thumbnail Height' ),
    619 				'readonly' => false,
    620 				'option'   => 'thumbnail_size_h',
    621 			),
    622 			'thumbnail_crop'          => array(
    623 				'desc'     => __( 'Crop thumbnail to exact dimensions' ),
    624 				'readonly' => false,
    625 				'option'   => 'thumbnail_crop',
    626 			),
    627 			'medium_size_w'           => array(
    628 				'desc'     => __( 'Medium size image width' ),
    629 				'readonly' => false,
    630 				'option'   => 'medium_size_w',
    631 			),
    632 			'medium_size_h'           => array(
    633 				'desc'     => __( 'Medium size image height' ),
    634 				'readonly' => false,
    635 				'option'   => 'medium_size_h',
    636 			),
    637 			'medium_large_size_w'     => array(
    638 				'desc'     => __( 'Medium-Large size image width' ),
    639 				'readonly' => false,
    640 				'option'   => 'medium_large_size_w',
    641 			),
    642 			'medium_large_size_h'     => array(
    643 				'desc'     => __( 'Medium-Large size image height' ),
    644 				'readonly' => false,
    645 				'option'   => 'medium_large_size_h',
    646 			),
    647 			'large_size_w'            => array(
    648 				'desc'     => __( 'Large size image width' ),
    649 				'readonly' => false,
    650 				'option'   => 'large_size_w',
    651 			),
    652 			'large_size_h'            => array(
    653 				'desc'     => __( 'Large size image height' ),
    654 				'readonly' => false,
    655 				'option'   => 'large_size_h',
    656 			),
    657 			'default_comment_status'  => array(
    658 				'desc'     => __( 'Allow people to submit comments on new posts.' ),
    659 				'readonly' => false,
    660 				'option'   => 'default_comment_status',
    661 			),
    662 			'default_ping_status'     => array(
    663 				'desc'     => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.' ),
    664 				'readonly' => false,
    665 				'option'   => 'default_ping_status',
    666 			),
    667 		);
    668 
    669 		/**
    670 		 * Filters the XML-RPC blog options property.
    671 		 *
    672 		 * @since 2.6.0
    673 		 *
    674 		 * @param array $blog_options An array of XML-RPC blog options.
    675 		 */
    676 		$this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
    677 	}
    678 
    679 	/**
    680 	 * Retrieve the blogs of the user.
    681 	 *
    682 	 * @since 2.6.0
    683 	 *
    684 	 * @param array $args {
    685 	 *     Method arguments. Note: arguments must be ordered as documented.
    686 	 *
    687 	 *     @type string $username Username.
    688 	 *     @type string $password Password.
    689 	 * }
    690 	 * @return array|IXR_Error Array contains:
    691 	 *  - 'isAdmin'
    692 	 *  - 'isPrimary' - whether the blog is the user's primary blog
    693 	 *  - 'url'
    694 	 *  - 'blogid'
    695 	 *  - 'blogName'
    696 	 *  - 'xmlrpc' - url of xmlrpc endpoint
    697 	 */
    698 	public function wp_getUsersBlogs( $args ) {
    699 		if ( ! $this->minimum_args( $args, 2 ) ) {
    700 			return $this->error;
    701 		}
    702 
    703 		// If this isn't on WPMU then just use blogger_getUsersBlogs().
    704 		if ( ! is_multisite() ) {
    705 			array_unshift( $args, 1 );
    706 			return $this->blogger_getUsersBlogs( $args );
    707 		}
    708 
    709 		$this->escape( $args );
    710 
    711 		$username = $args[0];
    712 		$password = $args[1];
    713 
    714 		$user = $this->login( $username, $password );
    715 		if ( ! $user ) {
    716 			return $this->error;
    717 		}
    718 
    719 		/**
    720 		 * Fires after the XML-RPC user has been authenticated but before the rest of
    721 		 * the method logic begins.
    722 		 *
    723 		 * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
    724 		 * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
    725 		 *
    726 		 * @since 2.5.0
    727 		 * @since 5.7.0 Added the `$args` and `$server` parameters.
    728 		 *
    729 		 * @param string           $name   The method name.
    730 		 * @param array|string     $args   The escaped arguments passed to the method.
    731 		 * @param wp_xmlrpc_server $server The XML-RPC server instance.
    732 		 */
    733 		do_action( 'xmlrpc_call', 'wp.getUsersBlogs', $args, $this );
    734 
    735 		$blogs           = (array) get_blogs_of_user( $user->ID );
    736 		$struct          = array();
    737 		$primary_blog_id = 0;
    738 		$active_blog     = get_active_blog_for_user( $user->ID );
    739 		if ( $active_blog ) {
    740 			$primary_blog_id = (int) $active_blog->blog_id;
    741 		}
    742 
    743 		foreach ( $blogs as $blog ) {
    744 			// Don't include blogs that aren't hosted at this site.
    745 			if ( get_current_network_id() != $blog->site_id ) {
    746 				continue;
    747 			}
    748 
    749 			$blog_id = $blog->userblog_id;
    750 
    751 			switch_to_blog( $blog_id );
    752 
    753 			$is_admin   = current_user_can( 'manage_options' );
    754 			$is_primary = ( (int) $blog_id === $primary_blog_id );
    755 
    756 			$struct[] = array(
    757 				'isAdmin'   => $is_admin,
    758 				'isPrimary' => $is_primary,
    759 				'url'       => home_url( '/' ),
    760 				'blogid'    => (string) $blog_id,
    761 				'blogName'  => get_option( 'blogname' ),
    762 				'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
    763 			);
    764 
    765 			restore_current_blog();
    766 		}
    767 
    768 		return $struct;
    769 	}
    770 
    771 	/**
    772 	 * Checks if the method received at least the minimum number of arguments.
    773 	 *
    774 	 * @since 3.4.0
    775 	 *
    776 	 * @param array $args  An array of arguments to check.
    777 	 * @param int   $count Minimum number of arguments.
    778 	 * @return bool True if `$args` contains at least `$count` arguments, false otherwise.
    779 	 */
    780 	protected function minimum_args( $args, $count ) {
    781 		if ( ! is_array( $args ) || count( $args ) < $count ) {
    782 			$this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
    783 			return false;
    784 		}
    785 
    786 		return true;
    787 	}
    788 
    789 	/**
    790 	 * Prepares taxonomy data for return in an XML-RPC object.
    791 	 *
    792 	 * @param WP_Taxonomy $taxonomy The unprepared taxonomy data.
    793 	 * @param array       $fields   The subset of taxonomy fields to return.
    794 	 * @return array The prepared taxonomy data.
    795 	 */
    796 	protected function _prepare_taxonomy( $taxonomy, $fields ) {
    797 		$_taxonomy = array(
    798 			'name'         => $taxonomy->name,
    799 			'label'        => $taxonomy->label,
    800 			'hierarchical' => (bool) $taxonomy->hierarchical,
    801 			'public'       => (bool) $taxonomy->public,
    802 			'show_ui'      => (bool) $taxonomy->show_ui,
    803 			'_builtin'     => (bool) $taxonomy->_builtin,
    804 		);
    805 
    806 		if ( in_array( 'labels', $fields, true ) ) {
    807 			$_taxonomy['labels'] = (array) $taxonomy->labels;
    808 		}
    809 
    810 		if ( in_array( 'cap', $fields, true ) ) {
    811 			$_taxonomy['cap'] = (array) $taxonomy->cap;
    812 		}
    813 
    814 		if ( in_array( 'menu', $fields, true ) ) {
    815 			$_taxonomy['show_in_menu'] = (bool) $taxonomy->show_in_menu;
    816 		}
    817 
    818 		if ( in_array( 'object_type', $fields, true ) ) {
    819 			$_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
    820 		}
    821 
    822 		/**
    823 		 * Filters XML-RPC-prepared data for the given taxonomy.
    824 		 *
    825 		 * @since 3.4.0
    826 		 *
    827 		 * @param array       $_taxonomy An array of taxonomy data.
    828 		 * @param WP_Taxonomy $taxonomy  Taxonomy object.
    829 		 * @param array       $fields    The subset of taxonomy fields to return.
    830 		 */
    831 		return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
    832 	}
    833 
    834 	/**
    835 	 * Prepares term data for return in an XML-RPC object.
    836 	 *
    837 	 * @param array|object $term The unprepared term data.
    838 	 * @return array The prepared term data.
    839 	 */
    840 	protected function _prepare_term( $term ) {
    841 		$_term = $term;
    842 		if ( ! is_array( $_term ) ) {
    843 			$_term = get_object_vars( $_term );
    844 		}
    845 
    846 		// For integers which may be larger than XML-RPC supports ensure we return strings.
    847 		$_term['term_id']          = (string) $_term['term_id'];
    848 		$_term['term_group']       = (string) $_term['term_group'];
    849 		$_term['term_taxonomy_id'] = (string) $_term['term_taxonomy_id'];
    850 		$_term['parent']           = (string) $_term['parent'];
    851 
    852 		// Count we are happy to return as an integer because people really shouldn't use terms that much.
    853 		$_term['count'] = (int) $_term['count'];
    854 
    855 		// Get term meta.
    856 		$_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] );
    857 
    858 		/**
    859 		 * Filters XML-RPC-prepared data for the given term.
    860 		 *
    861 		 * @since 3.4.0
    862 		 *
    863 		 * @param array        $_term An array of term data.
    864 		 * @param array|object $term  Term object or array.
    865 		 */
    866 		return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
    867 	}
    868 
    869 	/**
    870 	 * Convert a WordPress date string to an IXR_Date object.
    871 	 *
    872 	 * @param string $date Date string to convert.
    873 	 * @return IXR_Date IXR_Date object.
    874 	 */
    875 	protected function _convert_date( $date ) {
    876 		if ( '0000-00-00 00:00:00' === $date ) {
    877 			return new IXR_Date( '00000000T00:00:00Z' );
    878 		}
    879 		return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
    880 	}
    881 
    882 	/**
    883 	 * Convert a WordPress GMT date string to an IXR_Date object.
    884 	 *
    885 	 * @param string $date_gmt WordPress GMT date string.
    886 	 * @param string $date     Date string.
    887 	 * @return IXR_Date IXR_Date object.
    888 	 */
    889 	protected function _convert_date_gmt( $date_gmt, $date ) {
    890 		if ( '0000-00-00 00:00:00' !== $date && '0000-00-00 00:00:00' === $date_gmt ) {
    891 			return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
    892 		}
    893 		return $this->_convert_date( $date_gmt );
    894 	}
    895 
    896 	/**
    897 	 * Prepares post data for return in an XML-RPC object.
    898 	 *
    899 	 * @param array $post   The unprepared post data.
    900 	 * @param array $fields The subset of post type fields to return.
    901 	 * @return array The prepared post data.
    902 	 */
    903 	protected function _prepare_post( $post, $fields ) {
    904 		// Holds the data for this post. built up based on $fields.
    905 		$_post = array( 'post_id' => (string) $post['ID'] );
    906 
    907 		// Prepare common post fields.
    908 		$post_fields = array(
    909 			'post_title'        => $post['post_title'],
    910 			'post_date'         => $this->_convert_date( $post['post_date'] ),
    911 			'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
    912 			'post_modified'     => $this->_convert_date( $post['post_modified'] ),
    913 			'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
    914 			'post_status'       => $post['post_status'],
    915 			'post_type'         => $post['post_type'],
    916 			'post_name'         => $post['post_name'],
    917 			'post_author'       => $post['post_author'],
    918 			'post_password'     => $post['post_password'],
    919 			'post_excerpt'      => $post['post_excerpt'],
    920 			'post_content'      => $post['post_content'],
    921 			'post_parent'       => (string) $post['post_parent'],
    922 			'post_mime_type'    => $post['post_mime_type'],
    923 			'link'              => get_permalink( $post['ID'] ),
    924 			'guid'              => $post['guid'],
    925 			'menu_order'        => (int) $post['menu_order'],
    926 			'comment_status'    => $post['comment_status'],
    927 			'ping_status'       => $post['ping_status'],
    928 			'sticky'            => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ),
    929 		);
    930 
    931 		// Thumbnail.
    932 		$post_fields['post_thumbnail'] = array();
    933 		$thumbnail_id                  = get_post_thumbnail_id( $post['ID'] );
    934 		if ( $thumbnail_id ) {
    935 			$thumbnail_size                = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail';
    936 			$post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
    937 		}
    938 
    939 		// Consider future posts as published.
    940 		if ( 'future' === $post_fields['post_status'] ) {
    941 			$post_fields['post_status'] = 'publish';
    942 		}
    943 
    944 		// Fill in blank post format.
    945 		$post_fields['post_format'] = get_post_format( $post['ID'] );
    946 		if ( empty( $post_fields['post_format'] ) ) {
    947 			$post_fields['post_format'] = 'standard';
    948 		}
    949 
    950 		// Merge requested $post_fields fields into $_post.
    951 		if ( in_array( 'post', $fields, true ) ) {
    952 			$_post = array_merge( $_post, $post_fields );
    953 		} else {
    954 			$requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
    955 			$_post            = array_merge( $_post, $requested_fields );
    956 		}
    957 
    958 		$all_taxonomy_fields = in_array( 'taxonomies', $fields, true );
    959 
    960 		if ( $all_taxonomy_fields || in_array( 'terms', $fields, true ) ) {
    961 			$post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
    962 			$terms                = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
    963 			$_post['terms']       = array();
    964 			foreach ( $terms as $term ) {
    965 				$_post['terms'][] = $this->_prepare_term( $term );
    966 			}
    967 		}
    968 
    969 		if ( in_array( 'custom_fields', $fields, true ) ) {
    970 			$_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
    971 		}
    972 
    973 		if ( in_array( 'enclosure', $fields, true ) ) {
    974 			$_post['enclosure'] = array();
    975 			$enclosures         = (array) get_post_meta( $post['ID'], 'enclosure' );
    976 			if ( ! empty( $enclosures ) ) {
    977 				$encdata                      = explode( "\n", $enclosures[0] );
    978 				$_post['enclosure']['url']    = trim( htmlspecialchars( $encdata[0] ) );
    979 				$_post['enclosure']['length'] = (int) trim( $encdata[1] );
    980 				$_post['enclosure']['type']   = trim( $encdata[2] );
    981 			}
    982 		}
    983 
    984 		/**
    985 		 * Filters XML-RPC-prepared date for the given post.
    986 		 *
    987 		 * @since 3.4.0
    988 		 *
    989 		 * @param array $_post  An array of modified post data.
    990 		 * @param array $post   An array of post data.
    991 		 * @param array $fields An array of post fields.
    992 		 */
    993 		return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
    994 	}
    995 
    996 	/**
    997 	 * Prepares post data for return in an XML-RPC object.
    998 	 *
    999 	 * @since 3.4.0
   1000 	 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
   1001 	 *
   1002 	 * @param WP_Post_Type $post_type Post type object.
   1003 	 * @param array        $fields    The subset of post fields to return.
   1004 	 * @return array The prepared post type data.
   1005 	 */
   1006 	protected function _prepare_post_type( $post_type, $fields ) {
   1007 		$_post_type = array(
   1008 			'name'         => $post_type->name,
   1009 			'label'        => $post_type->label,
   1010 			'hierarchical' => (bool) $post_type->hierarchical,
   1011 			'public'       => (bool) $post_type->public,
   1012 			'show_ui'      => (bool) $post_type->show_ui,
   1013 			'_builtin'     => (bool) $post_type->_builtin,
   1014 			'has_archive'  => (bool) $post_type->has_archive,
   1015 			'supports'     => get_all_post_type_supports( $post_type->name ),
   1016 		);
   1017 
   1018 		if ( in_array( 'labels', $fields, true ) ) {
   1019 			$_post_type['labels'] = (array) $post_type->labels;
   1020 		}
   1021 
   1022 		if ( in_array( 'cap', $fields, true ) ) {
   1023 			$_post_type['cap']          = (array) $post_type->cap;
   1024 			$_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
   1025 		}
   1026 
   1027 		if ( in_array( 'menu', $fields, true ) ) {
   1028 			$_post_type['menu_position'] = (int) $post_type->menu_position;
   1029 			$_post_type['menu_icon']     = $post_type->menu_icon;
   1030 			$_post_type['show_in_menu']  = (bool) $post_type->show_in_menu;
   1031 		}
   1032 
   1033 		if ( in_array( 'taxonomies', $fields, true ) ) {
   1034 			$_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
   1035 		}
   1036 
   1037 		/**
   1038 		 * Filters XML-RPC-prepared date for the given post type.
   1039 		 *
   1040 		 * @since 3.4.0
   1041 		 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
   1042 		 *
   1043 		 * @param array        $_post_type An array of post type data.
   1044 		 * @param WP_Post_Type $post_type  Post type object.
   1045 		 */
   1046 		return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
   1047 	}
   1048 
   1049 	/**
   1050 	 * Prepares media item data for return in an XML-RPC object.
   1051 	 *
   1052 	 * @param WP_Post $media_item     The unprepared media item data.
   1053 	 * @param string  $thumbnail_size The image size to use for the thumbnail URL.
   1054 	 * @return array The prepared media item data.
   1055 	 */
   1056 	protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
   1057 		$_media_item = array(
   1058 			'attachment_id'    => (string) $media_item->ID,
   1059 			'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
   1060 			'parent'           => $media_item->post_parent,
   1061 			'link'             => wp_get_attachment_url( $media_item->ID ),
   1062 			'title'            => $media_item->post_title,
   1063 			'caption'          => $media_item->post_excerpt,
   1064 			'description'      => $media_item->post_content,
   1065 			'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
   1066 			'type'             => $media_item->post_mime_type,
   1067 		);
   1068 
   1069 		$thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
   1070 		if ( $thumbnail_src ) {
   1071 			$_media_item['thumbnail'] = $thumbnail_src[0];
   1072 		} else {
   1073 			$_media_item['thumbnail'] = $_media_item['link'];
   1074 		}
   1075 
   1076 		/**
   1077 		 * Filters XML-RPC-prepared data for the given media item.
   1078 		 *
   1079 		 * @since 3.4.0
   1080 		 *
   1081 		 * @param array   $_media_item    An array of media item data.
   1082 		 * @param WP_Post $media_item     Media item object.
   1083 		 * @param string  $thumbnail_size Image size.
   1084 		 */
   1085 		return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
   1086 	}
   1087 
   1088 	/**
   1089 	 * Prepares page data for return in an XML-RPC object.
   1090 	 *
   1091 	 * @param WP_Post $page The unprepared page data.
   1092 	 * @return array The prepared page data.
   1093 	 */
   1094 	protected function _prepare_page( $page ) {
   1095 		// Get all of the page content and link.
   1096 		$full_page = get_extended( $page->post_content );
   1097 		$link      = get_permalink( $page->ID );
   1098 
   1099 		// Get info the page parent if there is one.
   1100 		$parent_title = '';
   1101 		if ( ! empty( $page->post_parent ) ) {
   1102 			$parent       = get_post( $page->post_parent );
   1103 			$parent_title = $parent->post_title;
   1104 		}
   1105 
   1106 		// Determine comment and ping settings.
   1107 		$allow_comments = comments_open( $page->ID ) ? 1 : 0;
   1108 		$allow_pings    = pings_open( $page->ID ) ? 1 : 0;
   1109 
   1110 		// Format page date.
   1111 		$page_date     = $this->_convert_date( $page->post_date );
   1112 		$page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
   1113 
   1114 		// Pull the categories info together.
   1115 		$categories = array();
   1116 		if ( is_object_in_taxonomy( 'page', 'category' ) ) {
   1117 			foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
   1118 				$categories[] = get_cat_name( $cat_id );
   1119 			}
   1120 		}
   1121 
   1122 		// Get the author info.
   1123 		$author = get_userdata( $page->post_author );
   1124 
   1125 		$page_template = get_page_template_slug( $page->ID );
   1126 		if ( empty( $page_template ) ) {
   1127 			$page_template = 'default';
   1128 		}
   1129 
   1130 		$_page = array(
   1131 			'dateCreated'            => $page_date,
   1132 			'userid'                 => $page->post_author,
   1133 			'page_id'                => $page->ID,
   1134 			'page_status'            => $page->post_status,
   1135 			'description'            => $full_page['main'],
   1136 			'title'                  => $page->post_title,
   1137 			'link'                   => $link,
   1138 			'permaLink'              => $link,
   1139 			'categories'             => $categories,
   1140 			'excerpt'                => $page->post_excerpt,
   1141 			'text_more'              => $full_page['extended'],
   1142 			'mt_allow_comments'      => $allow_comments,
   1143 			'mt_allow_pings'         => $allow_pings,
   1144 			'wp_slug'                => $page->post_name,
   1145 			'wp_password'            => $page->post_password,
   1146 			'wp_author'              => $author->display_name,
   1147 			'wp_page_parent_id'      => $page->post_parent,
   1148 			'wp_page_parent_title'   => $parent_title,
   1149 			'wp_page_order'          => $page->menu_order,
   1150 			'wp_author_id'           => (string) $author->ID,
   1151 			'wp_author_display_name' => $author->display_name,
   1152 			'date_created_gmt'       => $page_date_gmt,
   1153 			'custom_fields'          => $this->get_custom_fields( $page->ID ),
   1154 			'wp_page_template'       => $page_template,
   1155 		);
   1156 
   1157 		/**
   1158 		 * Filters XML-RPC-prepared data for the given page.
   1159 		 *
   1160 		 * @since 3.4.0
   1161 		 *
   1162 		 * @param array   $_page An array of page data.
   1163 		 * @param WP_Post $page  Page object.
   1164 		 */
   1165 		return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
   1166 	}
   1167 
   1168 	/**
   1169 	 * Prepares comment data for return in an XML-RPC object.
   1170 	 *
   1171 	 * @param WP_Comment $comment The unprepared comment data.
   1172 	 * @return array The prepared comment data.
   1173 	 */
   1174 	protected function _prepare_comment( $comment ) {
   1175 		// Format page date.
   1176 		$comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
   1177 
   1178 		if ( '0' == $comment->comment_approved ) {
   1179 			$comment_status = 'hold';
   1180 		} elseif ( 'spam' === $comment->comment_approved ) {
   1181 			$comment_status = 'spam';
   1182 		} elseif ( '1' == $comment->comment_approved ) {
   1183 			$comment_status = 'approve';
   1184 		} else {
   1185 			$comment_status = $comment->comment_approved;
   1186 		}
   1187 		$_comment = array(
   1188 			'date_created_gmt' => $comment_date_gmt,
   1189 			'user_id'          => $comment->user_id,
   1190 			'comment_id'       => $comment->comment_ID,
   1191 			'parent'           => $comment->comment_parent,
   1192 			'status'           => $comment_status,
   1193 			'content'          => $comment->comment_content,
   1194 			'link'             => get_comment_link( $comment ),
   1195 			'post_id'          => $comment->comment_post_ID,
   1196 			'post_title'       => get_the_title( $comment->comment_post_ID ),
   1197 			'author'           => $comment->comment_author,
   1198 			'author_url'       => $comment->comment_author_url,
   1199 			'author_email'     => $comment->comment_author_email,
   1200 			'author_ip'        => $comment->comment_author_IP,
   1201 			'type'             => $comment->comment_type,
   1202 		);
   1203 
   1204 		/**
   1205 		 * Filters XML-RPC-prepared data for the given comment.
   1206 		 *
   1207 		 * @since 3.4.0
   1208 		 *
   1209 		 * @param array      $_comment An array of prepared comment data.
   1210 		 * @param WP_Comment $comment  Comment object.
   1211 		 */
   1212 		return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
   1213 	}
   1214 
   1215 	/**
   1216 	 * Prepares user data for return in an XML-RPC object.
   1217 	 *
   1218 	 * @param WP_User $user   The unprepared user object.
   1219 	 * @param array   $fields The subset of user fields to return.
   1220 	 * @return array The prepared user data.
   1221 	 */
   1222 	protected function _prepare_user( $user, $fields ) {
   1223 		$_user = array( 'user_id' => (string) $user->ID );
   1224 
   1225 		$user_fields = array(
   1226 			'username'     => $user->user_login,
   1227 			'first_name'   => $user->user_firstname,
   1228 			'last_name'    => $user->user_lastname,
   1229 			'registered'   => $this->_convert_date( $user->user_registered ),
   1230 			'bio'          => $user->user_description,
   1231 			'email'        => $user->user_email,
   1232 			'nickname'     => $user->nickname,
   1233 			'nicename'     => $user->user_nicename,
   1234 			'url'          => $user->user_url,
   1235 			'display_name' => $user->display_name,
   1236 			'roles'        => $user->roles,
   1237 		);
   1238 
   1239 		if ( in_array( 'all', $fields, true ) ) {
   1240 			$_user = array_merge( $_user, $user_fields );
   1241 		} else {
   1242 			if ( in_array( 'basic', $fields, true ) ) {
   1243 				$basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
   1244 				$fields       = array_merge( $fields, $basic_fields );
   1245 			}
   1246 			$requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
   1247 			$_user            = array_merge( $_user, $requested_fields );
   1248 		}
   1249 
   1250 		/**
   1251 		 * Filters XML-RPC-prepared data for the given user.
   1252 		 *
   1253 		 * @since 3.5.0
   1254 		 *
   1255 		 * @param array   $_user  An array of user data.
   1256 		 * @param WP_User $user   User object.
   1257 		 * @param array   $fields An array of user fields.
   1258 		 */
   1259 		return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
   1260 	}
   1261 
   1262 	/**
   1263 	 * Create a new post for any registered post type.
   1264 	 *
   1265 	 * @since 3.4.0
   1266 	 *
   1267 	 * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
   1268 	 *
   1269 	 * @param array $args {
   1270 	 *     Method arguments. Note: top-level arguments must be ordered as documented.
   1271 	 *
   1272 	 *     @type int    $blog_id        Blog ID (unused).
   1273 	 *     @type string $username       Username.
   1274 	 *     @type string $password       Password.
   1275 	 *     @type array  $content_struct {
   1276 	 *         Content struct for adding a new post. See wp_insert_post() for information on
   1277 	 *         additional post fields
   1278 	 *
   1279 	 *         @type string $post_type      Post type. Default 'post'.
   1280 	 *         @type string $post_status    Post status. Default 'draft'
   1281 	 *         @type string $post_title     Post title.
   1282 	 *         @type int    $post_author    Post author ID.
   1283 	 *         @type string $post_excerpt   Post excerpt.
   1284 	 *         @type string $post_content   Post content.
   1285 	 *         @type string $post_date_gmt  Post date in GMT.
   1286 	 *         @type string $post_date      Post date.
   1287 	 *         @type string $post_password  Post password (20-character limit).
   1288 	 *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
   1289 	 *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
   1290 	 *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
   1291 	 *                                      `$post_status` is 'private'.
   1292 	 *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
   1293 	 *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
   1294 	 *         @type array  $terms          Associative array with taxonomy names as keys and arrays
   1295 	 *                                      of term IDs as values.
   1296 	 *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
   1297 	 *                                      of term names as values.
   1298 	 *         @type array  $enclosure      {
   1299 	 *             Array of feed enclosure data to add to post meta.
   1300 	 *
   1301 	 *             @type string $url    URL for the feed enclosure.
   1302 	 *             @type int    $length Size in bytes of the enclosure.
   1303 	 *             @type string $type   Mime-type for the enclosure.
   1304 	 *         }
   1305 	 *     }
   1306 	 * }
   1307 	 * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
   1308 	 */
   1309 	public function wp_newPost( $args ) {
   1310 		if ( ! $this->minimum_args( $args, 4 ) ) {
   1311 			return $this->error;
   1312 		}
   1313 
   1314 		$this->escape( $args );
   1315 
   1316 		$username       = $args[1];
   1317 		$password       = $args[2];
   1318 		$content_struct = $args[3];
   1319 
   1320 		$user = $this->login( $username, $password );
   1321 		if ( ! $user ) {
   1322 			return $this->error;
   1323 		}
   1324 
   1325 		// Convert the date field back to IXR form.
   1326 		if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
   1327 			$content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
   1328 		}
   1329 
   1330 		/*
   1331 		 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
   1332 		 * since _insert_post() will ignore the non-GMT date if the GMT date is set.
   1333 		 */
   1334 		if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
   1335 			if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
   1336 				unset( $content_struct['post_date_gmt'] );
   1337 			} else {
   1338 				$content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
   1339 			}
   1340 		}
   1341 
   1342 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1343 		do_action( 'xmlrpc_call', 'wp.newPost', $args, $this );
   1344 
   1345 		unset( $content_struct['ID'] );
   1346 
   1347 		return $this->_insert_post( $user, $content_struct );
   1348 	}
   1349 
   1350 	/**
   1351 	 * Helper method for filtering out elements from an array.
   1352 	 *
   1353 	 * @since 3.4.0
   1354 	 *
   1355 	 * @param int $count Number to compare to one.
   1356 	 * @return bool True if the number is greater than one, false otherwise.
   1357 	 */
   1358 	private function _is_greater_than_one( $count ) {
   1359 		return $count > 1;
   1360 	}
   1361 
   1362 	/**
   1363 	 * Encapsulate the logic for sticking a post
   1364 	 * and determining if the user has permission to do so
   1365 	 *
   1366 	 * @since 4.3.0
   1367 	 *
   1368 	 * @param array $post_data
   1369 	 * @param bool  $update
   1370 	 * @return void|IXR_Error
   1371 	 */
   1372 	private function _toggle_sticky( $post_data, $update = false ) {
   1373 		$post_type = get_post_type_object( $post_data['post_type'] );
   1374 
   1375 		// Private and password-protected posts cannot be stickied.
   1376 		if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
   1377 			// Error if the client tried to stick the post, otherwise, silently unstick.
   1378 			if ( ! empty( $post_data['sticky'] ) ) {
   1379 				return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
   1380 			}
   1381 
   1382 			if ( $update ) {
   1383 				unstick_post( $post_data['ID'] );
   1384 			}
   1385 		} elseif ( isset( $post_data['sticky'] ) ) {
   1386 			if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
   1387 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
   1388 			}
   1389 
   1390 			$sticky = wp_validate_boolean( $post_data['sticky'] );
   1391 			if ( $sticky ) {
   1392 				stick_post( $post_data['ID'] );
   1393 			} else {
   1394 				unstick_post( $post_data['ID'] );
   1395 			}
   1396 		}
   1397 	}
   1398 
   1399 	/**
   1400 	 * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
   1401 	 *
   1402 	 * @since 3.4.0
   1403 	 *
   1404 	 * @see wp_insert_post()
   1405 	 *
   1406 	 * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
   1407 	 * @param array|IXR_Error $content_struct Post data to insert.
   1408 	 * @return IXR_Error|string
   1409 	 */
   1410 	protected function _insert_post( $user, $content_struct ) {
   1411 		$defaults = array(
   1412 			'post_status'    => 'draft',
   1413 			'post_type'      => 'post',
   1414 			'post_author'    => null,
   1415 			'post_password'  => null,
   1416 			'post_excerpt'   => null,
   1417 			'post_content'   => null,
   1418 			'post_title'     => null,
   1419 			'post_date'      => null,
   1420 			'post_date_gmt'  => null,
   1421 			'post_format'    => null,
   1422 			'post_name'      => null,
   1423 			'post_thumbnail' => null,
   1424 			'post_parent'    => null,
   1425 			'ping_status'    => null,
   1426 			'comment_status' => null,
   1427 			'custom_fields'  => null,
   1428 			'terms_names'    => null,
   1429 			'terms'          => null,
   1430 			'sticky'         => null,
   1431 			'enclosure'      => null,
   1432 			'ID'             => null,
   1433 		);
   1434 
   1435 		$post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
   1436 
   1437 		$post_type = get_post_type_object( $post_data['post_type'] );
   1438 		if ( ! $post_type ) {
   1439 			return new IXR_Error( 403, __( 'Invalid post type.' ) );
   1440 		}
   1441 
   1442 		$update = ! empty( $post_data['ID'] );
   1443 
   1444 		if ( $update ) {
   1445 			if ( ! get_post( $post_data['ID'] ) ) {
   1446 				return new IXR_Error( 401, __( 'Invalid post ID.' ) );
   1447 			}
   1448 			if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
   1449 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   1450 			}
   1451 			if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) {
   1452 				return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
   1453 			}
   1454 		} else {
   1455 			if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
   1456 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
   1457 			}
   1458 		}
   1459 
   1460 		switch ( $post_data['post_status'] ) {
   1461 			case 'draft':
   1462 			case 'pending':
   1463 				break;
   1464 			case 'private':
   1465 				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
   1466 					return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
   1467 				}
   1468 				break;
   1469 			case 'publish':
   1470 			case 'future':
   1471 				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
   1472 					return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
   1473 				}
   1474 				break;
   1475 			default:
   1476 				if ( ! get_post_status_object( $post_data['post_status'] ) ) {
   1477 					$post_data['post_status'] = 'draft';
   1478 				}
   1479 				break;
   1480 		}
   1481 
   1482 		if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
   1483 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
   1484 		}
   1485 
   1486 		$post_data['post_author'] = absint( $post_data['post_author'] );
   1487 		if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
   1488 			if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
   1489 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
   1490 			}
   1491 
   1492 			$author = get_userdata( $post_data['post_author'] );
   1493 
   1494 			if ( ! $author ) {
   1495 				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
   1496 			}
   1497 		} else {
   1498 			$post_data['post_author'] = $user->ID;
   1499 		}
   1500 
   1501 		if ( isset( $post_data['comment_status'] ) && 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) {
   1502 			unset( $post_data['comment_status'] );
   1503 		}
   1504 
   1505 		if ( isset( $post_data['ping_status'] ) && 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) {
   1506 			unset( $post_data['ping_status'] );
   1507 		}
   1508 
   1509 		// Do some timestamp voodoo.
   1510 		if ( ! empty( $post_data['post_date_gmt'] ) ) {
   1511 			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
   1512 			$dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
   1513 		} elseif ( ! empty( $post_data['post_date'] ) ) {
   1514 			$dateCreated = $post_data['post_date']->getIso();
   1515 		}
   1516 
   1517 		// Default to not flagging the post date to be edited unless it's intentional.
   1518 		$post_data['edit_date'] = false;
   1519 
   1520 		if ( ! empty( $dateCreated ) ) {
   1521 			$post_data['post_date']     = iso8601_to_datetime( $dateCreated );
   1522 			$post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
   1523 
   1524 			// Flag the post date to be edited.
   1525 			$post_data['edit_date'] = true;
   1526 		}
   1527 
   1528 		if ( ! isset( $post_data['ID'] ) ) {
   1529 			$post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
   1530 		}
   1531 		$post_ID = $post_data['ID'];
   1532 
   1533 		if ( 'post' === $post_data['post_type'] ) {
   1534 			$error = $this->_toggle_sticky( $post_data, $update );
   1535 			if ( $error ) {
   1536 				return $error;
   1537 			}
   1538 		}
   1539 
   1540 		if ( isset( $post_data['post_thumbnail'] ) ) {
   1541 			// Empty value deletes, non-empty value adds/updates.
   1542 			if ( ! $post_data['post_thumbnail'] ) {
   1543 				delete_post_thumbnail( $post_ID );
   1544 			} elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) {
   1545 				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
   1546 			}
   1547 			set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
   1548 			unset( $content_struct['post_thumbnail'] );
   1549 		}
   1550 
   1551 		if ( isset( $post_data['custom_fields'] ) ) {
   1552 			$this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
   1553 		}
   1554 
   1555 		if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
   1556 			$post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
   1557 
   1558 			// Accumulate term IDs from terms and terms_names.
   1559 			$terms = array();
   1560 
   1561 			// First validate the terms specified by ID.
   1562 			if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
   1563 				$taxonomies = array_keys( $post_data['terms'] );
   1564 
   1565 				// Validating term IDs.
   1566 				foreach ( $taxonomies as $taxonomy ) {
   1567 					if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
   1568 						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
   1569 					}
   1570 
   1571 					if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
   1572 						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
   1573 					}
   1574 
   1575 					$term_ids           = $post_data['terms'][ $taxonomy ];
   1576 					$terms[ $taxonomy ] = array();
   1577 					foreach ( $term_ids as $term_id ) {
   1578 						$term = get_term_by( 'id', $term_id, $taxonomy );
   1579 
   1580 						if ( ! $term ) {
   1581 							return new IXR_Error( 403, __( 'Invalid term ID.' ) );
   1582 						}
   1583 
   1584 						$terms[ $taxonomy ][] = (int) $term_id;
   1585 					}
   1586 				}
   1587 			}
   1588 
   1589 			// Now validate terms specified by name.
   1590 			if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
   1591 				$taxonomies = array_keys( $post_data['terms_names'] );
   1592 
   1593 				foreach ( $taxonomies as $taxonomy ) {
   1594 					if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
   1595 						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
   1596 					}
   1597 
   1598 					if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
   1599 						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
   1600 					}
   1601 
   1602 					/*
   1603 					 * For hierarchical taxonomies, we can't assign a term when multiple terms
   1604 					 * in the hierarchy share the same name.
   1605 					 */
   1606 					$ambiguous_terms = array();
   1607 					if ( is_taxonomy_hierarchical( $taxonomy ) ) {
   1608 						$tax_term_names = get_terms(
   1609 							array(
   1610 								'taxonomy'   => $taxonomy,
   1611 								'fields'     => 'names',
   1612 								'hide_empty' => false,
   1613 							)
   1614 						);
   1615 
   1616 						// Count the number of terms with the same name.
   1617 						$tax_term_names_count = array_count_values( $tax_term_names );
   1618 
   1619 						// Filter out non-ambiguous term names.
   1620 						$ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) );
   1621 
   1622 						$ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
   1623 					}
   1624 
   1625 					$term_names = $post_data['terms_names'][ $taxonomy ];
   1626 					foreach ( $term_names as $term_name ) {
   1627 						if ( in_array( $term_name, $ambiguous_terms, true ) ) {
   1628 							return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
   1629 						}
   1630 
   1631 						$term = get_term_by( 'name', $term_name, $taxonomy );
   1632 
   1633 						if ( ! $term ) {
   1634 							// Term doesn't exist, so check that the user is allowed to create new terms.
   1635 							if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) {
   1636 								return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
   1637 							}
   1638 
   1639 							// Create the new term.
   1640 							$term_info = wp_insert_term( $term_name, $taxonomy );
   1641 							if ( is_wp_error( $term_info ) ) {
   1642 								return new IXR_Error( 500, $term_info->get_error_message() );
   1643 							}
   1644 
   1645 							$terms[ $taxonomy ][] = (int) $term_info['term_id'];
   1646 						} else {
   1647 							$terms[ $taxonomy ][] = (int) $term->term_id;
   1648 						}
   1649 					}
   1650 				}
   1651 			}
   1652 
   1653 			$post_data['tax_input'] = $terms;
   1654 			unset( $post_data['terms'], $post_data['terms_names'] );
   1655 		}
   1656 
   1657 		if ( isset( $post_data['post_format'] ) ) {
   1658 			$format = set_post_format( $post_ID, $post_data['post_format'] );
   1659 
   1660 			if ( is_wp_error( $format ) ) {
   1661 				return new IXR_Error( 500, $format->get_error_message() );
   1662 			}
   1663 
   1664 			unset( $post_data['post_format'] );
   1665 		}
   1666 
   1667 		// Handle enclosures.
   1668 		$enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
   1669 		$this->add_enclosure_if_new( $post_ID, $enclosure );
   1670 
   1671 		$this->attach_uploads( $post_ID, $post_data['post_content'] );
   1672 
   1673 		/**
   1674 		 * Filters post data array to be inserted via XML-RPC.
   1675 		 *
   1676 		 * @since 3.4.0
   1677 		 *
   1678 		 * @param array $post_data      Parsed array of post data.
   1679 		 * @param array $content_struct Post data array.
   1680 		 */
   1681 		$post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
   1682 
   1683 		$post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
   1684 		if ( is_wp_error( $post_ID ) ) {
   1685 			return new IXR_Error( 500, $post_ID->get_error_message() );
   1686 		}
   1687 
   1688 		if ( ! $post_ID ) {
   1689 			if ( $update ) {
   1690 				return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) );
   1691 			} else {
   1692 				return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) );
   1693 			}
   1694 		}
   1695 
   1696 		return (string) $post_ID;
   1697 	}
   1698 
   1699 	/**
   1700 	 * Edit a post for any registered post type.
   1701 	 *
   1702 	 * The $content_struct parameter only needs to contain fields that
   1703 	 * should be changed. All other fields will retain their existing values.
   1704 	 *
   1705 	 * @since 3.4.0
   1706 	 *
   1707 	 * @param array $args {
   1708 	 *     Method arguments. Note: arguments must be ordered as documented.
   1709 	 *
   1710 	 *     @type int    $blog_id        Blog ID (unused).
   1711 	 *     @type string $username       Username.
   1712 	 *     @type string $password       Password.
   1713 	 *     @type int    $post_id        Post ID.
   1714 	 *     @type array  $content_struct Extra content arguments.
   1715 	 * }
   1716 	 * @return true|IXR_Error True on success, IXR_Error on failure.
   1717 	 */
   1718 	public function wp_editPost( $args ) {
   1719 		if ( ! $this->minimum_args( $args, 5 ) ) {
   1720 			return $this->error;
   1721 		}
   1722 
   1723 		$this->escape( $args );
   1724 
   1725 		$username       = $args[1];
   1726 		$password       = $args[2];
   1727 		$post_id        = (int) $args[3];
   1728 		$content_struct = $args[4];
   1729 
   1730 		$user = $this->login( $username, $password );
   1731 		if ( ! $user ) {
   1732 			return $this->error;
   1733 		}
   1734 
   1735 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1736 		do_action( 'xmlrpc_call', 'wp.editPost', $args, $this );
   1737 
   1738 		$post = get_post( $post_id, ARRAY_A );
   1739 
   1740 		if ( empty( $post['ID'] ) ) {
   1741 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   1742 		}
   1743 
   1744 		if ( isset( $content_struct['if_not_modified_since'] ) ) {
   1745 			// If the post has been modified since the date provided, return an error.
   1746 			if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
   1747 				return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
   1748 			}
   1749 		}
   1750 
   1751 		// Convert the date field back to IXR form.
   1752 		$post['post_date'] = $this->_convert_date( $post['post_date'] );
   1753 
   1754 		/*
   1755 		 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
   1756 		 * since _insert_post() will ignore the non-GMT date if the GMT date is set.
   1757 		 */
   1758 		if ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
   1759 			unset( $post['post_date_gmt'] );
   1760 		} else {
   1761 			$post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
   1762 		}
   1763 
   1764 		/*
   1765 		 * If the API client did not provide 'post_date', then we must not perpetuate the value that
   1766 		 * was stored in the database, or it will appear to be an intentional edit. Conveying it here
   1767 		 * as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt'
   1768 		 * to get set with the value that was originally stored in the database when the draft was created.
   1769 		 */
   1770 		if ( ! isset( $content_struct['post_date'] ) ) {
   1771 			unset( $post['post_date'] );
   1772 		}
   1773 
   1774 		$this->escape( $post );
   1775 		$merged_content_struct = array_merge( $post, $content_struct );
   1776 
   1777 		$retval = $this->_insert_post( $user, $merged_content_struct );
   1778 		if ( $retval instanceof IXR_Error ) {
   1779 			return $retval;
   1780 		}
   1781 
   1782 		return true;
   1783 	}
   1784 
   1785 	/**
   1786 	 * Delete a post for any registered post type.
   1787 	 *
   1788 	 * @since 3.4.0
   1789 	 *
   1790 	 * @see wp_delete_post()
   1791 	 *
   1792 	 * @param array $args {
   1793 	 *     Method arguments. Note: arguments must be ordered as documented.
   1794 	 *
   1795 	 *     @type int    $blog_id  Blog ID (unused).
   1796 	 *     @type string $username Username.
   1797 	 *     @type string $password Password.
   1798 	 *     @type int    $post_id  Post ID.
   1799 	 * }
   1800 	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
   1801 	 */
   1802 	public function wp_deletePost( $args ) {
   1803 		if ( ! $this->minimum_args( $args, 4 ) ) {
   1804 			return $this->error;
   1805 		}
   1806 
   1807 		$this->escape( $args );
   1808 
   1809 		$username = $args[1];
   1810 		$password = $args[2];
   1811 		$post_id  = (int) $args[3];
   1812 
   1813 		$user = $this->login( $username, $password );
   1814 		if ( ! $user ) {
   1815 			return $this->error;
   1816 		}
   1817 
   1818 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1819 		do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this );
   1820 
   1821 		$post = get_post( $post_id, ARRAY_A );
   1822 		if ( empty( $post['ID'] ) ) {
   1823 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   1824 		}
   1825 
   1826 		if ( ! current_user_can( 'delete_post', $post_id ) ) {
   1827 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
   1828 		}
   1829 
   1830 		$result = wp_delete_post( $post_id );
   1831 
   1832 		if ( ! $result ) {
   1833 			return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
   1834 		}
   1835 
   1836 		return true;
   1837 	}
   1838 
   1839 	/**
   1840 	 * Retrieve a post.
   1841 	 *
   1842 	 * @since 3.4.0
   1843 	 *
   1844 	 * The optional $fields parameter specifies what fields will be included
   1845 	 * in the response array. This should be a list of field names. 'post_id' will
   1846 	 * always be included in the response regardless of the value of $fields.
   1847 	 *
   1848 	 * Instead of, or in addition to, individual field names, conceptual group
   1849 	 * names can be used to specify multiple fields. The available conceptual
   1850 	 * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
   1851 	 * and 'enclosure'.
   1852 	 *
   1853 	 * @see get_post()
   1854 	 *
   1855 	 * @param array $args {
   1856 	 *     Method arguments. Note: arguments must be ordered as documented.
   1857 	 *
   1858 	 *     @type int    $blog_id  Blog ID (unused).
   1859 	 *     @type string $username Username.
   1860 	 *     @type string $password Password.
   1861 	 *     @type int    $post_id  Post ID.
   1862 	 *     @type array  $fields   The subset of post type fields to return.
   1863 	 * }
   1864 	 * @return array|IXR_Error Array contains (based on $fields parameter):
   1865 	 *  - 'post_id'
   1866 	 *  - 'post_title'
   1867 	 *  - 'post_date'
   1868 	 *  - 'post_date_gmt'
   1869 	 *  - 'post_modified'
   1870 	 *  - 'post_modified_gmt'
   1871 	 *  - 'post_status'
   1872 	 *  - 'post_type'
   1873 	 *  - 'post_name'
   1874 	 *  - 'post_author'
   1875 	 *  - 'post_password'
   1876 	 *  - 'post_excerpt'
   1877 	 *  - 'post_content'
   1878 	 *  - 'link'
   1879 	 *  - 'comment_status'
   1880 	 *  - 'ping_status'
   1881 	 *  - 'sticky'
   1882 	 *  - 'custom_fields'
   1883 	 *  - 'terms'
   1884 	 *  - 'categories'
   1885 	 *  - 'tags'
   1886 	 *  - 'enclosure'
   1887 	 */
   1888 	public function wp_getPost( $args ) {
   1889 		if ( ! $this->minimum_args( $args, 4 ) ) {
   1890 			return $this->error;
   1891 		}
   1892 
   1893 		$this->escape( $args );
   1894 
   1895 		$username = $args[1];
   1896 		$password = $args[2];
   1897 		$post_id  = (int) $args[3];
   1898 
   1899 		if ( isset( $args[4] ) ) {
   1900 			$fields = $args[4];
   1901 		} else {
   1902 			/**
   1903 			 * Filters the list of post query fields used by the given XML-RPC method.
   1904 			 *
   1905 			 * @since 3.4.0
   1906 			 *
   1907 			 * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
   1908 			 * @param string $method Method name.
   1909 			 */
   1910 			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
   1911 		}
   1912 
   1913 		$user = $this->login( $username, $password );
   1914 		if ( ! $user ) {
   1915 			return $this->error;
   1916 		}
   1917 
   1918 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1919 		do_action( 'xmlrpc_call', 'wp.getPost', $args, $this );
   1920 
   1921 		$post = get_post( $post_id, ARRAY_A );
   1922 
   1923 		if ( empty( $post['ID'] ) ) {
   1924 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   1925 		}
   1926 
   1927 		if ( ! current_user_can( 'edit_post', $post_id ) ) {
   1928 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   1929 		}
   1930 
   1931 		return $this->_prepare_post( $post, $fields );
   1932 	}
   1933 
   1934 	/**
   1935 	 * Retrieve posts.
   1936 	 *
   1937 	 * @since 3.4.0
   1938 	 *
   1939 	 * @see wp_get_recent_posts()
   1940 	 * @see wp_getPost() for more on `$fields`
   1941 	 * @see get_posts() for more on `$filter` values
   1942 	 *
   1943 	 * @param array $args {
   1944 	 *     Method arguments. Note: arguments must be ordered as documented.
   1945 	 *
   1946 	 *     @type int    $blog_id  Blog ID (unused).
   1947 	 *     @type string $username Username.
   1948 	 *     @type string $password Password.
   1949 	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
   1950 	 *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
   1951 	 *                            Default empty array.
   1952 	 *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
   1953 	 * }
   1954 	 * @return array|IXR_Error Array contains a collection of posts.
   1955 	 */
   1956 	public function wp_getPosts( $args ) {
   1957 		if ( ! $this->minimum_args( $args, 3 ) ) {
   1958 			return $this->error;
   1959 		}
   1960 
   1961 		$this->escape( $args );
   1962 
   1963 		$username = $args[1];
   1964 		$password = $args[2];
   1965 		$filter   = isset( $args[3] ) ? $args[3] : array();
   1966 
   1967 		if ( isset( $args[4] ) ) {
   1968 			$fields = $args[4];
   1969 		} else {
   1970 			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1971 			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
   1972 		}
   1973 
   1974 		$user = $this->login( $username, $password );
   1975 		if ( ! $user ) {
   1976 			return $this->error;
   1977 		}
   1978 
   1979 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   1980 		do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this );
   1981 
   1982 		$query = array();
   1983 
   1984 		if ( isset( $filter['post_type'] ) ) {
   1985 			$post_type = get_post_type_object( $filter['post_type'] );
   1986 			if ( ! ( (bool) $post_type ) ) {
   1987 				return new IXR_Error( 403, __( 'Invalid post type.' ) );
   1988 			}
   1989 		} else {
   1990 			$post_type = get_post_type_object( 'post' );
   1991 		}
   1992 
   1993 		if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
   1994 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
   1995 		}
   1996 
   1997 		$query['post_type'] = $post_type->name;
   1998 
   1999 		if ( isset( $filter['post_status'] ) ) {
   2000 			$query['post_status'] = $filter['post_status'];
   2001 		}
   2002 
   2003 		if ( isset( $filter['number'] ) ) {
   2004 			$query['numberposts'] = absint( $filter['number'] );
   2005 		}
   2006 
   2007 		if ( isset( $filter['offset'] ) ) {
   2008 			$query['offset'] = absint( $filter['offset'] );
   2009 		}
   2010 
   2011 		if ( isset( $filter['orderby'] ) ) {
   2012 			$query['orderby'] = $filter['orderby'];
   2013 
   2014 			if ( isset( $filter['order'] ) ) {
   2015 				$query['order'] = $filter['order'];
   2016 			}
   2017 		}
   2018 
   2019 		if ( isset( $filter['s'] ) ) {
   2020 			$query['s'] = $filter['s'];
   2021 		}
   2022 
   2023 		$posts_list = wp_get_recent_posts( $query );
   2024 
   2025 		if ( ! $posts_list ) {
   2026 			return array();
   2027 		}
   2028 
   2029 		// Holds all the posts data.
   2030 		$struct = array();
   2031 
   2032 		foreach ( $posts_list as $post ) {
   2033 			if ( ! current_user_can( 'edit_post', $post['ID'] ) ) {
   2034 				continue;
   2035 			}
   2036 
   2037 			$struct[] = $this->_prepare_post( $post, $fields );
   2038 		}
   2039 
   2040 		return $struct;
   2041 	}
   2042 
   2043 	/**
   2044 	 * Create a new term.
   2045 	 *
   2046 	 * @since 3.4.0
   2047 	 *
   2048 	 * @see wp_insert_term()
   2049 	 *
   2050 	 * @param array $args {
   2051 	 *     Method arguments. Note: arguments must be ordered as documented.
   2052 	 *
   2053 	 *     @type int    $blog_id        Blog ID (unused).
   2054 	 *     @type string $username       Username.
   2055 	 *     @type string $password       Password.
   2056 	 *     @type array  $content_struct Content struct for adding a new term. The struct must contain
   2057 	 *                                  the term 'name' and 'taxonomy'. Optional accepted values include
   2058 	 *                                  'parent', 'description', and 'slug'.
   2059 	 * }
   2060 	 * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
   2061 	 */
   2062 	public function wp_newTerm( $args ) {
   2063 		if ( ! $this->minimum_args( $args, 4 ) ) {
   2064 			return $this->error;
   2065 		}
   2066 
   2067 		$this->escape( $args );
   2068 
   2069 		$username       = $args[1];
   2070 		$password       = $args[2];
   2071 		$content_struct = $args[3];
   2072 
   2073 		$user = $this->login( $username, $password );
   2074 		if ( ! $user ) {
   2075 			return $this->error;
   2076 		}
   2077 
   2078 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2079 		do_action( 'xmlrpc_call', 'wp.newTerm', $args, $this );
   2080 
   2081 		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
   2082 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2083 		}
   2084 
   2085 		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
   2086 
   2087 		if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
   2088 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
   2089 		}
   2090 
   2091 		$taxonomy = (array) $taxonomy;
   2092 
   2093 		// Hold the data of the term.
   2094 		$term_data = array();
   2095 
   2096 		$term_data['name'] = trim( $content_struct['name'] );
   2097 		if ( empty( $term_data['name'] ) ) {
   2098 			return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
   2099 		}
   2100 
   2101 		if ( isset( $content_struct['parent'] ) ) {
   2102 			if ( ! $taxonomy['hierarchical'] ) {
   2103 				return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
   2104 			}
   2105 
   2106 			$parent_term_id = (int) $content_struct['parent'];
   2107 			$parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
   2108 
   2109 			if ( is_wp_error( $parent_term ) ) {
   2110 				return new IXR_Error( 500, $parent_term->get_error_message() );
   2111 			}
   2112 
   2113 			if ( ! $parent_term ) {
   2114 				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
   2115 			}
   2116 
   2117 			$term_data['parent'] = $content_struct['parent'];
   2118 		}
   2119 
   2120 		if ( isset( $content_struct['description'] ) ) {
   2121 			$term_data['description'] = $content_struct['description'];
   2122 		}
   2123 
   2124 		if ( isset( $content_struct['slug'] ) ) {
   2125 			$term_data['slug'] = $content_struct['slug'];
   2126 		}
   2127 
   2128 		$term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data );
   2129 
   2130 		if ( is_wp_error( $term ) ) {
   2131 			return new IXR_Error( 500, $term->get_error_message() );
   2132 		}
   2133 
   2134 		if ( ! $term ) {
   2135 			return new IXR_Error( 500, __( 'Sorry, the term could not be created.' ) );
   2136 		}
   2137 
   2138 		// Add term meta.
   2139 		if ( isset( $content_struct['custom_fields'] ) ) {
   2140 			$this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
   2141 		}
   2142 
   2143 		return (string) $term['term_id'];
   2144 	}
   2145 
   2146 	/**
   2147 	 * Edit a term.
   2148 	 *
   2149 	 * @since 3.4.0
   2150 	 *
   2151 	 * @see wp_update_term()
   2152 	 *
   2153 	 * @param array $args {
   2154 	 *     Method arguments. Note: arguments must be ordered as documented.
   2155 	 *
   2156 	 *     @type int    $blog_id        Blog ID (unused).
   2157 	 *     @type string $username       Username.
   2158 	 *     @type string $password       Password.
   2159 	 *     @type int    $term_id        Term ID.
   2160 	 *     @type array  $content_struct Content struct for editing a term. The struct must contain the
   2161 	 *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
   2162 	 *                                  'description', and 'slug'.
   2163 	 * }
   2164 	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
   2165 	 */
   2166 	public function wp_editTerm( $args ) {
   2167 		if ( ! $this->minimum_args( $args, 5 ) ) {
   2168 			return $this->error;
   2169 		}
   2170 
   2171 		$this->escape( $args );
   2172 
   2173 		$username       = $args[1];
   2174 		$password       = $args[2];
   2175 		$term_id        = (int) $args[3];
   2176 		$content_struct = $args[4];
   2177 
   2178 		$user = $this->login( $username, $password );
   2179 		if ( ! $user ) {
   2180 			return $this->error;
   2181 		}
   2182 
   2183 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2184 		do_action( 'xmlrpc_call', 'wp.editTerm', $args, $this );
   2185 
   2186 		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
   2187 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2188 		}
   2189 
   2190 		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
   2191 
   2192 		$taxonomy = (array) $taxonomy;
   2193 
   2194 		// Hold the data of the term.
   2195 		$term_data = array();
   2196 
   2197 		$term = get_term( $term_id, $content_struct['taxonomy'] );
   2198 
   2199 		if ( is_wp_error( $term ) ) {
   2200 			return new IXR_Error( 500, $term->get_error_message() );
   2201 		}
   2202 
   2203 		if ( ! $term ) {
   2204 			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
   2205 		}
   2206 
   2207 		if ( ! current_user_can( 'edit_term', $term_id ) ) {
   2208 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
   2209 		}
   2210 
   2211 		if ( isset( $content_struct['name'] ) ) {
   2212 			$term_data['name'] = trim( $content_struct['name'] );
   2213 
   2214 			if ( empty( $term_data['name'] ) ) {
   2215 				return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
   2216 			}
   2217 		}
   2218 
   2219 		if ( ! empty( $content_struct['parent'] ) ) {
   2220 			if ( ! $taxonomy['hierarchical'] ) {
   2221 				return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) );
   2222 			}
   2223 
   2224 			$parent_term_id = (int) $content_struct['parent'];
   2225 			$parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
   2226 
   2227 			if ( is_wp_error( $parent_term ) ) {
   2228 				return new IXR_Error( 500, $parent_term->get_error_message() );
   2229 			}
   2230 
   2231 			if ( ! $parent_term ) {
   2232 				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
   2233 			}
   2234 
   2235 			$term_data['parent'] = $content_struct['parent'];
   2236 		}
   2237 
   2238 		if ( isset( $content_struct['description'] ) ) {
   2239 			$term_data['description'] = $content_struct['description'];
   2240 		}
   2241 
   2242 		if ( isset( $content_struct['slug'] ) ) {
   2243 			$term_data['slug'] = $content_struct['slug'];
   2244 		}
   2245 
   2246 		$term = wp_update_term( $term_id, $taxonomy['name'], $term_data );
   2247 
   2248 		if ( is_wp_error( $term ) ) {
   2249 			return new IXR_Error( 500, $term->get_error_message() );
   2250 		}
   2251 
   2252 		if ( ! $term ) {
   2253 			return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
   2254 		}
   2255 
   2256 		// Update term meta.
   2257 		if ( isset( $content_struct['custom_fields'] ) ) {
   2258 			$this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
   2259 		}
   2260 
   2261 		return true;
   2262 	}
   2263 
   2264 	/**
   2265 	 * Delete a term.
   2266 	 *
   2267 	 * @since 3.4.0
   2268 	 *
   2269 	 * @see wp_delete_term()
   2270 	 *
   2271 	 * @param array $args {
   2272 	 *     Method arguments. Note: arguments must be ordered as documented.
   2273 	 *
   2274 	 *     @type int    $blog_id      Blog ID (unused).
   2275 	 *     @type string $username     Username.
   2276 	 *     @type string $password     Password.
   2277 	 *     @type string $taxnomy_name Taxonomy name.
   2278 	 *     @type int    $term_id      Term ID.
   2279 	 * }
   2280 	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
   2281 	 */
   2282 	public function wp_deleteTerm( $args ) {
   2283 		if ( ! $this->minimum_args( $args, 5 ) ) {
   2284 			return $this->error;
   2285 		}
   2286 
   2287 		$this->escape( $args );
   2288 
   2289 		$username = $args[1];
   2290 		$password = $args[2];
   2291 		$taxonomy = $args[3];
   2292 		$term_id  = (int) $args[4];
   2293 
   2294 		$user = $this->login( $username, $password );
   2295 		if ( ! $user ) {
   2296 			return $this->error;
   2297 		}
   2298 
   2299 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2300 		do_action( 'xmlrpc_call', 'wp.deleteTerm', $args, $this );
   2301 
   2302 		if ( ! taxonomy_exists( $taxonomy ) ) {
   2303 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2304 		}
   2305 
   2306 		$taxonomy = get_taxonomy( $taxonomy );
   2307 		$term     = get_term( $term_id, $taxonomy->name );
   2308 
   2309 		if ( is_wp_error( $term ) ) {
   2310 			return new IXR_Error( 500, $term->get_error_message() );
   2311 		}
   2312 
   2313 		if ( ! $term ) {
   2314 			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
   2315 		}
   2316 
   2317 		if ( ! current_user_can( 'delete_term', $term_id ) ) {
   2318 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
   2319 		}
   2320 
   2321 		$result = wp_delete_term( $term_id, $taxonomy->name );
   2322 
   2323 		if ( is_wp_error( $result ) ) {
   2324 			return new IXR_Error( 500, $term->get_error_message() );
   2325 		}
   2326 
   2327 		if ( ! $result ) {
   2328 			return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
   2329 		}
   2330 
   2331 		return $result;
   2332 	}
   2333 
   2334 	/**
   2335 	 * Retrieve a term.
   2336 	 *
   2337 	 * @since 3.4.0
   2338 	 *
   2339 	 * @see get_term()
   2340 	 *
   2341 	 * @param array $args {
   2342 	 *     Method arguments. Note: arguments must be ordered as documented.
   2343 	 *
   2344 	 *     @type int    $blog_id  Blog ID (unused).
   2345 	 *     @type string $username Username.
   2346 	 *     @type string $password Password.
   2347 	 *     @type string $taxnomy  Taxonomy name.
   2348 	 *     @type string $term_id  Term ID.
   2349 	 * }
   2350 	 * @return array|IXR_Error IXR_Error on failure, array on success, containing:
   2351 	 *  - 'term_id'
   2352 	 *  - 'name'
   2353 	 *  - 'slug'
   2354 	 *  - 'term_group'
   2355 	 *  - 'term_taxonomy_id'
   2356 	 *  - 'taxonomy'
   2357 	 *  - 'description'
   2358 	 *  - 'parent'
   2359 	 *  - 'count'
   2360 	 */
   2361 	public function wp_getTerm( $args ) {
   2362 		if ( ! $this->minimum_args( $args, 5 ) ) {
   2363 			return $this->error;
   2364 		}
   2365 
   2366 		$this->escape( $args );
   2367 
   2368 		$username = $args[1];
   2369 		$password = $args[2];
   2370 		$taxonomy = $args[3];
   2371 		$term_id  = (int) $args[4];
   2372 
   2373 		$user = $this->login( $username, $password );
   2374 		if ( ! $user ) {
   2375 			return $this->error;
   2376 		}
   2377 
   2378 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2379 		do_action( 'xmlrpc_call', 'wp.getTerm', $args, $this );
   2380 
   2381 		if ( ! taxonomy_exists( $taxonomy ) ) {
   2382 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2383 		}
   2384 
   2385 		$taxonomy = get_taxonomy( $taxonomy );
   2386 
   2387 		$term = get_term( $term_id, $taxonomy->name, ARRAY_A );
   2388 
   2389 		if ( is_wp_error( $term ) ) {
   2390 			return new IXR_Error( 500, $term->get_error_message() );
   2391 		}
   2392 
   2393 		if ( ! $term ) {
   2394 			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
   2395 		}
   2396 
   2397 		if ( ! current_user_can( 'assign_term', $term_id ) ) {
   2398 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
   2399 		}
   2400 
   2401 		return $this->_prepare_term( $term );
   2402 	}
   2403 
   2404 	/**
   2405 	 * Retrieve all terms for a taxonomy.
   2406 	 *
   2407 	 * @since 3.4.0
   2408 	 *
   2409 	 * The optional $filter parameter modifies the query used to retrieve terms.
   2410 	 * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
   2411 	 *
   2412 	 * @see get_terms()
   2413 	 *
   2414 	 * @param array $args {
   2415 	 *     Method arguments. Note: arguments must be ordered as documented.
   2416 	 *
   2417 	 *     @type int    $blog_id  Blog ID (unused).
   2418 	 *     @type string $username Username.
   2419 	 *     @type string $password Password.
   2420 	 *     @type string $taxnomy  Taxonomy name.
   2421 	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
   2422 	 *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
   2423 	 * }
   2424 	 * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
   2425 	 */
   2426 	public function wp_getTerms( $args ) {
   2427 		if ( ! $this->minimum_args( $args, 4 ) ) {
   2428 			return $this->error;
   2429 		}
   2430 
   2431 		$this->escape( $args );
   2432 
   2433 		$username = $args[1];
   2434 		$password = $args[2];
   2435 		$taxonomy = $args[3];
   2436 		$filter   = isset( $args[4] ) ? $args[4] : array();
   2437 
   2438 		$user = $this->login( $username, $password );
   2439 		if ( ! $user ) {
   2440 			return $this->error;
   2441 		}
   2442 
   2443 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2444 		do_action( 'xmlrpc_call', 'wp.getTerms', $args, $this );
   2445 
   2446 		if ( ! taxonomy_exists( $taxonomy ) ) {
   2447 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2448 		}
   2449 
   2450 		$taxonomy = get_taxonomy( $taxonomy );
   2451 
   2452 		if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
   2453 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
   2454 		}
   2455 
   2456 		$query = array( 'taxonomy' => $taxonomy->name );
   2457 
   2458 		if ( isset( $filter['number'] ) ) {
   2459 			$query['number'] = absint( $filter['number'] );
   2460 		}
   2461 
   2462 		if ( isset( $filter['offset'] ) ) {
   2463 			$query['offset'] = absint( $filter['offset'] );
   2464 		}
   2465 
   2466 		if ( isset( $filter['orderby'] ) ) {
   2467 			$query['orderby'] = $filter['orderby'];
   2468 
   2469 			if ( isset( $filter['order'] ) ) {
   2470 				$query['order'] = $filter['order'];
   2471 			}
   2472 		}
   2473 
   2474 		if ( isset( $filter['hide_empty'] ) ) {
   2475 			$query['hide_empty'] = $filter['hide_empty'];
   2476 		} else {
   2477 			$query['get'] = 'all';
   2478 		}
   2479 
   2480 		if ( isset( $filter['search'] ) ) {
   2481 			$query['search'] = $filter['search'];
   2482 		}
   2483 
   2484 		$terms = get_terms( $query );
   2485 
   2486 		if ( is_wp_error( $terms ) ) {
   2487 			return new IXR_Error( 500, $terms->get_error_message() );
   2488 		}
   2489 
   2490 		$struct = array();
   2491 
   2492 		foreach ( $terms as $term ) {
   2493 			$struct[] = $this->_prepare_term( $term );
   2494 		}
   2495 
   2496 		return $struct;
   2497 	}
   2498 
   2499 	/**
   2500 	 * Retrieve a taxonomy.
   2501 	 *
   2502 	 * @since 3.4.0
   2503 	 *
   2504 	 * @see get_taxonomy()
   2505 	 *
   2506 	 * @param array $args {
   2507 	 *     Method arguments. Note: arguments must be ordered as documented.
   2508 	 *
   2509 	 *     @type int    $blog_id  Blog ID (unused).
   2510 	 *     @type string $username Username.
   2511 	 *     @type string $password Password.
   2512 	 *     @type string $taxnomy  Taxonomy name.
   2513 	 *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
   2514 	 *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
   2515 	 *                            Default empty array.
   2516 	 * }
   2517 	 * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
   2518 	 */
   2519 	public function wp_getTaxonomy( $args ) {
   2520 		if ( ! $this->minimum_args( $args, 4 ) ) {
   2521 			return $this->error;
   2522 		}
   2523 
   2524 		$this->escape( $args );
   2525 
   2526 		$username = $args[1];
   2527 		$password = $args[2];
   2528 		$taxonomy = $args[3];
   2529 
   2530 		if ( isset( $args[4] ) ) {
   2531 			$fields = $args[4];
   2532 		} else {
   2533 			/**
   2534 			 * Filters the taxonomy query fields used by the given XML-RPC method.
   2535 			 *
   2536 			 * @since 3.4.0
   2537 			 *
   2538 			 * @param array  $fields An array of taxonomy fields to retrieve.
   2539 			 * @param string $method The method name.
   2540 			 */
   2541 			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
   2542 		}
   2543 
   2544 		$user = $this->login( $username, $password );
   2545 		if ( ! $user ) {
   2546 			return $this->error;
   2547 		}
   2548 
   2549 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2550 		do_action( 'xmlrpc_call', 'wp.getTaxonomy', $args, $this );
   2551 
   2552 		if ( ! taxonomy_exists( $taxonomy ) ) {
   2553 			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
   2554 		}
   2555 
   2556 		$taxonomy = get_taxonomy( $taxonomy );
   2557 
   2558 		if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
   2559 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
   2560 		}
   2561 
   2562 		return $this->_prepare_taxonomy( $taxonomy, $fields );
   2563 	}
   2564 
   2565 	/**
   2566 	 * Retrieve all taxonomies.
   2567 	 *
   2568 	 * @since 3.4.0
   2569 	 *
   2570 	 * @see get_taxonomies()
   2571 	 *
   2572 	 * @param array $args {
   2573 	 *     Method arguments. Note: arguments must be ordered as documented.
   2574 	 *
   2575 	 *     @type int    $blog_id  Blog ID (unused).
   2576 	 *     @type string $username Username.
   2577 	 *     @type string $password Password.
   2578 	 *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
   2579 	 *     @type array  $fields   Optional. The subset of taxonomy fields to return.
   2580 	 * }
   2581 	 * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
   2582 	 *                         by `$fields`, or an IXR_Error instance on failure.
   2583 	 */
   2584 	public function wp_getTaxonomies( $args ) {
   2585 		if ( ! $this->minimum_args( $args, 3 ) ) {
   2586 			return $this->error;
   2587 		}
   2588 
   2589 		$this->escape( $args );
   2590 
   2591 		$username = $args[1];
   2592 		$password = $args[2];
   2593 		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
   2594 
   2595 		if ( isset( $args[4] ) ) {
   2596 			$fields = $args[4];
   2597 		} else {
   2598 			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2599 			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
   2600 		}
   2601 
   2602 		$user = $this->login( $username, $password );
   2603 		if ( ! $user ) {
   2604 			return $this->error;
   2605 		}
   2606 
   2607 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2608 		do_action( 'xmlrpc_call', 'wp.getTaxonomies', $args, $this );
   2609 
   2610 		$taxonomies = get_taxonomies( $filter, 'objects' );
   2611 
   2612 		// Holds all the taxonomy data.
   2613 		$struct = array();
   2614 
   2615 		foreach ( $taxonomies as $taxonomy ) {
   2616 			// Capability check for post types.
   2617 			if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
   2618 				continue;
   2619 			}
   2620 
   2621 			$struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
   2622 		}
   2623 
   2624 		return $struct;
   2625 	}
   2626 
   2627 	/**
   2628 	 * Retrieve a user.
   2629 	 *
   2630 	 * The optional $fields parameter specifies what fields will be included
   2631 	 * in the response array. This should be a list of field names. 'user_id' will
   2632 	 * always be included in the response regardless of the value of $fields.
   2633 	 *
   2634 	 * Instead of, or in addition to, individual field names, conceptual group
   2635 	 * names can be used to specify multiple fields. The available conceptual
   2636 	 * groups are 'basic' and 'all'.
   2637 	 *
   2638 	 * @uses get_userdata()
   2639 	 *
   2640 	 * @param array $args {
   2641 	 *     Method arguments. Note: arguments must be ordered as documented.
   2642 	 *
   2643 	 *     @type int    $blog_id (unused)
   2644 	 *     @type string $username
   2645 	 *     @type string $password
   2646 	 *     @type int    $user_id
   2647 	 *     @type array  $fields (optional)
   2648 	 * }
   2649 	 * @return array|IXR_Error Array contains (based on $fields parameter):
   2650 	 *  - 'user_id'
   2651 	 *  - 'username'
   2652 	 *  - 'first_name'
   2653 	 *  - 'last_name'
   2654 	 *  - 'registered'
   2655 	 *  - 'bio'
   2656 	 *  - 'email'
   2657 	 *  - 'nickname'
   2658 	 *  - 'nicename'
   2659 	 *  - 'url'
   2660 	 *  - 'display_name'
   2661 	 *  - 'roles'
   2662 	 */
   2663 	public function wp_getUser( $args ) {
   2664 		if ( ! $this->minimum_args( $args, 4 ) ) {
   2665 			return $this->error;
   2666 		}
   2667 
   2668 		$this->escape( $args );
   2669 
   2670 		$username = $args[1];
   2671 		$password = $args[2];
   2672 		$user_id  = (int) $args[3];
   2673 
   2674 		if ( isset( $args[4] ) ) {
   2675 			$fields = $args[4];
   2676 		} else {
   2677 			/**
   2678 			 * Filters the default user query fields used by the given XML-RPC method.
   2679 			 *
   2680 			 * @since 3.5.0
   2681 			 *
   2682 			 * @param array  $fields User query fields for given method. Default 'all'.
   2683 			 * @param string $method The method name.
   2684 			 */
   2685 			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
   2686 		}
   2687 
   2688 		$user = $this->login( $username, $password );
   2689 		if ( ! $user ) {
   2690 			return $this->error;
   2691 		}
   2692 
   2693 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2694 		do_action( 'xmlrpc_call', 'wp.getUser', $args, $this );
   2695 
   2696 		if ( ! current_user_can( 'edit_user', $user_id ) ) {
   2697 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
   2698 		}
   2699 
   2700 		$user_data = get_userdata( $user_id );
   2701 
   2702 		if ( ! $user_data ) {
   2703 			return new IXR_Error( 404, __( 'Invalid user ID.' ) );
   2704 		}
   2705 
   2706 		return $this->_prepare_user( $user_data, $fields );
   2707 	}
   2708 
   2709 	/**
   2710 	 * Retrieve users.
   2711 	 *
   2712 	 * The optional $filter parameter modifies the query used to retrieve users.
   2713 	 * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
   2714 	 * 'who', 'orderby', and 'order'.
   2715 	 *
   2716 	 * The optional $fields parameter specifies what fields will be included
   2717 	 * in the response array.
   2718 	 *
   2719 	 * @uses get_users()
   2720 	 * @see wp_getUser() for more on $fields and return values
   2721 	 *
   2722 	 * @param array $args {
   2723 	 *     Method arguments. Note: arguments must be ordered as documented.
   2724 	 *
   2725 	 *     @type int    $blog_id (unused)
   2726 	 *     @type string $username
   2727 	 *     @type string $password
   2728 	 *     @type array  $filter (optional)
   2729 	 *     @type array  $fields (optional)
   2730 	 * }
   2731 	 * @return array|IXR_Error users data
   2732 	 */
   2733 	public function wp_getUsers( $args ) {
   2734 		if ( ! $this->minimum_args( $args, 3 ) ) {
   2735 			return $this->error;
   2736 		}
   2737 
   2738 		$this->escape( $args );
   2739 
   2740 		$username = $args[1];
   2741 		$password = $args[2];
   2742 		$filter   = isset( $args[3] ) ? $args[3] : array();
   2743 
   2744 		if ( isset( $args[4] ) ) {
   2745 			$fields = $args[4];
   2746 		} else {
   2747 			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2748 			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
   2749 		}
   2750 
   2751 		$user = $this->login( $username, $password );
   2752 		if ( ! $user ) {
   2753 			return $this->error;
   2754 		}
   2755 
   2756 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2757 		do_action( 'xmlrpc_call', 'wp.getUsers', $args, $this );
   2758 
   2759 		if ( ! current_user_can( 'list_users' ) ) {
   2760 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
   2761 		}
   2762 
   2763 		$query = array( 'fields' => 'all_with_meta' );
   2764 
   2765 		$query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
   2766 		$query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
   2767 
   2768 		if ( isset( $filter['orderby'] ) ) {
   2769 			$query['orderby'] = $filter['orderby'];
   2770 
   2771 			if ( isset( $filter['order'] ) ) {
   2772 				$query['order'] = $filter['order'];
   2773 			}
   2774 		}
   2775 
   2776 		if ( isset( $filter['role'] ) ) {
   2777 			if ( get_role( $filter['role'] ) === null ) {
   2778 				return new IXR_Error( 403, __( 'Invalid role.' ) );
   2779 			}
   2780 
   2781 			$query['role'] = $filter['role'];
   2782 		}
   2783 
   2784 		if ( isset( $filter['who'] ) ) {
   2785 			$query['who'] = $filter['who'];
   2786 		}
   2787 
   2788 		$users = get_users( $query );
   2789 
   2790 		$_users = array();
   2791 		foreach ( $users as $user_data ) {
   2792 			if ( current_user_can( 'edit_user', $user_data->ID ) ) {
   2793 				$_users[] = $this->_prepare_user( $user_data, $fields );
   2794 			}
   2795 		}
   2796 		return $_users;
   2797 	}
   2798 
   2799 	/**
   2800 	 * Retrieve information about the requesting user.
   2801 	 *
   2802 	 * @uses get_userdata()
   2803 	 *
   2804 	 * @param array $args {
   2805 	 *     Method arguments. Note: arguments must be ordered as documented.
   2806 	 *
   2807 	 *     @type int    $blog_id (unused)
   2808 	 *     @type string $username
   2809 	 *     @type string $password
   2810 	 *     @type array  $fields (optional)
   2811 	 * }
   2812 	 * @return array|IXR_Error (@see wp_getUser)
   2813 	 */
   2814 	public function wp_getProfile( $args ) {
   2815 		if ( ! $this->minimum_args( $args, 3 ) ) {
   2816 			return $this->error;
   2817 		}
   2818 
   2819 		$this->escape( $args );
   2820 
   2821 		$username = $args[1];
   2822 		$password = $args[2];
   2823 
   2824 		if ( isset( $args[3] ) ) {
   2825 			$fields = $args[3];
   2826 		} else {
   2827 			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2828 			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
   2829 		}
   2830 
   2831 		$user = $this->login( $username, $password );
   2832 		if ( ! $user ) {
   2833 			return $this->error;
   2834 		}
   2835 
   2836 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2837 		do_action( 'xmlrpc_call', 'wp.getProfile', $args, $this );
   2838 
   2839 		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
   2840 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
   2841 		}
   2842 
   2843 		$user_data = get_userdata( $user->ID );
   2844 
   2845 		return $this->_prepare_user( $user_data, $fields );
   2846 	}
   2847 
   2848 	/**
   2849 	 * Edit user's profile.
   2850 	 *
   2851 	 * @uses wp_update_user()
   2852 	 *
   2853 	 * @param array $args {
   2854 	 *     Method arguments. Note: arguments must be ordered as documented.
   2855 	 *
   2856 	 *     @type int    $blog_id (unused)
   2857 	 *     @type string $username
   2858 	 *     @type string $password
   2859 	 *     @type array  $content_struct It can optionally contain:
   2860 	 *      - 'first_name'
   2861 	 *      - 'last_name'
   2862 	 *      - 'website'
   2863 	 *      - 'display_name'
   2864 	 *      - 'nickname'
   2865 	 *      - 'nicename'
   2866 	 *      - 'bio'
   2867 	 * }
   2868 	 * @return true|IXR_Error True, on success.
   2869 	 */
   2870 	public function wp_editProfile( $args ) {
   2871 		if ( ! $this->minimum_args( $args, 4 ) ) {
   2872 			return $this->error;
   2873 		}
   2874 
   2875 		$this->escape( $args );
   2876 
   2877 		$username       = $args[1];
   2878 		$password       = $args[2];
   2879 		$content_struct = $args[3];
   2880 
   2881 		$user = $this->login( $username, $password );
   2882 		if ( ! $user ) {
   2883 			return $this->error;
   2884 		}
   2885 
   2886 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2887 		do_action( 'xmlrpc_call', 'wp.editProfile', $args, $this );
   2888 
   2889 		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
   2890 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
   2891 		}
   2892 
   2893 		// Holds data of the user.
   2894 		$user_data       = array();
   2895 		$user_data['ID'] = $user->ID;
   2896 
   2897 		// Only set the user details if they were given.
   2898 		if ( isset( $content_struct['first_name'] ) ) {
   2899 			$user_data['first_name'] = $content_struct['first_name'];
   2900 		}
   2901 
   2902 		if ( isset( $content_struct['last_name'] ) ) {
   2903 			$user_data['last_name'] = $content_struct['last_name'];
   2904 		}
   2905 
   2906 		if ( isset( $content_struct['url'] ) ) {
   2907 			$user_data['user_url'] = $content_struct['url'];
   2908 		}
   2909 
   2910 		if ( isset( $content_struct['display_name'] ) ) {
   2911 			$user_data['display_name'] = $content_struct['display_name'];
   2912 		}
   2913 
   2914 		if ( isset( $content_struct['nickname'] ) ) {
   2915 			$user_data['nickname'] = $content_struct['nickname'];
   2916 		}
   2917 
   2918 		if ( isset( $content_struct['nicename'] ) ) {
   2919 			$user_data['user_nicename'] = $content_struct['nicename'];
   2920 		}
   2921 
   2922 		if ( isset( $content_struct['bio'] ) ) {
   2923 			$user_data['description'] = $content_struct['bio'];
   2924 		}
   2925 
   2926 		$result = wp_update_user( $user_data );
   2927 
   2928 		if ( is_wp_error( $result ) ) {
   2929 			return new IXR_Error( 500, $result->get_error_message() );
   2930 		}
   2931 
   2932 		if ( ! $result ) {
   2933 			return new IXR_Error( 500, __( 'Sorry, the user could not be updated.' ) );
   2934 		}
   2935 
   2936 		return true;
   2937 	}
   2938 
   2939 	/**
   2940 	 * Retrieve page.
   2941 	 *
   2942 	 * @since 2.2.0
   2943 	 *
   2944 	 * @param array $args {
   2945 	 *     Method arguments. Note: arguments must be ordered as documented.
   2946 	 *
   2947 	 *     @type int    $blog_id (unused)
   2948 	 *     @type int    $page_id
   2949 	 *     @type string $username
   2950 	 *     @type string $password
   2951 	 * }
   2952 	 * @return array|IXR_Error
   2953 	 */
   2954 	public function wp_getPage( $args ) {
   2955 		$this->escape( $args );
   2956 
   2957 		$page_id  = (int) $args[1];
   2958 		$username = $args[2];
   2959 		$password = $args[3];
   2960 
   2961 		$user = $this->login( $username, $password );
   2962 		if ( ! $user ) {
   2963 			return $this->error;
   2964 		}
   2965 
   2966 		$page = get_post( $page_id );
   2967 		if ( ! $page ) {
   2968 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   2969 		}
   2970 
   2971 		if ( ! current_user_can( 'edit_page', $page_id ) ) {
   2972 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
   2973 		}
   2974 
   2975 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   2976 		do_action( 'xmlrpc_call', 'wp.getPage', $args, $this );
   2977 
   2978 		// If we found the page then format the data.
   2979 		if ( $page->ID && ( 'page' === $page->post_type ) ) {
   2980 			return $this->_prepare_page( $page );
   2981 		} else {
   2982 			// If the page doesn't exist, indicate that.
   2983 			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
   2984 		}
   2985 	}
   2986 
   2987 	/**
   2988 	 * Retrieve Pages.
   2989 	 *
   2990 	 * @since 2.2.0
   2991 	 *
   2992 	 * @param array $args {
   2993 	 *     Method arguments. Note: arguments must be ordered as documented.
   2994 	 *
   2995 	 *     @type int    $blog_id (unused)
   2996 	 *     @type string $username
   2997 	 *     @type string $password
   2998 	 *     @type int    $num_pages
   2999 	 * }
   3000 	 * @return array|IXR_Error
   3001 	 */
   3002 	public function wp_getPages( $args ) {
   3003 		$this->escape( $args );
   3004 
   3005 		$username  = $args[1];
   3006 		$password  = $args[2];
   3007 		$num_pages = isset( $args[3] ) ? (int) $args[3] : 10;
   3008 
   3009 		$user = $this->login( $username, $password );
   3010 		if ( ! $user ) {
   3011 			return $this->error;
   3012 		}
   3013 
   3014 		if ( ! current_user_can( 'edit_pages' ) ) {
   3015 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
   3016 		}
   3017 
   3018 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3019 		do_action( 'xmlrpc_call', 'wp.getPages', $args, $this );
   3020 
   3021 		$pages     = get_posts(
   3022 			array(
   3023 				'post_type'   => 'page',
   3024 				'post_status' => 'any',
   3025 				'numberposts' => $num_pages,
   3026 			)
   3027 		);
   3028 		$num_pages = count( $pages );
   3029 
   3030 		// If we have pages, put together their info.
   3031 		if ( $num_pages >= 1 ) {
   3032 			$pages_struct = array();
   3033 
   3034 			foreach ( $pages as $page ) {
   3035 				if ( current_user_can( 'edit_page', $page->ID ) ) {
   3036 					$pages_struct[] = $this->_prepare_page( $page );
   3037 				}
   3038 			}
   3039 
   3040 			return $pages_struct;
   3041 		}
   3042 
   3043 		return array();
   3044 	}
   3045 
   3046 	/**
   3047 	 * Create new page.
   3048 	 *
   3049 	 * @since 2.2.0
   3050 	 *
   3051 	 * @see wp_xmlrpc_server::mw_newPost()
   3052 	 *
   3053 	 * @param array $args {
   3054 	 *     Method arguments. Note: arguments must be ordered as documented.
   3055 	 *
   3056 	 *     @type int    $blog_id (unused)
   3057 	 *     @type string $username
   3058 	 *     @type string $password
   3059 	 *     @type array  $content_struct
   3060 	 * }
   3061 	 * @return int|IXR_Error
   3062 	 */
   3063 	public function wp_newPage( $args ) {
   3064 		// Items not escaped here will be escaped in wp_newPost().
   3065 		$username = $this->escape( $args[1] );
   3066 		$password = $this->escape( $args[2] );
   3067 
   3068 		$user = $this->login( $username, $password );
   3069 		if ( ! $user ) {
   3070 			return $this->error;
   3071 		}
   3072 
   3073 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3074 		do_action( 'xmlrpc_call', 'wp.newPage', $args, $this );
   3075 
   3076 		// Mark this as content for a page.
   3077 		$args[3]['post_type'] = 'page';
   3078 
   3079 		// Let mw_newPost() do all of the heavy lifting.
   3080 		return $this->mw_newPost( $args );
   3081 	}
   3082 
   3083 	/**
   3084 	 * Delete page.
   3085 	 *
   3086 	 * @since 2.2.0
   3087 	 *
   3088 	 * @param array $args {
   3089 	 *     Method arguments. Note: arguments must be ordered as documented.
   3090 	 *
   3091 	 *     @type int    $blog_id (unused)
   3092 	 *     @type string $username
   3093 	 *     @type string $password
   3094 	 *     @type int    $page_id
   3095 	 * }
   3096 	 * @return true|IXR_Error True, if success.
   3097 	 */
   3098 	public function wp_deletePage( $args ) {
   3099 		$this->escape( $args );
   3100 
   3101 		$username = $args[1];
   3102 		$password = $args[2];
   3103 		$page_id  = (int) $args[3];
   3104 
   3105 		$user = $this->login( $username, $password );
   3106 		if ( ! $user ) {
   3107 			return $this->error;
   3108 		}
   3109 
   3110 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3111 		do_action( 'xmlrpc_call', 'wp.deletePage', $args, $this );
   3112 
   3113 		// Get the current page based on the 'page_id' and
   3114 		// make sure it is a page and not a post.
   3115 		$actual_page = get_post( $page_id, ARRAY_A );
   3116 		if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
   3117 			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
   3118 		}
   3119 
   3120 		// Make sure the user can delete pages.
   3121 		if ( ! current_user_can( 'delete_page', $page_id ) ) {
   3122 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
   3123 		}
   3124 
   3125 		// Attempt to delete the page.
   3126 		$result = wp_delete_post( $page_id );
   3127 		if ( ! $result ) {
   3128 			return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
   3129 		}
   3130 
   3131 		/**
   3132 		 * Fires after a page has been successfully deleted via XML-RPC.
   3133 		 *
   3134 		 * @since 3.4.0
   3135 		 *
   3136 		 * @param int   $page_id ID of the deleted page.
   3137 		 * @param array $args    An array of arguments to delete the page.
   3138 		 */
   3139 		do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   3140 
   3141 		return true;
   3142 	}
   3143 
   3144 	/**
   3145 	 * Edit page.
   3146 	 *
   3147 	 * @since 2.2.0
   3148 	 *
   3149 	 * @param array $args {
   3150 	 *     Method arguments. Note: arguments must be ordered as documented.
   3151 	 *
   3152 	 *     @type int    $blog_id (unused)
   3153 	 *     @type int    $page_id
   3154 	 *     @type string $username
   3155 	 *     @type string $password
   3156 	 *     @type string $content
   3157 	 *     @type string $publish
   3158 	 * }
   3159 	 * @return array|IXR_Error
   3160 	 */
   3161 	public function wp_editPage( $args ) {
   3162 		// Items will be escaped in mw_editPost().
   3163 		$page_id  = (int) $args[1];
   3164 		$username = $args[2];
   3165 		$password = $args[3];
   3166 		$content  = $args[4];
   3167 		$publish  = $args[5];
   3168 
   3169 		$escaped_username = $this->escape( $username );
   3170 		$escaped_password = $this->escape( $password );
   3171 
   3172 		$user = $this->login( $escaped_username, $escaped_password );
   3173 		if ( ! $user ) {
   3174 			return $this->error;
   3175 		}
   3176 
   3177 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3178 		do_action( 'xmlrpc_call', 'wp.editPage', $args, $this );
   3179 
   3180 		// Get the page data and make sure it is a page.
   3181 		$actual_page = get_post( $page_id, ARRAY_A );
   3182 		if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
   3183 			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
   3184 		}
   3185 
   3186 		// Make sure the user is allowed to edit pages.
   3187 		if ( ! current_user_can( 'edit_page', $page_id ) ) {
   3188 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
   3189 		}
   3190 
   3191 		// Mark this as content for a page.
   3192 		$content['post_type'] = 'page';
   3193 
   3194 		// Arrange args in the way mw_editPost() understands.
   3195 		$args = array(
   3196 			$page_id,
   3197 			$username,
   3198 			$password,
   3199 			$content,
   3200 			$publish,
   3201 		);
   3202 
   3203 		// Let mw_editPost() do all of the heavy lifting.
   3204 		return $this->mw_editPost( $args );
   3205 	}
   3206 
   3207 	/**
   3208 	 * Retrieve page list.
   3209 	 *
   3210 	 * @since 2.2.0
   3211 	 *
   3212 	 * @global wpdb $wpdb WordPress database abstraction object.
   3213 	 *
   3214 	 * @param array $args {
   3215 	 *     Method arguments. Note: arguments must be ordered as documented.
   3216 	 *
   3217 	 *     @type int    $blog_id (unused)
   3218 	 *     @type string $username
   3219 	 *     @type string $password
   3220 	 * }
   3221 	 * @return array|IXR_Error
   3222 	 */
   3223 	public function wp_getPageList( $args ) {
   3224 		global $wpdb;
   3225 
   3226 		$this->escape( $args );
   3227 
   3228 		$username = $args[1];
   3229 		$password = $args[2];
   3230 
   3231 		$user = $this->login( $username, $password );
   3232 		if ( ! $user ) {
   3233 			return $this->error;
   3234 		}
   3235 
   3236 		if ( ! current_user_can( 'edit_pages' ) ) {
   3237 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
   3238 		}
   3239 
   3240 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3241 		do_action( 'xmlrpc_call', 'wp.getPageList', $args, $this );
   3242 
   3243 		// Get list of page IDs and titles.
   3244 		$page_list = $wpdb->get_results(
   3245 			"
   3246 			SELECT ID page_id,
   3247 				post_title page_title,
   3248 				post_parent page_parent_id,
   3249 				post_date_gmt,
   3250 				post_date,
   3251 				post_status
   3252 			FROM {$wpdb->posts}
   3253 			WHERE post_type = 'page'
   3254 			ORDER BY ID
   3255 		"
   3256 		);
   3257 
   3258 		// The date needs to be formatted properly.
   3259 		$num_pages = count( $page_list );
   3260 		for ( $i = 0; $i < $num_pages; $i++ ) {
   3261 			$page_list[ $i ]->dateCreated      = $this->_convert_date( $page_list[ $i ]->post_date );
   3262 			$page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date );
   3263 
   3264 			unset( $page_list[ $i ]->post_date_gmt );
   3265 			unset( $page_list[ $i ]->post_date );
   3266 			unset( $page_list[ $i ]->post_status );
   3267 		}
   3268 
   3269 		return $page_list;
   3270 	}
   3271 
   3272 	/**
   3273 	 * Retrieve authors list.
   3274 	 *
   3275 	 * @since 2.2.0
   3276 	 *
   3277 	 * @param array $args {
   3278 	 *     Method arguments. Note: arguments must be ordered as documented.
   3279 	 *
   3280 	 *     @type int    $blog_id (unused)
   3281 	 *     @type string $username
   3282 	 *     @type string $password
   3283 	 * }
   3284 	 * @return array|IXR_Error
   3285 	 */
   3286 	public function wp_getAuthors( $args ) {
   3287 		$this->escape( $args );
   3288 
   3289 		$username = $args[1];
   3290 		$password = $args[2];
   3291 
   3292 		$user = $this->login( $username, $password );
   3293 		if ( ! $user ) {
   3294 			return $this->error;
   3295 		}
   3296 
   3297 		if ( ! current_user_can( 'edit_posts' ) ) {
   3298 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
   3299 		}
   3300 
   3301 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3302 		do_action( 'xmlrpc_call', 'wp.getAuthors', $args, $this );
   3303 
   3304 		$authors = array();
   3305 		foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) {
   3306 			$authors[] = array(
   3307 				'user_id'      => $user->ID,
   3308 				'user_login'   => $user->user_login,
   3309 				'display_name' => $user->display_name,
   3310 			);
   3311 		}
   3312 
   3313 		return $authors;
   3314 	}
   3315 
   3316 	/**
   3317 	 * Get list of all tags
   3318 	 *
   3319 	 * @since 2.7.0
   3320 	 *
   3321 	 * @param array $args {
   3322 	 *     Method arguments. Note: arguments must be ordered as documented.
   3323 	 *
   3324 	 *     @type int    $blog_id (unused)
   3325 	 *     @type string $username
   3326 	 *     @type string $password
   3327 	 * }
   3328 	 * @return array|IXR_Error
   3329 	 */
   3330 	public function wp_getTags( $args ) {
   3331 		$this->escape( $args );
   3332 
   3333 		$username = $args[1];
   3334 		$password = $args[2];
   3335 
   3336 		$user = $this->login( $username, $password );
   3337 		if ( ! $user ) {
   3338 			return $this->error;
   3339 		}
   3340 
   3341 		if ( ! current_user_can( 'edit_posts' ) ) {
   3342 			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
   3343 		}
   3344 
   3345 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3346 		do_action( 'xmlrpc_call', 'wp.getKeywords', $args, $this );
   3347 
   3348 		$tags = array();
   3349 
   3350 		$all_tags = get_tags();
   3351 		if ( $all_tags ) {
   3352 			foreach ( (array) $all_tags as $tag ) {
   3353 				$struct             = array();
   3354 				$struct['tag_id']   = $tag->term_id;
   3355 				$struct['name']     = $tag->name;
   3356 				$struct['count']    = $tag->count;
   3357 				$struct['slug']     = $tag->slug;
   3358 				$struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) );
   3359 				$struct['rss_url']  = esc_html( get_tag_feed_link( $tag->term_id ) );
   3360 
   3361 				$tags[] = $struct;
   3362 			}
   3363 		}
   3364 
   3365 		return $tags;
   3366 	}
   3367 
   3368 	/**
   3369 	 * Create new category.
   3370 	 *
   3371 	 * @since 2.2.0
   3372 	 *
   3373 	 * @param array $args {
   3374 	 *     Method arguments. Note: arguments must be ordered as documented.
   3375 	 *
   3376 	 *     @type int    $blog_id (unused)
   3377 	 *     @type string $username
   3378 	 *     @type string $password
   3379 	 *     @type array  $category
   3380 	 * }
   3381 	 * @return int|IXR_Error Category ID.
   3382 	 */
   3383 	public function wp_newCategory( $args ) {
   3384 		$this->escape( $args );
   3385 
   3386 		$username = $args[1];
   3387 		$password = $args[2];
   3388 		$category = $args[3];
   3389 
   3390 		$user = $this->login( $username, $password );
   3391 		if ( ! $user ) {
   3392 			return $this->error;
   3393 		}
   3394 
   3395 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3396 		do_action( 'xmlrpc_call', 'wp.newCategory', $args, $this );
   3397 
   3398 		// Make sure the user is allowed to add a category.
   3399 		if ( ! current_user_can( 'manage_categories' ) ) {
   3400 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) );
   3401 		}
   3402 
   3403 		// If no slug was provided, make it empty
   3404 		// so that WordPress will generate one.
   3405 		if ( empty( $category['slug'] ) ) {
   3406 			$category['slug'] = '';
   3407 		}
   3408 
   3409 		// If no parent_id was provided, make it empty
   3410 		// so that it will be a top-level page (no parent).
   3411 		if ( ! isset( $category['parent_id'] ) ) {
   3412 			$category['parent_id'] = '';
   3413 		}
   3414 
   3415 		// If no description was provided, make it empty.
   3416 		if ( empty( $category['description'] ) ) {
   3417 			$category['description'] = '';
   3418 		}
   3419 
   3420 		$new_category = array(
   3421 			'cat_name'             => $category['name'],
   3422 			'category_nicename'    => $category['slug'],
   3423 			'category_parent'      => $category['parent_id'],
   3424 			'category_description' => $category['description'],
   3425 		);
   3426 
   3427 		$cat_id = wp_insert_category( $new_category, true );
   3428 		if ( is_wp_error( $cat_id ) ) {
   3429 			if ( 'term_exists' === $cat_id->get_error_code() ) {
   3430 				return (int) $cat_id->get_error_data();
   3431 			} else {
   3432 				return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
   3433 			}
   3434 		} elseif ( ! $cat_id ) {
   3435 			return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
   3436 		}
   3437 
   3438 		/**
   3439 		 * Fires after a new category has been successfully created via XML-RPC.
   3440 		 *
   3441 		 * @since 3.4.0
   3442 		 *
   3443 		 * @param int   $cat_id ID of the new category.
   3444 		 * @param array $args   An array of new category arguments.
   3445 		 */
   3446 		do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   3447 
   3448 		return $cat_id;
   3449 	}
   3450 
   3451 	/**
   3452 	 * Remove category.
   3453 	 *
   3454 	 * @since 2.5.0
   3455 	 *
   3456 	 * @param array $args {
   3457 	 *     Method arguments. Note: arguments must be ordered as documented.
   3458 	 *
   3459 	 *     @type int    $blog_id (unused)
   3460 	 *     @type string $username
   3461 	 *     @type string $password
   3462 	 *     @type int    $category_id
   3463 	 * }
   3464 	 * @return bool|IXR_Error See wp_delete_term() for return info.
   3465 	 */
   3466 	public function wp_deleteCategory( $args ) {
   3467 		$this->escape( $args );
   3468 
   3469 		$username    = $args[1];
   3470 		$password    = $args[2];
   3471 		$category_id = (int) $args[3];
   3472 
   3473 		$user = $this->login( $username, $password );
   3474 		if ( ! $user ) {
   3475 			return $this->error;
   3476 		}
   3477 
   3478 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3479 		do_action( 'xmlrpc_call', 'wp.deleteCategory', $args, $this );
   3480 
   3481 		if ( ! current_user_can( 'delete_term', $category_id ) ) {
   3482 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) );
   3483 		}
   3484 
   3485 		$status = wp_delete_term( $category_id, 'category' );
   3486 
   3487 		if ( true == $status ) {
   3488 			/**
   3489 			 * Fires after a category has been successfully deleted via XML-RPC.
   3490 			 *
   3491 			 * @since 3.4.0
   3492 			 *
   3493 			 * @param int   $category_id ID of the deleted category.
   3494 			 * @param array $args        An array of arguments to delete the category.
   3495 			 */
   3496 			do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   3497 		}
   3498 
   3499 		return $status;
   3500 	}
   3501 
   3502 	/**
   3503 	 * Retrieve category list.
   3504 	 *
   3505 	 * @since 2.2.0
   3506 	 *
   3507 	 * @param array $args {
   3508 	 *     Method arguments. Note: arguments must be ordered as documented.
   3509 	 *
   3510 	 *     @type int    $blog_id (unused)
   3511 	 *     @type string $username
   3512 	 *     @type string $password
   3513 	 *     @type array  $category
   3514 	 *     @type int    $max_results
   3515 	 * }
   3516 	 * @return array|IXR_Error
   3517 	 */
   3518 	public function wp_suggestCategories( $args ) {
   3519 		$this->escape( $args );
   3520 
   3521 		$username    = $args[1];
   3522 		$password    = $args[2];
   3523 		$category    = $args[3];
   3524 		$max_results = (int) $args[4];
   3525 
   3526 		$user = $this->login( $username, $password );
   3527 		if ( ! $user ) {
   3528 			return $this->error;
   3529 		}
   3530 
   3531 		if ( ! current_user_can( 'edit_posts' ) ) {
   3532 			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
   3533 		}
   3534 
   3535 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3536 		do_action( 'xmlrpc_call', 'wp.suggestCategories', $args, $this );
   3537 
   3538 		$category_suggestions = array();
   3539 		$args                 = array(
   3540 			'get'        => 'all',
   3541 			'number'     => $max_results,
   3542 			'name__like' => $category,
   3543 		);
   3544 		foreach ( (array) get_categories( $args ) as $cat ) {
   3545 			$category_suggestions[] = array(
   3546 				'category_id'   => $cat->term_id,
   3547 				'category_name' => $cat->name,
   3548 			);
   3549 		}
   3550 
   3551 		return $category_suggestions;
   3552 	}
   3553 
   3554 	/**
   3555 	 * Retrieve comment.
   3556 	 *
   3557 	 * @since 2.7.0
   3558 	 *
   3559 	 * @param array $args {
   3560 	 *     Method arguments. Note: arguments must be ordered as documented.
   3561 	 *
   3562 	 *     @type int    $blog_id (unused)
   3563 	 *     @type string $username
   3564 	 *     @type string $password
   3565 	 *     @type int    $comment_id
   3566 	 * }
   3567 	 * @return array|IXR_Error
   3568 	 */
   3569 	public function wp_getComment( $args ) {
   3570 		$this->escape( $args );
   3571 
   3572 		$username   = $args[1];
   3573 		$password   = $args[2];
   3574 		$comment_id = (int) $args[3];
   3575 
   3576 		$user = $this->login( $username, $password );
   3577 		if ( ! $user ) {
   3578 			return $this->error;
   3579 		}
   3580 
   3581 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3582 		do_action( 'xmlrpc_call', 'wp.getComment', $args, $this );
   3583 
   3584 		$comment = get_comment( $comment_id );
   3585 		if ( ! $comment ) {
   3586 			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
   3587 		}
   3588 
   3589 		if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
   3590 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
   3591 		}
   3592 
   3593 		return $this->_prepare_comment( $comment );
   3594 	}
   3595 
   3596 	/**
   3597 	 * Retrieve comments.
   3598 	 *
   3599 	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
   3600 	 * array as last argument.
   3601 	 *
   3602 	 * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
   3603 	 *
   3604 	 * The defaults are as follows:
   3605 	 * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
   3606 	 * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
   3607 	 * - 'number' - Default is 10. Total number of media items to retrieve.
   3608 	 * - 'offset' - Default is 0. See WP_Query::query() for more.
   3609 	 *
   3610 	 * @since 2.7.0
   3611 	 *
   3612 	 * @param array $args {
   3613 	 *     Method arguments. Note: arguments must be ordered as documented.
   3614 	 *
   3615 	 *     @type int    $blog_id (unused)
   3616 	 *     @type string $username
   3617 	 *     @type string $password
   3618 	 *     @type array  $struct
   3619 	 * }
   3620 	 * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
   3621 	 */
   3622 	public function wp_getComments( $args ) {
   3623 		$this->escape( $args );
   3624 
   3625 		$username = $args[1];
   3626 		$password = $args[2];
   3627 		$struct   = isset( $args[3] ) ? $args[3] : array();
   3628 
   3629 		$user = $this->login( $username, $password );
   3630 		if ( ! $user ) {
   3631 			return $this->error;
   3632 		}
   3633 
   3634 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3635 		do_action( 'xmlrpc_call', 'wp.getComments', $args, $this );
   3636 
   3637 		if ( isset( $struct['status'] ) ) {
   3638 			$status = $struct['status'];
   3639 		} else {
   3640 			$status = '';
   3641 		}
   3642 
   3643 		if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
   3644 			return new IXR_Error( 401, __( 'Invalid comment status.' ) );
   3645 		}
   3646 
   3647 		$post_id = '';
   3648 		if ( isset( $struct['post_id'] ) ) {
   3649 			$post_id = absint( $struct['post_id'] );
   3650 		}
   3651 
   3652 		$post_type = '';
   3653 		if ( isset( $struct['post_type'] ) ) {
   3654 			$post_type_object = get_post_type_object( $struct['post_type'] );
   3655 			if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
   3656 				return new IXR_Error( 404, __( 'Invalid post type.' ) );
   3657 			}
   3658 			$post_type = $struct['post_type'];
   3659 		}
   3660 
   3661 		$offset = 0;
   3662 		if ( isset( $struct['offset'] ) ) {
   3663 			$offset = absint( $struct['offset'] );
   3664 		}
   3665 
   3666 		$number = 10;
   3667 		if ( isset( $struct['number'] ) ) {
   3668 			$number = absint( $struct['number'] );
   3669 		}
   3670 
   3671 		$comments = get_comments(
   3672 			array(
   3673 				'status'    => $status,
   3674 				'post_id'   => $post_id,
   3675 				'offset'    => $offset,
   3676 				'number'    => $number,
   3677 				'post_type' => $post_type,
   3678 			)
   3679 		);
   3680 
   3681 		$comments_struct = array();
   3682 		if ( is_array( $comments ) ) {
   3683 			foreach ( $comments as $comment ) {
   3684 				$comments_struct[] = $this->_prepare_comment( $comment );
   3685 			}
   3686 		}
   3687 
   3688 		return $comments_struct;
   3689 	}
   3690 
   3691 	/**
   3692 	 * Delete a comment.
   3693 	 *
   3694 	 * By default, the comment will be moved to the Trash instead of deleted.
   3695 	 * See wp_delete_comment() for more information on this behavior.
   3696 	 *
   3697 	 * @since 2.7.0
   3698 	 *
   3699 	 * @param array $args {
   3700 	 *     Method arguments. Note: arguments must be ordered as documented.
   3701 	 *
   3702 	 *     @type int    $blog_id (unused)
   3703 	 *     @type string $username
   3704 	 *     @type string $password
   3705 	 *     @type int    $comment_ID
   3706 	 * }
   3707 	 * @return bool|IXR_Error See wp_delete_comment().
   3708 	 */
   3709 	public function wp_deleteComment( $args ) {
   3710 		$this->escape( $args );
   3711 
   3712 		$username   = $args[1];
   3713 		$password   = $args[2];
   3714 		$comment_ID = (int) $args[3];
   3715 
   3716 		$user = $this->login( $username, $password );
   3717 		if ( ! $user ) {
   3718 			return $this->error;
   3719 		}
   3720 
   3721 		if ( ! get_comment( $comment_ID ) ) {
   3722 			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
   3723 		}
   3724 
   3725 		if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
   3726 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) );
   3727 		}
   3728 
   3729 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3730 		do_action( 'xmlrpc_call', 'wp.deleteComment', $args, $this );
   3731 
   3732 		$status = wp_delete_comment( $comment_ID );
   3733 
   3734 		if ( $status ) {
   3735 			/**
   3736 			 * Fires after a comment has been successfully deleted via XML-RPC.
   3737 			 *
   3738 			 * @since 3.4.0
   3739 			 *
   3740 			 * @param int   $comment_ID ID of the deleted comment.
   3741 			 * @param array $args       An array of arguments to delete the comment.
   3742 			 */
   3743 			do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   3744 		}
   3745 
   3746 		return $status;
   3747 	}
   3748 
   3749 	/**
   3750 	 * Edit comment.
   3751 	 *
   3752 	 * Besides the common blog_id (unused), username, and password arguments, it takes a
   3753 	 * comment_id integer and a content_struct array as last argument.
   3754 	 *
   3755 	 * The allowed keys in the content_struct array are:
   3756 	 *  - 'author'
   3757 	 *  - 'author_url'
   3758 	 *  - 'author_email'
   3759 	 *  - 'content'
   3760 	 *  - 'date_created_gmt'
   3761 	 *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
   3762 	 *
   3763 	 * @since 2.7.0
   3764 	 *
   3765 	 * @param array $args {
   3766 	 *     Method arguments. Note: arguments must be ordered as documented.
   3767 	 *
   3768 	 *     @type int    $blog_id (unused)
   3769 	 *     @type string $username
   3770 	 *     @type string $password
   3771 	 *     @type int    $comment_ID
   3772 	 *     @type array  $content_struct
   3773 	 * }
   3774 	 * @return true|IXR_Error True, on success.
   3775 	 */
   3776 	public function wp_editComment( $args ) {
   3777 		$this->escape( $args );
   3778 
   3779 		$username       = $args[1];
   3780 		$password       = $args[2];
   3781 		$comment_ID     = (int) $args[3];
   3782 		$content_struct = $args[4];
   3783 
   3784 		$user = $this->login( $username, $password );
   3785 		if ( ! $user ) {
   3786 			return $this->error;
   3787 		}
   3788 
   3789 		if ( ! get_comment( $comment_ID ) ) {
   3790 			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
   3791 		}
   3792 
   3793 		if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
   3794 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
   3795 		}
   3796 
   3797 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3798 		do_action( 'xmlrpc_call', 'wp.editComment', $args, $this );
   3799 		$comment = array(
   3800 			'comment_ID' => $comment_ID,
   3801 		);
   3802 
   3803 		if ( isset( $content_struct['status'] ) ) {
   3804 			$statuses = get_comment_statuses();
   3805 			$statuses = array_keys( $statuses );
   3806 
   3807 			if ( ! in_array( $content_struct['status'], $statuses, true ) ) {
   3808 				return new IXR_Error( 401, __( 'Invalid comment status.' ) );
   3809 			}
   3810 
   3811 			$comment['comment_approved'] = $content_struct['status'];
   3812 		}
   3813 
   3814 		// Do some timestamp voodoo.
   3815 		if ( ! empty( $content_struct['date_created_gmt'] ) ) {
   3816 			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
   3817 			$dateCreated                 = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
   3818 			$comment['comment_date']     = get_date_from_gmt( $dateCreated );
   3819 			$comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
   3820 		}
   3821 
   3822 		if ( isset( $content_struct['content'] ) ) {
   3823 			$comment['comment_content'] = $content_struct['content'];
   3824 		}
   3825 
   3826 		if ( isset( $content_struct['author'] ) ) {
   3827 			$comment['comment_author'] = $content_struct['author'];
   3828 		}
   3829 
   3830 		if ( isset( $content_struct['author_url'] ) ) {
   3831 			$comment['comment_author_url'] = $content_struct['author_url'];
   3832 		}
   3833 
   3834 		if ( isset( $content_struct['author_email'] ) ) {
   3835 			$comment['comment_author_email'] = $content_struct['author_email'];
   3836 		}
   3837 
   3838 		$result = wp_update_comment( $comment, true );
   3839 		if ( is_wp_error( $result ) ) {
   3840 			return new IXR_Error( 500, $result->get_error_message() );
   3841 		}
   3842 
   3843 		if ( ! $result ) {
   3844 			return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) );
   3845 		}
   3846 
   3847 		/**
   3848 		 * Fires after a comment has been successfully updated via XML-RPC.
   3849 		 *
   3850 		 * @since 3.4.0
   3851 		 *
   3852 		 * @param int   $comment_ID ID of the updated comment.
   3853 		 * @param array $args       An array of arguments to update the comment.
   3854 		 */
   3855 		do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   3856 
   3857 		return true;
   3858 	}
   3859 
   3860 	/**
   3861 	 * Create new comment.
   3862 	 *
   3863 	 * @since 2.7.0
   3864 	 *
   3865 	 * @param array $args {
   3866 	 *     Method arguments. Note: arguments must be ordered as documented.
   3867 	 *
   3868 	 *     @type int        $blog_id (unused)
   3869 	 *     @type string     $username
   3870 	 *     @type string     $password
   3871 	 *     @type string|int $post
   3872 	 *     @type array      $content_struct
   3873 	 * }
   3874 	 * @return int|IXR_Error See wp_new_comment().
   3875 	 */
   3876 	public function wp_newComment( $args ) {
   3877 		$this->escape( $args );
   3878 
   3879 		$username       = $args[1];
   3880 		$password       = $args[2];
   3881 		$post           = $args[3];
   3882 		$content_struct = $args[4];
   3883 
   3884 		/**
   3885 		 * Filters whether to allow anonymous comments over XML-RPC.
   3886 		 *
   3887 		 * @since 2.7.0
   3888 		 *
   3889 		 * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
   3890 		 *                    Default false.
   3891 		 */
   3892 		$allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
   3893 
   3894 		$user = $this->login( $username, $password );
   3895 
   3896 		if ( ! $user ) {
   3897 			$logged_in = false;
   3898 			if ( $allow_anon && get_option( 'comment_registration' ) ) {
   3899 				return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) );
   3900 			} elseif ( ! $allow_anon ) {
   3901 				return $this->error;
   3902 			}
   3903 		} else {
   3904 			$logged_in = true;
   3905 		}
   3906 
   3907 		if ( is_numeric( $post ) ) {
   3908 			$post_id = absint( $post );
   3909 		} else {
   3910 			$post_id = url_to_postid( $post );
   3911 		}
   3912 
   3913 		if ( ! $post_id ) {
   3914 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   3915 		}
   3916 
   3917 		if ( ! get_post( $post_id ) ) {
   3918 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   3919 		}
   3920 
   3921 		if ( ! comments_open( $post_id ) ) {
   3922 			return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
   3923 		}
   3924 
   3925 		if (
   3926 			'publish' === get_post_status( $post_id ) &&
   3927 			! current_user_can( 'edit_post', $post_id ) &&
   3928 			post_password_required( $post_id )
   3929 		) {
   3930 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
   3931 		}
   3932 
   3933 		if (
   3934 			'private' === get_post_status( $post_id ) &&
   3935 			! current_user_can( 'read_post', $post_id )
   3936 		) {
   3937 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
   3938 		}
   3939 
   3940 		$comment = array(
   3941 			'comment_post_ID' => $post_id,
   3942 			'comment_content' => trim( $content_struct['content'] ),
   3943 		);
   3944 
   3945 		if ( $logged_in ) {
   3946 			$display_name = $user->display_name;
   3947 			$user_email   = $user->user_email;
   3948 			$user_url     = $user->user_url;
   3949 
   3950 			$comment['comment_author']       = $this->escape( $display_name );
   3951 			$comment['comment_author_email'] = $this->escape( $user_email );
   3952 			$comment['comment_author_url']   = $this->escape( $user_url );
   3953 			$comment['user_ID']              = $user->ID;
   3954 		} else {
   3955 			$comment['comment_author'] = '';
   3956 			if ( isset( $content_struct['author'] ) ) {
   3957 				$comment['comment_author'] = $content_struct['author'];
   3958 			}
   3959 
   3960 			$comment['comment_author_email'] = '';
   3961 			if ( isset( $content_struct['author_email'] ) ) {
   3962 				$comment['comment_author_email'] = $content_struct['author_email'];
   3963 			}
   3964 
   3965 			$comment['comment_author_url'] = '';
   3966 			if ( isset( $content_struct['author_url'] ) ) {
   3967 				$comment['comment_author_url'] = $content_struct['author_url'];
   3968 			}
   3969 
   3970 			$comment['user_ID'] = 0;
   3971 
   3972 			if ( get_option( 'require_name_email' ) ) {
   3973 				if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) {
   3974 					return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
   3975 				} elseif ( ! is_email( $comment['comment_author_email'] ) ) {
   3976 					return new IXR_Error( 403, __( 'A valid email address is required.' ) );
   3977 				}
   3978 			}
   3979 		}
   3980 
   3981 		$comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0;
   3982 
   3983 		/** This filter is documented in wp-includes/comment.php */
   3984 		$allow_empty = apply_filters( 'allow_empty_comment', false, $comment );
   3985 
   3986 		if ( ! $allow_empty && '' === $comment['comment_content'] ) {
   3987 			return new IXR_Error( 403, __( 'Comment is required.' ) );
   3988 		}
   3989 
   3990 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   3991 		do_action( 'xmlrpc_call', 'wp.newComment', $args, $this );
   3992 
   3993 		$comment_ID = wp_new_comment( $comment, true );
   3994 		if ( is_wp_error( $comment_ID ) ) {
   3995 			return new IXR_Error( 403, $comment_ID->get_error_message() );
   3996 		}
   3997 
   3998 		if ( ! $comment_ID ) {
   3999 			return new IXR_Error( 403, __( 'Something went wrong.' ) );
   4000 		}
   4001 
   4002 		/**
   4003 		 * Fires after a new comment has been successfully created via XML-RPC.
   4004 		 *
   4005 		 * @since 3.4.0
   4006 		 *
   4007 		 * @param int   $comment_ID ID of the new comment.
   4008 		 * @param array $args       An array of new comment arguments.
   4009 		 */
   4010 		do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   4011 
   4012 		return $comment_ID;
   4013 	}
   4014 
   4015 	/**
   4016 	 * Retrieve all of the comment status.
   4017 	 *
   4018 	 * @since 2.7.0
   4019 	 *
   4020 	 * @param array $args {
   4021 	 *     Method arguments. Note: arguments must be ordered as documented.
   4022 	 *
   4023 	 *     @type int    $blog_id (unused)
   4024 	 *     @type string $username
   4025 	 *     @type string $password
   4026 	 * }
   4027 	 * @return array|IXR_Error
   4028 	 */
   4029 	public function wp_getCommentStatusList( $args ) {
   4030 		$this->escape( $args );
   4031 
   4032 		$username = $args[1];
   4033 		$password = $args[2];
   4034 
   4035 		$user = $this->login( $username, $password );
   4036 		if ( ! $user ) {
   4037 			return $this->error;
   4038 		}
   4039 
   4040 		if ( ! current_user_can( 'publish_posts' ) ) {
   4041 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
   4042 		}
   4043 
   4044 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4045 		do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this );
   4046 
   4047 		return get_comment_statuses();
   4048 	}
   4049 
   4050 	/**
   4051 	 * Retrieve comment count.
   4052 	 *
   4053 	 * @since 2.5.0
   4054 	 *
   4055 	 * @param array $args {
   4056 	 *     Method arguments. Note: arguments must be ordered as documented.
   4057 	 *
   4058 	 *     @type int    $blog_id (unused)
   4059 	 *     @type string $username
   4060 	 *     @type string $password
   4061 	 *     @type int    $post_id
   4062 	 * }
   4063 	 * @return array|IXR_Error
   4064 	 */
   4065 	public function wp_getCommentCount( $args ) {
   4066 		$this->escape( $args );
   4067 
   4068 		$username = $args[1];
   4069 		$password = $args[2];
   4070 		$post_id  = (int) $args[3];
   4071 
   4072 		$user = $this->login( $username, $password );
   4073 		if ( ! $user ) {
   4074 			return $this->error;
   4075 		}
   4076 
   4077 		$post = get_post( $post_id, ARRAY_A );
   4078 		if ( empty( $post['ID'] ) ) {
   4079 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4080 		}
   4081 
   4082 		if ( ! current_user_can( 'edit_post', $post_id ) ) {
   4083 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) );
   4084 		}
   4085 
   4086 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4087 		do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this );
   4088 
   4089 		$count = wp_count_comments( $post_id );
   4090 
   4091 		return array(
   4092 			'approved'            => $count->approved,
   4093 			'awaiting_moderation' => $count->moderated,
   4094 			'spam'                => $count->spam,
   4095 			'total_comments'      => $count->total_comments,
   4096 		);
   4097 	}
   4098 
   4099 	/**
   4100 	 * Retrieve post statuses.
   4101 	 *
   4102 	 * @since 2.5.0
   4103 	 *
   4104 	 * @param array $args {
   4105 	 *     Method arguments. Note: arguments must be ordered as documented.
   4106 	 *
   4107 	 *     @type int    $blog_id (unused)
   4108 	 *     @type string $username
   4109 	 *     @type string $password
   4110 	 * }
   4111 	 * @return array|IXR_Error
   4112 	 */
   4113 	public function wp_getPostStatusList( $args ) {
   4114 		$this->escape( $args );
   4115 
   4116 		$username = $args[1];
   4117 		$password = $args[2];
   4118 
   4119 		$user = $this->login( $username, $password );
   4120 		if ( ! $user ) {
   4121 			return $this->error;
   4122 		}
   4123 
   4124 		if ( ! current_user_can( 'edit_posts' ) ) {
   4125 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
   4126 		}
   4127 
   4128 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4129 		do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this );
   4130 
   4131 		return get_post_statuses();
   4132 	}
   4133 
   4134 	/**
   4135 	 * Retrieve page statuses.
   4136 	 *
   4137 	 * @since 2.5.0
   4138 	 *
   4139 	 * @param array $args {
   4140 	 *     Method arguments. Note: arguments must be ordered as documented.
   4141 	 *
   4142 	 *     @type int    $blog_id (unused)
   4143 	 *     @type string $username
   4144 	 *     @type string $password
   4145 	 * }
   4146 	 * @return array|IXR_Error
   4147 	 */
   4148 	public function wp_getPageStatusList( $args ) {
   4149 		$this->escape( $args );
   4150 
   4151 		$username = $args[1];
   4152 		$password = $args[2];
   4153 
   4154 		$user = $this->login( $username, $password );
   4155 		if ( ! $user ) {
   4156 			return $this->error;
   4157 		}
   4158 
   4159 		if ( ! current_user_can( 'edit_pages' ) ) {
   4160 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
   4161 		}
   4162 
   4163 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4164 		do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this );
   4165 
   4166 		return get_page_statuses();
   4167 	}
   4168 
   4169 	/**
   4170 	 * Retrieve page templates.
   4171 	 *
   4172 	 * @since 2.6.0
   4173 	 *
   4174 	 * @param array $args {
   4175 	 *     Method arguments. Note: arguments must be ordered as documented.
   4176 	 *
   4177 	 *     @type int    $blog_id (unused)
   4178 	 *     @type string $username
   4179 	 *     @type string $password
   4180 	 * }
   4181 	 * @return array|IXR_Error
   4182 	 */
   4183 	public function wp_getPageTemplates( $args ) {
   4184 		$this->escape( $args );
   4185 
   4186 		$username = $args[1];
   4187 		$password = $args[2];
   4188 
   4189 		$user = $this->login( $username, $password );
   4190 		if ( ! $user ) {
   4191 			return $this->error;
   4192 		}
   4193 
   4194 		if ( ! current_user_can( 'edit_pages' ) ) {
   4195 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
   4196 		}
   4197 
   4198 		$templates            = get_page_templates();
   4199 		$templates['Default'] = 'default';
   4200 
   4201 		return $templates;
   4202 	}
   4203 
   4204 	/**
   4205 	 * Retrieve blog options.
   4206 	 *
   4207 	 * @since 2.6.0
   4208 	 *
   4209 	 * @param array $args {
   4210 	 *     Method arguments. Note: arguments must be ordered as documented.
   4211 	 *
   4212 	 *     @type int    $blog_id (unused)
   4213 	 *     @type string $username
   4214 	 *     @type string $password
   4215 	 *     @type array  $options
   4216 	 * }
   4217 	 * @return array|IXR_Error
   4218 	 */
   4219 	public function wp_getOptions( $args ) {
   4220 		$this->escape( $args );
   4221 
   4222 		$username = $args[1];
   4223 		$password = $args[2];
   4224 		$options  = isset( $args[3] ) ? (array) $args[3] : array();
   4225 
   4226 		$user = $this->login( $username, $password );
   4227 		if ( ! $user ) {
   4228 			return $this->error;
   4229 		}
   4230 
   4231 		// If no specific options where asked for, return all of them.
   4232 		if ( count( $options ) == 0 ) {
   4233 			$options = array_keys( $this->blog_options );
   4234 		}
   4235 
   4236 		return $this->_getOptions( $options );
   4237 	}
   4238 
   4239 	/**
   4240 	 * Retrieve blog options value from list.
   4241 	 *
   4242 	 * @since 2.6.0
   4243 	 *
   4244 	 * @param array $options Options to retrieve.
   4245 	 * @return array
   4246 	 */
   4247 	public function _getOptions( $options ) {
   4248 		$data       = array();
   4249 		$can_manage = current_user_can( 'manage_options' );
   4250 		foreach ( $options as $option ) {
   4251 			if ( array_key_exists( $option, $this->blog_options ) ) {
   4252 				$data[ $option ] = $this->blog_options[ $option ];
   4253 				// Is the value static or dynamic?
   4254 				if ( isset( $data[ $option ]['option'] ) ) {
   4255 					$data[ $option ]['value'] = get_option( $data[ $option ]['option'] );
   4256 					unset( $data[ $option ]['option'] );
   4257 				}
   4258 
   4259 				if ( ! $can_manage ) {
   4260 					$data[ $option ]['readonly'] = true;
   4261 				}
   4262 			}
   4263 		}
   4264 
   4265 		return $data;
   4266 	}
   4267 
   4268 	/**
   4269 	 * Update blog options.
   4270 	 *
   4271 	 * @since 2.6.0
   4272 	 *
   4273 	 * @param array $args {
   4274 	 *     Method arguments. Note: arguments must be ordered as documented.
   4275 	 *
   4276 	 *     @type int    $blog_id (unused)
   4277 	 *     @type string $username
   4278 	 *     @type string $password
   4279 	 *     @type array  $options
   4280 	 * }
   4281 	 * @return array|IXR_Error
   4282 	 */
   4283 	public function wp_setOptions( $args ) {
   4284 		$this->escape( $args );
   4285 
   4286 		$username = $args[1];
   4287 		$password = $args[2];
   4288 		$options  = (array) $args[3];
   4289 
   4290 		$user = $this->login( $username, $password );
   4291 		if ( ! $user ) {
   4292 			return $this->error;
   4293 		}
   4294 
   4295 		if ( ! current_user_can( 'manage_options' ) ) {
   4296 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
   4297 		}
   4298 
   4299 		$option_names = array();
   4300 		foreach ( $options as $o_name => $o_value ) {
   4301 			$option_names[] = $o_name;
   4302 			if ( ! array_key_exists( $o_name, $this->blog_options ) ) {
   4303 				continue;
   4304 			}
   4305 
   4306 			if ( true == $this->blog_options[ $o_name ]['readonly'] ) {
   4307 				continue;
   4308 			}
   4309 
   4310 			update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) );
   4311 		}
   4312 
   4313 		// Now return the updated values.
   4314 		return $this->_getOptions( $option_names );
   4315 	}
   4316 
   4317 	/**
   4318 	 * Retrieve a media item by ID
   4319 	 *
   4320 	 * @since 3.1.0
   4321 	 *
   4322 	 * @param array $args {
   4323 	 *     Method arguments. Note: arguments must be ordered as documented.
   4324 	 *
   4325 	 *     @type int    $blog_id (unused)
   4326 	 *     @type string $username
   4327 	 *     @type string $password
   4328 	 *     @type int    $attachment_id
   4329 	 * }
   4330 	 * @return array|IXR_Error Associative array contains:
   4331 	 *  - 'date_created_gmt'
   4332 	 *  - 'parent'
   4333 	 *  - 'link'
   4334 	 *  - 'thumbnail'
   4335 	 *  - 'title'
   4336 	 *  - 'caption'
   4337 	 *  - 'description'
   4338 	 *  - 'metadata'
   4339 	 */
   4340 	public function wp_getMediaItem( $args ) {
   4341 		$this->escape( $args );
   4342 
   4343 		$username      = $args[1];
   4344 		$password      = $args[2];
   4345 		$attachment_id = (int) $args[3];
   4346 
   4347 		$user = $this->login( $username, $password );
   4348 		if ( ! $user ) {
   4349 			return $this->error;
   4350 		}
   4351 
   4352 		if ( ! current_user_can( 'upload_files' ) ) {
   4353 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
   4354 		}
   4355 
   4356 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4357 		do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this );
   4358 
   4359 		$attachment = get_post( $attachment_id );
   4360 		if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
   4361 			return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
   4362 		}
   4363 
   4364 		return $this->_prepare_media_item( $attachment );
   4365 	}
   4366 
   4367 	/**
   4368 	 * Retrieves a collection of media library items (or attachments)
   4369 	 *
   4370 	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
   4371 	 * array as last argument.
   4372 	 *
   4373 	 * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
   4374 	 *
   4375 	 * The defaults are as follows:
   4376 	 * - 'number' - Default is 5. Total number of media items to retrieve.
   4377 	 * - 'offset' - Default is 0. See WP_Query::query() for more.
   4378 	 * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
   4379 	 * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
   4380 	 *
   4381 	 * @since 3.1.0
   4382 	 *
   4383 	 * @param array $args {
   4384 	 *     Method arguments. Note: arguments must be ordered as documented.
   4385 	 *
   4386 	 *     @type int    $blog_id (unused)
   4387 	 *     @type string $username
   4388 	 *     @type string $password
   4389 	 *     @type array  $struct
   4390 	 * }
   4391 	 * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
   4392 	 */
   4393 	public function wp_getMediaLibrary( $args ) {
   4394 		$this->escape( $args );
   4395 
   4396 		$username = $args[1];
   4397 		$password = $args[2];
   4398 		$struct   = isset( $args[3] ) ? $args[3] : array();
   4399 
   4400 		$user = $this->login( $username, $password );
   4401 		if ( ! $user ) {
   4402 			return $this->error;
   4403 		}
   4404 
   4405 		if ( ! current_user_can( 'upload_files' ) ) {
   4406 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
   4407 		}
   4408 
   4409 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4410 		do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this );
   4411 
   4412 		$parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : '';
   4413 		$mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : '';
   4414 		$offset    = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0;
   4415 		$number    = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1;
   4416 
   4417 		$attachments = get_posts(
   4418 			array(
   4419 				'post_type'      => 'attachment',
   4420 				'post_parent'    => $parent_id,
   4421 				'offset'         => $offset,
   4422 				'numberposts'    => $number,
   4423 				'post_mime_type' => $mime_type,
   4424 			)
   4425 		);
   4426 
   4427 		$attachments_struct = array();
   4428 
   4429 		foreach ( $attachments as $attachment ) {
   4430 			$attachments_struct[] = $this->_prepare_media_item( $attachment );
   4431 		}
   4432 
   4433 		return $attachments_struct;
   4434 	}
   4435 
   4436 	/**
   4437 	 * Retrieves a list of post formats used by the site.
   4438 	 *
   4439 	 * @since 3.1.0
   4440 	 *
   4441 	 * @param array $args {
   4442 	 *     Method arguments. Note: arguments must be ordered as documented.
   4443 	 *
   4444 	 *     @type int    $blog_id (unused)
   4445 	 *     @type string $username
   4446 	 *     @type string $password
   4447 	 * }
   4448 	 * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
   4449 	 */
   4450 	public function wp_getPostFormats( $args ) {
   4451 		$this->escape( $args );
   4452 
   4453 		$username = $args[1];
   4454 		$password = $args[2];
   4455 
   4456 		$user = $this->login( $username, $password );
   4457 		if ( ! $user ) {
   4458 			return $this->error;
   4459 		}
   4460 
   4461 		if ( ! current_user_can( 'edit_posts' ) ) {
   4462 			return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
   4463 		}
   4464 
   4465 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4466 		do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this );
   4467 
   4468 		$formats = get_post_format_strings();
   4469 
   4470 		// Find out if they want a list of currently supports formats.
   4471 		if ( isset( $args[3] ) && is_array( $args[3] ) ) {
   4472 			if ( $args[3]['show-supported'] ) {
   4473 				if ( current_theme_supports( 'post-formats' ) ) {
   4474 					$supported = get_theme_support( 'post-formats' );
   4475 
   4476 					$data              = array();
   4477 					$data['all']       = $formats;
   4478 					$data['supported'] = $supported[0];
   4479 
   4480 					$formats = $data;
   4481 				}
   4482 			}
   4483 		}
   4484 
   4485 		return $formats;
   4486 	}
   4487 
   4488 	/**
   4489 	 * Retrieves a post type
   4490 	 *
   4491 	 * @since 3.4.0
   4492 	 *
   4493 	 * @see get_post_type_object()
   4494 	 *
   4495 	 * @param array $args {
   4496 	 *     Method arguments. Note: arguments must be ordered as documented.
   4497 	 *
   4498 	 *     @type int    $blog_id (unused)
   4499 	 *     @type string $username
   4500 	 *     @type string $password
   4501 	 *     @type string $post_type_name
   4502 	 *     @type array  $fields (optional)
   4503 	 * }
   4504 	 * @return array|IXR_Error Array contains:
   4505 	 *  - 'labels'
   4506 	 *  - 'description'
   4507 	 *  - 'capability_type'
   4508 	 *  - 'cap'
   4509 	 *  - 'map_meta_cap'
   4510 	 *  - 'hierarchical'
   4511 	 *  - 'menu_position'
   4512 	 *  - 'taxonomies'
   4513 	 *  - 'supports'
   4514 	 */
   4515 	public function wp_getPostType( $args ) {
   4516 		if ( ! $this->minimum_args( $args, 4 ) ) {
   4517 			return $this->error;
   4518 		}
   4519 
   4520 		$this->escape( $args );
   4521 
   4522 		$username       = $args[1];
   4523 		$password       = $args[2];
   4524 		$post_type_name = $args[3];
   4525 
   4526 		if ( isset( $args[4] ) ) {
   4527 			$fields = $args[4];
   4528 		} else {
   4529 			/**
   4530 			 * Filters the default query fields used by the given XML-RPC method.
   4531 			 *
   4532 			 * @since 3.4.0
   4533 			 *
   4534 			 * @param array  $fields An array of post type query fields for the given method.
   4535 			 * @param string $method The method name.
   4536 			 */
   4537 			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
   4538 		}
   4539 
   4540 		$user = $this->login( $username, $password );
   4541 		if ( ! $user ) {
   4542 			return $this->error;
   4543 		}
   4544 
   4545 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4546 		do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this );
   4547 
   4548 		if ( ! post_type_exists( $post_type_name ) ) {
   4549 			return new IXR_Error( 403, __( 'Invalid post type.' ) );
   4550 		}
   4551 
   4552 		$post_type = get_post_type_object( $post_type_name );
   4553 
   4554 		if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
   4555 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
   4556 		}
   4557 
   4558 		return $this->_prepare_post_type( $post_type, $fields );
   4559 	}
   4560 
   4561 	/**
   4562 	 * Retrieves a post types
   4563 	 *
   4564 	 * @since 3.4.0
   4565 	 *
   4566 	 * @see get_post_types()
   4567 	 *
   4568 	 * @param array $args {
   4569 	 *     Method arguments. Note: arguments must be ordered as documented.
   4570 	 *
   4571 	 *     @type int    $blog_id (unused)
   4572 	 *     @type string $username
   4573 	 *     @type string $password
   4574 	 *     @type array  $filter (optional)
   4575 	 *     @type array  $fields (optional)
   4576 	 * }
   4577 	 * @return array|IXR_Error
   4578 	 */
   4579 	public function wp_getPostTypes( $args ) {
   4580 		if ( ! $this->minimum_args( $args, 3 ) ) {
   4581 			return $this->error;
   4582 		}
   4583 
   4584 		$this->escape( $args );
   4585 
   4586 		$username = $args[1];
   4587 		$password = $args[2];
   4588 		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
   4589 
   4590 		if ( isset( $args[4] ) ) {
   4591 			$fields = $args[4];
   4592 		} else {
   4593 			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4594 			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
   4595 		}
   4596 
   4597 		$user = $this->login( $username, $password );
   4598 		if ( ! $user ) {
   4599 			return $this->error;
   4600 		}
   4601 
   4602 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4603 		do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this );
   4604 
   4605 		$post_types = get_post_types( $filter, 'objects' );
   4606 
   4607 		$struct = array();
   4608 
   4609 		foreach ( $post_types as $post_type ) {
   4610 			if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
   4611 				continue;
   4612 			}
   4613 
   4614 			$struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields );
   4615 		}
   4616 
   4617 		return $struct;
   4618 	}
   4619 
   4620 	/**
   4621 	 * Retrieve revisions for a specific post.
   4622 	 *
   4623 	 * @since 3.5.0
   4624 	 *
   4625 	 * The optional $fields parameter specifies what fields will be included
   4626 	 * in the response array.
   4627 	 *
   4628 	 * @uses wp_get_post_revisions()
   4629 	 * @see wp_getPost() for more on $fields
   4630 	 *
   4631 	 * @param array $args {
   4632 	 *     Method arguments. Note: arguments must be ordered as documented.
   4633 	 *
   4634 	 *     @type int    $blog_id (unused)
   4635 	 *     @type string $username
   4636 	 *     @type string $password
   4637 	 *     @type int    $post_id
   4638 	 *     @type array  $fields (optional)
   4639 	 * }
   4640 	 * @return array|IXR_Error contains a collection of posts.
   4641 	 */
   4642 	public function wp_getRevisions( $args ) {
   4643 		if ( ! $this->minimum_args( $args, 4 ) ) {
   4644 			return $this->error;
   4645 		}
   4646 
   4647 		$this->escape( $args );
   4648 
   4649 		$username = $args[1];
   4650 		$password = $args[2];
   4651 		$post_id  = (int) $args[3];
   4652 
   4653 		if ( isset( $args[4] ) ) {
   4654 			$fields = $args[4];
   4655 		} else {
   4656 			/**
   4657 			 * Filters the default revision query fields used by the given XML-RPC method.
   4658 			 *
   4659 			 * @since 3.5.0
   4660 			 *
   4661 			 * @param array  $field  An array of revision query fields.
   4662 			 * @param string $method The method name.
   4663 			 */
   4664 			$fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
   4665 		}
   4666 
   4667 		$user = $this->login( $username, $password );
   4668 		if ( ! $user ) {
   4669 			return $this->error;
   4670 		}
   4671 
   4672 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4673 		do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this );
   4674 
   4675 		$post = get_post( $post_id );
   4676 		if ( ! $post ) {
   4677 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4678 		}
   4679 
   4680 		if ( ! current_user_can( 'edit_post', $post_id ) ) {
   4681 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
   4682 		}
   4683 
   4684 		// Check if revisions are enabled.
   4685 		if ( ! wp_revisions_enabled( $post ) ) {
   4686 			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
   4687 		}
   4688 
   4689 		$revisions = wp_get_post_revisions( $post_id );
   4690 
   4691 		if ( ! $revisions ) {
   4692 			return array();
   4693 		}
   4694 
   4695 		$struct = array();
   4696 
   4697 		foreach ( $revisions as $revision ) {
   4698 			if ( ! current_user_can( 'read_post', $revision->ID ) ) {
   4699 				continue;
   4700 			}
   4701 
   4702 			// Skip autosaves.
   4703 			if ( wp_is_post_autosave( $revision ) ) {
   4704 				continue;
   4705 			}
   4706 
   4707 			$struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
   4708 		}
   4709 
   4710 		return $struct;
   4711 	}
   4712 
   4713 	/**
   4714 	 * Restore a post revision
   4715 	 *
   4716 	 * @since 3.5.0
   4717 	 *
   4718 	 * @uses wp_restore_post_revision()
   4719 	 *
   4720 	 * @param array $args {
   4721 	 *     Method arguments. Note: arguments must be ordered as documented.
   4722 	 *
   4723 	 *     @type int    $blog_id (unused)
   4724 	 *     @type string $username
   4725 	 *     @type string $password
   4726 	 *     @type int    $revision_id
   4727 	 * }
   4728 	 * @return bool|IXR_Error false if there was an error restoring, true if success.
   4729 	 */
   4730 	public function wp_restoreRevision( $args ) {
   4731 		if ( ! $this->minimum_args( $args, 3 ) ) {
   4732 			return $this->error;
   4733 		}
   4734 
   4735 		$this->escape( $args );
   4736 
   4737 		$username    = $args[1];
   4738 		$password    = $args[2];
   4739 		$revision_id = (int) $args[3];
   4740 
   4741 		$user = $this->login( $username, $password );
   4742 		if ( ! $user ) {
   4743 			return $this->error;
   4744 		}
   4745 
   4746 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4747 		do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this );
   4748 
   4749 		$revision = wp_get_post_revision( $revision_id );
   4750 		if ( ! $revision ) {
   4751 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4752 		}
   4753 
   4754 		if ( wp_is_post_autosave( $revision ) ) {
   4755 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4756 		}
   4757 
   4758 		$post = get_post( $revision->post_parent );
   4759 		if ( ! $post ) {
   4760 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4761 		}
   4762 
   4763 		if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) {
   4764 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   4765 		}
   4766 
   4767 		// Check if revisions are disabled.
   4768 		if ( ! wp_revisions_enabled( $post ) ) {
   4769 			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
   4770 		}
   4771 
   4772 		$post = wp_restore_post_revision( $revision_id );
   4773 
   4774 		return (bool) $post;
   4775 	}
   4776 
   4777 	/*
   4778 	 * Blogger API functions.
   4779 	 * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
   4780 	 */
   4781 
   4782 	/**
   4783 	 * Retrieve blogs that user owns.
   4784 	 *
   4785 	 * Will make more sense once we support multiple blogs.
   4786 	 *
   4787 	 * @since 1.5.0
   4788 	 *
   4789 	 * @param array $args {
   4790 	 *     Method arguments. Note: arguments must be ordered as documented.
   4791 	 *
   4792 	 *     @type int    $blog_id (unused)
   4793 	 *     @type string $username
   4794 	 *     @type string $password
   4795 	 * }
   4796 	 * @return array|IXR_Error
   4797 	 */
   4798 	public function blogger_getUsersBlogs( $args ) {
   4799 		if ( ! $this->minimum_args( $args, 3 ) ) {
   4800 			return $this->error;
   4801 		}
   4802 
   4803 		if ( is_multisite() ) {
   4804 			return $this->_multisite_getUsersBlogs( $args );
   4805 		}
   4806 
   4807 		$this->escape( $args );
   4808 
   4809 		$username = $args[1];
   4810 		$password = $args[2];
   4811 
   4812 		$user = $this->login( $username, $password );
   4813 		if ( ! $user ) {
   4814 			return $this->error;
   4815 		}
   4816 
   4817 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4818 		do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this );
   4819 
   4820 		$is_admin = current_user_can( 'manage_options' );
   4821 
   4822 		$struct = array(
   4823 			'isAdmin'  => $is_admin,
   4824 			'url'      => get_option( 'home' ) . '/',
   4825 			'blogid'   => '1',
   4826 			'blogName' => get_option( 'blogname' ),
   4827 			'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
   4828 		);
   4829 
   4830 		return array( $struct );
   4831 	}
   4832 
   4833 	/**
   4834 	 * Private function for retrieving a users blogs for multisite setups
   4835 	 *
   4836 	 * @since 3.0.0
   4837 	 *
   4838 	 * @param array $args {
   4839 	 *     Method arguments. Note: arguments must be ordered as documented.
   4840 	 *
   4841 	 *     @type string $username Username.
   4842 	 *     @type string $password Password.
   4843 	 * }
   4844 	 * @return array|IXR_Error
   4845 	 */
   4846 	protected function _multisite_getUsersBlogs( $args ) {
   4847 		$current_blog = get_site();
   4848 
   4849 		$domain = $current_blog->domain;
   4850 		$path   = $current_blog->path . 'xmlrpc.php';
   4851 
   4852 		$rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
   4853 		$rpc->query( 'wp.getUsersBlogs', $args[1], $args[2] );
   4854 		$blogs = $rpc->getResponse();
   4855 
   4856 		if ( isset( $blogs['faultCode'] ) ) {
   4857 			return new IXR_Error( $blogs['faultCode'], $blogs['faultString'] );
   4858 		}
   4859 
   4860 		if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
   4861 			return $blogs;
   4862 		} else {
   4863 			foreach ( (array) $blogs as $blog ) {
   4864 				if ( strpos( $blog['url'], $_SERVER['HTTP_HOST'] ) ) {
   4865 					return array( $blog );
   4866 				}
   4867 			}
   4868 			return array();
   4869 		}
   4870 	}
   4871 
   4872 	/**
   4873 	 * Retrieve user's data.
   4874 	 *
   4875 	 * Gives your client some info about you, so you don't have to.
   4876 	 *
   4877 	 * @since 1.5.0
   4878 	 *
   4879 	 * @param array $args {
   4880 	 *     Method arguments. Note: arguments must be ordered as documented.
   4881 	 *
   4882 	 *     @type int    $blog_id (unused)
   4883 	 *     @type string $username
   4884 	 *     @type string $password
   4885 	 * }
   4886 	 * @return array|IXR_Error
   4887 	 */
   4888 	public function blogger_getUserInfo( $args ) {
   4889 		$this->escape( $args );
   4890 
   4891 		$username = $args[1];
   4892 		$password = $args[2];
   4893 
   4894 		$user = $this->login( $username, $password );
   4895 		if ( ! $user ) {
   4896 			return $this->error;
   4897 		}
   4898 
   4899 		if ( ! current_user_can( 'edit_posts' ) ) {
   4900 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
   4901 		}
   4902 
   4903 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4904 		do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this );
   4905 
   4906 		$struct = array(
   4907 			'nickname'  => $user->nickname,
   4908 			'userid'    => $user->ID,
   4909 			'url'       => $user->user_url,
   4910 			'lastname'  => $user->last_name,
   4911 			'firstname' => $user->first_name,
   4912 		);
   4913 
   4914 		return $struct;
   4915 	}
   4916 
   4917 	/**
   4918 	 * Retrieve post.
   4919 	 *
   4920 	 * @since 1.5.0
   4921 	 *
   4922 	 * @param array $args {
   4923 	 *     Method arguments. Note: arguments must be ordered as documented.
   4924 	 *
   4925 	 *     @type int    $blog_id (unused)
   4926 	 *     @type int    $post_ID
   4927 	 *     @type string $username
   4928 	 *     @type string $password
   4929 	 * }
   4930 	 * @return array|IXR_Error
   4931 	 */
   4932 	public function blogger_getPost( $args ) {
   4933 		$this->escape( $args );
   4934 
   4935 		$post_ID  = (int) $args[1];
   4936 		$username = $args[2];
   4937 		$password = $args[3];
   4938 
   4939 		$user = $this->login( $username, $password );
   4940 		if ( ! $user ) {
   4941 			return $this->error;
   4942 		}
   4943 
   4944 		$post_data = get_post( $post_ID, ARRAY_A );
   4945 		if ( ! $post_data ) {
   4946 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   4947 		}
   4948 
   4949 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   4950 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   4951 		}
   4952 
   4953 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   4954 		do_action( 'xmlrpc_call', 'blogger.getPost', $args, $this );
   4955 
   4956 		$categories = implode( ',', wp_get_post_categories( $post_ID ) );
   4957 
   4958 		$content  = '<title>' . wp_unslash( $post_data['post_title'] ) . '</title>';
   4959 		$content .= '<category>' . $categories . '</category>';
   4960 		$content .= wp_unslash( $post_data['post_content'] );
   4961 
   4962 		$struct = array(
   4963 			'userid'      => $post_data['post_author'],
   4964 			'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
   4965 			'content'     => $content,
   4966 			'postid'      => (string) $post_data['ID'],
   4967 		);
   4968 
   4969 		return $struct;
   4970 	}
   4971 
   4972 	/**
   4973 	 * Retrieve list of recent posts.
   4974 	 *
   4975 	 * @since 1.5.0
   4976 	 *
   4977 	 * @param array $args {
   4978 	 *     Method arguments. Note: arguments must be ordered as documented.
   4979 	 *
   4980 	 *     @type string $appkey (unused)
   4981 	 *     @type int    $blog_id (unused)
   4982 	 *     @type string $username
   4983 	 *     @type string $password
   4984 	 *     @type int    $numberposts (optional)
   4985 	 * }
   4986 	 * @return array|IXR_Error
   4987 	 */
   4988 	public function blogger_getRecentPosts( $args ) {
   4989 
   4990 		$this->escape( $args );
   4991 
   4992 		// $args[0] = appkey - ignored.
   4993 		$username = $args[2];
   4994 		$password = $args[3];
   4995 		if ( isset( $args[4] ) ) {
   4996 			$query = array( 'numberposts' => absint( $args[4] ) );
   4997 		} else {
   4998 			$query = array();
   4999 		}
   5000 
   5001 		$user = $this->login( $username, $password );
   5002 		if ( ! $user ) {
   5003 			return $this->error;
   5004 		}
   5005 
   5006 		if ( ! current_user_can( 'edit_posts' ) ) {
   5007 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
   5008 		}
   5009 
   5010 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5011 		do_action( 'xmlrpc_call', 'blogger.getRecentPosts', $args, $this );
   5012 
   5013 		$posts_list = wp_get_recent_posts( $query );
   5014 
   5015 		if ( ! $posts_list ) {
   5016 			$this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
   5017 			return $this->error;
   5018 		}
   5019 
   5020 		$recent_posts = array();
   5021 		foreach ( $posts_list as $entry ) {
   5022 			if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
   5023 				continue;
   5024 			}
   5025 
   5026 			$post_date  = $this->_convert_date( $entry['post_date'] );
   5027 			$categories = implode( ',', wp_get_post_categories( $entry['ID'] ) );
   5028 
   5029 			$content  = '<title>' . wp_unslash( $entry['post_title'] ) . '</title>';
   5030 			$content .= '<category>' . $categories . '</category>';
   5031 			$content .= wp_unslash( $entry['post_content'] );
   5032 
   5033 			$recent_posts[] = array(
   5034 				'userid'      => $entry['post_author'],
   5035 				'dateCreated' => $post_date,
   5036 				'content'     => $content,
   5037 				'postid'      => (string) $entry['ID'],
   5038 			);
   5039 		}
   5040 
   5041 		return $recent_posts;
   5042 	}
   5043 
   5044 	/**
   5045 	 * Deprecated.
   5046 	 *
   5047 	 * @since 1.5.0
   5048 	 * @deprecated 3.5.0
   5049 	 *
   5050 	 * @param array $args Unused.
   5051 	 * @return IXR_Error Error object.
   5052 	 */
   5053 	public function blogger_getTemplate( $args ) {
   5054 		return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
   5055 	}
   5056 
   5057 	/**
   5058 	 * Deprecated.
   5059 	 *
   5060 	 * @since 1.5.0
   5061 	 * @deprecated 3.5.0
   5062 	 *
   5063 	 * @param array $args Unused.
   5064 	 * @return IXR_Error Error object.
   5065 	 */
   5066 	public function blogger_setTemplate( $args ) {
   5067 		return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
   5068 	}
   5069 
   5070 	/**
   5071 	 * Creates new post.
   5072 	 *
   5073 	 * @since 1.5.0
   5074 	 *
   5075 	 * @param array $args {
   5076 	 *     Method arguments. Note: arguments must be ordered as documented.
   5077 	 *
   5078 	 *     @type string $appkey (unused)
   5079 	 *     @type int    $blog_id (unused)
   5080 	 *     @type string $username
   5081 	 *     @type string $password
   5082 	 *     @type string $content
   5083 	 *     @type string $publish
   5084 	 * }
   5085 	 * @return int|IXR_Error
   5086 	 */
   5087 	public function blogger_newPost( $args ) {
   5088 		$this->escape( $args );
   5089 
   5090 		$username = $args[2];
   5091 		$password = $args[3];
   5092 		$content  = $args[4];
   5093 		$publish  = $args[5];
   5094 
   5095 		$user = $this->login( $username, $password );
   5096 		if ( ! $user ) {
   5097 			return $this->error;
   5098 		}
   5099 
   5100 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5101 		do_action( 'xmlrpc_call', 'blogger.newPost', $args, $this );
   5102 
   5103 		$cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
   5104 		if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || ! current_user_can( $cap ) ) {
   5105 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
   5106 		}
   5107 
   5108 		$post_status = ( $publish ) ? 'publish' : 'draft';
   5109 
   5110 		$post_author = $user->ID;
   5111 
   5112 		$post_title    = xmlrpc_getposttitle( $content );
   5113 		$post_category = xmlrpc_getpostcategory( $content );
   5114 		$post_content  = xmlrpc_removepostdata( $content );
   5115 
   5116 		$post_date     = current_time( 'mysql' );
   5117 		$post_date_gmt = current_time( 'mysql', 1 );
   5118 
   5119 		$post_data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status' );
   5120 
   5121 		$post_ID = wp_insert_post( $post_data );
   5122 		if ( is_wp_error( $post_ID ) ) {
   5123 			return new IXR_Error( 500, $post_ID->get_error_message() );
   5124 		}
   5125 
   5126 		if ( ! $post_ID ) {
   5127 			return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
   5128 		}
   5129 
   5130 		$this->attach_uploads( $post_ID, $post_content );
   5131 
   5132 		/**
   5133 		 * Fires after a new post has been successfully created via the XML-RPC Blogger API.
   5134 		 *
   5135 		 * @since 3.4.0
   5136 		 *
   5137 		 * @param int   $post_ID ID of the new post.
   5138 		 * @param array $args    An array of new post arguments.
   5139 		 */
   5140 		do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   5141 
   5142 		return $post_ID;
   5143 	}
   5144 
   5145 	/**
   5146 	 * Edit a post.
   5147 	 *
   5148 	 * @since 1.5.0
   5149 	 *
   5150 	 * @param array $args {
   5151 	 *     Method arguments. Note: arguments must be ordered as documented.
   5152 	 *
   5153 	 *     @type int    $blog_id (unused)
   5154 	 *     @type int    $post_ID
   5155 	 *     @type string $username
   5156 	 *     @type string $password
   5157 	 *     @type string $content
   5158 	 *     @type bool   $publish
   5159 	 * }
   5160 	 * @return true|IXR_Error true when done.
   5161 	 */
   5162 	public function blogger_editPost( $args ) {
   5163 
   5164 		$this->escape( $args );
   5165 
   5166 		$post_ID  = (int) $args[1];
   5167 		$username = $args[2];
   5168 		$password = $args[3];
   5169 		$content  = $args[4];
   5170 		$publish  = $args[5];
   5171 
   5172 		$user = $this->login( $username, $password );
   5173 		if ( ! $user ) {
   5174 			return $this->error;
   5175 		}
   5176 
   5177 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5178 		do_action( 'xmlrpc_call', 'blogger.editPost', $args, $this );
   5179 
   5180 		$actual_post = get_post( $post_ID, ARRAY_A );
   5181 
   5182 		if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
   5183 			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
   5184 		}
   5185 
   5186 		$this->escape( $actual_post );
   5187 
   5188 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   5189 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   5190 		}
   5191 		if ( 'publish' === $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
   5192 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
   5193 		}
   5194 
   5195 		$postdata                  = array();
   5196 		$postdata['ID']            = $actual_post['ID'];
   5197 		$postdata['post_content']  = xmlrpc_removepostdata( $content );
   5198 		$postdata['post_title']    = xmlrpc_getposttitle( $content );
   5199 		$postdata['post_category'] = xmlrpc_getpostcategory( $content );
   5200 		$postdata['post_status']   = $actual_post['post_status'];
   5201 		$postdata['post_excerpt']  = $actual_post['post_excerpt'];
   5202 		$postdata['post_status']   = $publish ? 'publish' : 'draft';
   5203 
   5204 		$result = wp_update_post( $postdata );
   5205 
   5206 		if ( ! $result ) {
   5207 			return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
   5208 		}
   5209 		$this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
   5210 
   5211 		/**
   5212 		 * Fires after a post has been successfully updated via the XML-RPC Blogger API.
   5213 		 *
   5214 		 * @since 3.4.0
   5215 		 *
   5216 		 * @param int   $post_ID ID of the updated post.
   5217 		 * @param array $args    An array of arguments for the post to edit.
   5218 		 */
   5219 		do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   5220 
   5221 		return true;
   5222 	}
   5223 
   5224 	/**
   5225 	 * Remove a post.
   5226 	 *
   5227 	 * @since 1.5.0
   5228 	 *
   5229 	 * @param array $args {
   5230 	 *     Method arguments. Note: arguments must be ordered as documented.
   5231 	 *
   5232 	 *     @type int    $blog_id (unused)
   5233 	 *     @type int    $post_ID
   5234 	 *     @type string $username
   5235 	 *     @type string $password
   5236 	 * }
   5237 	 * @return true|IXR_Error True when post is deleted.
   5238 	 */
   5239 	public function blogger_deletePost( $args ) {
   5240 		$this->escape( $args );
   5241 
   5242 		$post_ID  = (int) $args[1];
   5243 		$username = $args[2];
   5244 		$password = $args[3];
   5245 
   5246 		$user = $this->login( $username, $password );
   5247 		if ( ! $user ) {
   5248 			return $this->error;
   5249 		}
   5250 
   5251 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5252 		do_action( 'xmlrpc_call', 'blogger.deletePost', $args, $this );
   5253 
   5254 		$actual_post = get_post( $post_ID, ARRAY_A );
   5255 
   5256 		if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
   5257 			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
   5258 		}
   5259 
   5260 		if ( ! current_user_can( 'delete_post', $post_ID ) ) {
   5261 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
   5262 		}
   5263 
   5264 		$result = wp_delete_post( $post_ID );
   5265 
   5266 		if ( ! $result ) {
   5267 			return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
   5268 		}
   5269 
   5270 		/**
   5271 		 * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
   5272 		 *
   5273 		 * @since 3.4.0
   5274 		 *
   5275 		 * @param int   $post_ID ID of the deleted post.
   5276 		 * @param array $args    An array of arguments to delete the post.
   5277 		 */
   5278 		do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   5279 
   5280 		return true;
   5281 	}
   5282 
   5283 	/*
   5284 	 * MetaWeblog API functions.
   5285 	 * Specs on wherever Dave Winer wants them to be.
   5286 	 */
   5287 
   5288 	/**
   5289 	 * Create a new post.
   5290 	 *
   5291 	 * The 'content_struct' argument must contain:
   5292 	 *  - title
   5293 	 *  - description
   5294 	 *  - mt_excerpt
   5295 	 *  - mt_text_more
   5296 	 *  - mt_keywords
   5297 	 *  - mt_tb_ping_urls
   5298 	 *  - categories
   5299 	 *
   5300 	 * Also, it can optionally contain:
   5301 	 *  - wp_slug
   5302 	 *  - wp_password
   5303 	 *  - wp_page_parent_id
   5304 	 *  - wp_page_order
   5305 	 *  - wp_author_id
   5306 	 *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
   5307 	 *  - mt_allow_comments - can be 'open' or 'closed'
   5308 	 *  - mt_allow_pings - can be 'open' or 'closed'
   5309 	 *  - date_created_gmt
   5310 	 *  - dateCreated
   5311 	 *  - wp_post_thumbnail
   5312 	 *
   5313 	 * @since 1.5.0
   5314 	 *
   5315 	 * @param array $args {
   5316 	 *     Method arguments. Note: arguments must be ordered as documented.
   5317 	 *
   5318 	 *     @type int    $blog_id (unused)
   5319 	 *     @type string $username
   5320 	 *     @type string $password
   5321 	 *     @type array  $content_struct
   5322 	 *     @type int    $publish
   5323 	 * }
   5324 	 * @return int|IXR_Error
   5325 	 */
   5326 	public function mw_newPost( $args ) {
   5327 		$this->escape( $args );
   5328 
   5329 		$username       = $args[1];
   5330 		$password       = $args[2];
   5331 		$content_struct = $args[3];
   5332 		$publish        = isset( $args[4] ) ? $args[4] : 0;
   5333 
   5334 		$user = $this->login( $username, $password );
   5335 		if ( ! $user ) {
   5336 			return $this->error;
   5337 		}
   5338 
   5339 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5340 		do_action( 'xmlrpc_call', 'metaWeblog.newPost', $args, $this );
   5341 
   5342 		$page_template = '';
   5343 		if ( ! empty( $content_struct['post_type'] ) ) {
   5344 			if ( 'page' === $content_struct['post_type'] ) {
   5345 				if ( $publish ) {
   5346 					$cap = 'publish_pages';
   5347 				} elseif ( isset( $content_struct['page_status'] ) && 'publish' === $content_struct['page_status'] ) {
   5348 					$cap = 'publish_pages';
   5349 				} else {
   5350 					$cap = 'edit_pages';
   5351 				}
   5352 				$error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
   5353 				$post_type     = 'page';
   5354 				if ( ! empty( $content_struct['wp_page_template'] ) ) {
   5355 					$page_template = $content_struct['wp_page_template'];
   5356 				}
   5357 			} elseif ( 'post' === $content_struct['post_type'] ) {
   5358 				if ( $publish ) {
   5359 					$cap = 'publish_posts';
   5360 				} elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
   5361 					$cap = 'publish_posts';
   5362 				} else {
   5363 					$cap = 'edit_posts';
   5364 				}
   5365 				$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
   5366 				$post_type     = 'post';
   5367 			} else {
   5368 				// No other 'post_type' values are allowed here.
   5369 				return new IXR_Error( 401, __( 'Invalid post type.' ) );
   5370 			}
   5371 		} else {
   5372 			if ( $publish ) {
   5373 				$cap = 'publish_posts';
   5374 			} elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
   5375 				$cap = 'publish_posts';
   5376 			} else {
   5377 				$cap = 'edit_posts';
   5378 			}
   5379 			$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
   5380 			$post_type     = 'post';
   5381 		}
   5382 
   5383 		if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) ) {
   5384 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
   5385 		}
   5386 		if ( ! current_user_can( $cap ) ) {
   5387 			return new IXR_Error( 401, $error_message );
   5388 		}
   5389 
   5390 		// Check for a valid post format if one was given.
   5391 		if ( isset( $content_struct['wp_post_format'] ) ) {
   5392 			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
   5393 			if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
   5394 				return new IXR_Error( 404, __( 'Invalid post format.' ) );
   5395 			}
   5396 		}
   5397 
   5398 		// Let WordPress generate the 'post_name' (slug) unless
   5399 		// one has been provided.
   5400 		$post_name = '';
   5401 		if ( isset( $content_struct['wp_slug'] ) ) {
   5402 			$post_name = $content_struct['wp_slug'];
   5403 		}
   5404 
   5405 		// Only use a password if one was given.
   5406 		if ( isset( $content_struct['wp_password'] ) ) {
   5407 			$post_password = $content_struct['wp_password'];
   5408 		} else {
   5409 			$post_password = '';
   5410 		}
   5411 
   5412 		// Only set a post parent if one was given.
   5413 		if ( isset( $content_struct['wp_page_parent_id'] ) ) {
   5414 			$post_parent = $content_struct['wp_page_parent_id'];
   5415 		} else {
   5416 			$post_parent = 0;
   5417 		}
   5418 
   5419 		// Only set the 'menu_order' if it was given.
   5420 		if ( isset( $content_struct['wp_page_order'] ) ) {
   5421 			$menu_order = $content_struct['wp_page_order'];
   5422 		} else {
   5423 			$menu_order = 0;
   5424 		}
   5425 
   5426 		$post_author = $user->ID;
   5427 
   5428 		// If an author id was provided then use it instead.
   5429 		if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
   5430 			switch ( $post_type ) {
   5431 				case 'post':
   5432 					if ( ! current_user_can( 'edit_others_posts' ) ) {
   5433 						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
   5434 					}
   5435 					break;
   5436 				case 'page':
   5437 					if ( ! current_user_can( 'edit_others_pages' ) ) {
   5438 						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
   5439 					}
   5440 					break;
   5441 				default:
   5442 					return new IXR_Error( 401, __( 'Invalid post type.' ) );
   5443 			}
   5444 			$author = get_userdata( $content_struct['wp_author_id'] );
   5445 			if ( ! $author ) {
   5446 				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
   5447 			}
   5448 			$post_author = $content_struct['wp_author_id'];
   5449 		}
   5450 
   5451 		$post_title   = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
   5452 		$post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
   5453 
   5454 		$post_status = $publish ? 'publish' : 'draft';
   5455 
   5456 		if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
   5457 			switch ( $content_struct[ "{$post_type}_status" ] ) {
   5458 				case 'draft':
   5459 				case 'pending':
   5460 				case 'private':
   5461 				case 'publish':
   5462 					$post_status = $content_struct[ "{$post_type}_status" ];
   5463 					break;
   5464 				default:
   5465 					$post_status = $publish ? 'publish' : 'draft';
   5466 					break;
   5467 			}
   5468 		}
   5469 
   5470 		$post_excerpt = isset( $content_struct['mt_excerpt'] ) ? $content_struct['mt_excerpt'] : null;
   5471 		$post_more    = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
   5472 
   5473 		$tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
   5474 
   5475 		if ( isset( $content_struct['mt_allow_comments'] ) ) {
   5476 			if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
   5477 				switch ( $content_struct['mt_allow_comments'] ) {
   5478 					case 'closed':
   5479 						$comment_status = 'closed';
   5480 						break;
   5481 					case 'open':
   5482 						$comment_status = 'open';
   5483 						break;
   5484 					default:
   5485 						$comment_status = get_default_comment_status( $post_type );
   5486 						break;
   5487 				}
   5488 			} else {
   5489 				switch ( (int) $content_struct['mt_allow_comments'] ) {
   5490 					case 0:
   5491 					case 2:
   5492 						$comment_status = 'closed';
   5493 						break;
   5494 					case 1:
   5495 						$comment_status = 'open';
   5496 						break;
   5497 					default:
   5498 						$comment_status = get_default_comment_status( $post_type );
   5499 						break;
   5500 				}
   5501 			}
   5502 		} else {
   5503 			$comment_status = get_default_comment_status( $post_type );
   5504 		}
   5505 
   5506 		if ( isset( $content_struct['mt_allow_pings'] ) ) {
   5507 			if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
   5508 				switch ( $content_struct['mt_allow_pings'] ) {
   5509 					case 'closed':
   5510 						$ping_status = 'closed';
   5511 						break;
   5512 					case 'open':
   5513 						$ping_status = 'open';
   5514 						break;
   5515 					default:
   5516 						$ping_status = get_default_comment_status( $post_type, 'pingback' );
   5517 						break;
   5518 				}
   5519 			} else {
   5520 				switch ( (int) $content_struct['mt_allow_pings'] ) {
   5521 					case 0:
   5522 						$ping_status = 'closed';
   5523 						break;
   5524 					case 1:
   5525 						$ping_status = 'open';
   5526 						break;
   5527 					default:
   5528 						$ping_status = get_default_comment_status( $post_type, 'pingback' );
   5529 						break;
   5530 				}
   5531 			}
   5532 		} else {
   5533 			$ping_status = get_default_comment_status( $post_type, 'pingback' );
   5534 		}
   5535 
   5536 		if ( $post_more ) {
   5537 			$post_content = $post_content . '<!--more-->' . $post_more;
   5538 		}
   5539 
   5540 		$to_ping = null;
   5541 		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
   5542 			$to_ping = $content_struct['mt_tb_ping_urls'];
   5543 			if ( is_array( $to_ping ) ) {
   5544 				$to_ping = implode( ' ', $to_ping );
   5545 			}
   5546 		}
   5547 
   5548 		// Do some timestamp voodoo.
   5549 		if ( ! empty( $content_struct['date_created_gmt'] ) ) {
   5550 			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
   5551 			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
   5552 		} elseif ( ! empty( $content_struct['dateCreated'] ) ) {
   5553 			$dateCreated = $content_struct['dateCreated']->getIso();
   5554 		}
   5555 
   5556 		if ( ! empty( $dateCreated ) ) {
   5557 			$post_date     = iso8601_to_datetime( $dateCreated );
   5558 			$post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
   5559 		} else {
   5560 			$post_date     = '';
   5561 			$post_date_gmt = '';
   5562 		}
   5563 
   5564 		$post_category = array();
   5565 		if ( isset( $content_struct['categories'] ) ) {
   5566 			$catnames = $content_struct['categories'];
   5567 
   5568 			if ( is_array( $catnames ) ) {
   5569 				foreach ( $catnames as $cat ) {
   5570 					$post_category[] = get_cat_ID( $cat );
   5571 				}
   5572 			}
   5573 		}
   5574 
   5575 		$postdata = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template' );
   5576 
   5577 		$post_ID        = get_default_post_to_edit( $post_type, true )->ID;
   5578 		$postdata['ID'] = $post_ID;
   5579 
   5580 		// Only posts can be sticky.
   5581 		if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
   5582 			$data           = $postdata;
   5583 			$data['sticky'] = $content_struct['sticky'];
   5584 			$error          = $this->_toggle_sticky( $data );
   5585 			if ( $error ) {
   5586 				return $error;
   5587 			}
   5588 		}
   5589 
   5590 		if ( isset( $content_struct['custom_fields'] ) ) {
   5591 			$this->set_custom_fields( $post_ID, $content_struct['custom_fields'] );
   5592 		}
   5593 
   5594 		if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
   5595 			if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false ) {
   5596 				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
   5597 			}
   5598 
   5599 			unset( $content_struct['wp_post_thumbnail'] );
   5600 		}
   5601 
   5602 		// Handle enclosures.
   5603 		$thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
   5604 		$this->add_enclosure_if_new( $post_ID, $thisEnclosure );
   5605 
   5606 		$this->attach_uploads( $post_ID, $post_content );
   5607 
   5608 		// Handle post formats if assigned, value is validated earlier
   5609 		// in this function.
   5610 		if ( isset( $content_struct['wp_post_format'] ) ) {
   5611 			set_post_format( $post_ID, $content_struct['wp_post_format'] );
   5612 		}
   5613 
   5614 		$post_ID = wp_insert_post( $postdata, true );
   5615 		if ( is_wp_error( $post_ID ) ) {
   5616 			return new IXR_Error( 500, $post_ID->get_error_message() );
   5617 		}
   5618 
   5619 		if ( ! $post_ID ) {
   5620 			return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
   5621 		}
   5622 
   5623 		/**
   5624 		 * Fires after a new post has been successfully created via the XML-RPC MovableType API.
   5625 		 *
   5626 		 * @since 3.4.0
   5627 		 *
   5628 		 * @param int   $post_ID ID of the new post.
   5629 		 * @param array $args    An array of arguments to create the new post.
   5630 		 */
   5631 		do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   5632 
   5633 		return (string) $post_ID;
   5634 	}
   5635 
   5636 	/**
   5637 	 * Adds an enclosure to a post if it's new.
   5638 	 *
   5639 	 * @since 2.8.0
   5640 	 *
   5641 	 * @param int   $post_ID   Post ID.
   5642 	 * @param array $enclosure Enclosure data.
   5643 	 */
   5644 	public function add_enclosure_if_new( $post_ID, $enclosure ) {
   5645 		if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
   5646 			$encstring  = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
   5647 			$found      = false;
   5648 			$enclosures = get_post_meta( $post_ID, 'enclosure' );
   5649 			if ( $enclosures ) {
   5650 				foreach ( $enclosures as $enc ) {
   5651 					// This method used to omit the trailing new line. #23219
   5652 					if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
   5653 						$found = true;
   5654 						break;
   5655 					}
   5656 				}
   5657 			}
   5658 			if ( ! $found ) {
   5659 				add_post_meta( $post_ID, 'enclosure', $encstring );
   5660 			}
   5661 		}
   5662 	}
   5663 
   5664 	/**
   5665 	 * Attach upload to a post.
   5666 	 *
   5667 	 * @since 2.1.0
   5668 	 *
   5669 	 * @global wpdb $wpdb WordPress database abstraction object.
   5670 	 *
   5671 	 * @param int    $post_ID      Post ID.
   5672 	 * @param string $post_content Post Content for attachment.
   5673 	 */
   5674 	public function attach_uploads( $post_ID, $post_content ) {
   5675 		global $wpdb;
   5676 
   5677 		// Find any unattached files.
   5678 		$attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
   5679 		if ( is_array( $attachments ) ) {
   5680 			foreach ( $attachments as $file ) {
   5681 				if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false ) {
   5682 					$wpdb->update( $wpdb->posts, array( 'post_parent' => $post_ID ), array( 'ID' => $file->ID ) );
   5683 				}
   5684 			}
   5685 		}
   5686 	}
   5687 
   5688 	/**
   5689 	 * Edit a post.
   5690 	 *
   5691 	 * @since 1.5.0
   5692 	 *
   5693 	 * @param array $args {
   5694 	 *     Method arguments. Note: arguments must be ordered as documented.
   5695 	 *
   5696 	 *     @type int    $blog_id (unused)
   5697 	 *     @type string $username
   5698 	 *     @type string $password
   5699 	 *     @type array  $content_struct
   5700 	 *     @type int    $publish
   5701 	 * }
   5702 	 * @return true|IXR_Error True on success.
   5703 	 */
   5704 	public function mw_editPost( $args ) {
   5705 		$this->escape( $args );
   5706 
   5707 		$post_ID        = (int) $args[0];
   5708 		$username       = $args[1];
   5709 		$password       = $args[2];
   5710 		$content_struct = $args[3];
   5711 		$publish        = isset( $args[4] ) ? $args[4] : 0;
   5712 
   5713 		$user = $this->login( $username, $password );
   5714 		if ( ! $user ) {
   5715 			return $this->error;
   5716 		}
   5717 
   5718 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   5719 		do_action( 'xmlrpc_call', 'metaWeblog.editPost', $args, $this );
   5720 
   5721 		$postdata = get_post( $post_ID, ARRAY_A );
   5722 
   5723 		/*
   5724 		 * If there is no post data for the give post ID, stop now and return an error.
   5725 		 * Otherwise a new post will be created (which was the old behavior).
   5726 		 */
   5727 		if ( ! $postdata || empty( $postdata['ID'] ) ) {
   5728 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   5729 		}
   5730 
   5731 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   5732 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   5733 		}
   5734 
   5735 		// Use wp.editPost to edit post types other than post and page.
   5736 		if ( ! in_array( $postdata['post_type'], array( 'post', 'page' ), true ) ) {
   5737 			return new IXR_Error( 401, __( 'Invalid post type.' ) );
   5738 		}
   5739 
   5740 		// Thwart attempt to change the post type.
   5741 		if ( ! empty( $content_struct['post_type'] ) && ( $content_struct['post_type'] != $postdata['post_type'] ) ) {
   5742 			return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
   5743 		}
   5744 
   5745 		// Check for a valid post format if one was given.
   5746 		if ( isset( $content_struct['wp_post_format'] ) ) {
   5747 			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
   5748 			if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
   5749 				return new IXR_Error( 404, __( 'Invalid post format.' ) );
   5750 			}
   5751 		}
   5752 
   5753 		$this->escape( $postdata );
   5754 
   5755 		$ID             = $postdata['ID'];
   5756 		$post_content   = $postdata['post_content'];
   5757 		$post_title     = $postdata['post_title'];
   5758 		$post_excerpt   = $postdata['post_excerpt'];
   5759 		$post_password  = $postdata['post_password'];
   5760 		$post_parent    = $postdata['post_parent'];
   5761 		$post_type      = $postdata['post_type'];
   5762 		$menu_order     = $postdata['menu_order'];
   5763 		$ping_status    = $postdata['ping_status'];
   5764 		$comment_status = $postdata['comment_status'];
   5765 
   5766 		// Let WordPress manage slug if none was provided.
   5767 		$post_name = $postdata['post_name'];
   5768 		if ( isset( $content_struct['wp_slug'] ) ) {
   5769 			$post_name = $content_struct['wp_slug'];
   5770 		}
   5771 
   5772 		// Only use a password if one was given.
   5773 		if ( isset( $content_struct['wp_password'] ) ) {
   5774 			$post_password = $content_struct['wp_password'];
   5775 		}
   5776 
   5777 		// Only set a post parent if one was given.
   5778 		if ( isset( $content_struct['wp_page_parent_id'] ) ) {
   5779 			$post_parent = $content_struct['wp_page_parent_id'];
   5780 		}
   5781 
   5782 		// Only set the 'menu_order' if it was given.
   5783 		if ( isset( $content_struct['wp_page_order'] ) ) {
   5784 			$menu_order = $content_struct['wp_page_order'];
   5785 		}
   5786 
   5787 		$page_template = null;
   5788 		if ( ! empty( $content_struct['wp_page_template'] ) && 'page' === $post_type ) {
   5789 			$page_template = $content_struct['wp_page_template'];
   5790 		}
   5791 
   5792 		$post_author = $postdata['post_author'];
   5793 
   5794 		// If an author id was provided then use it instead.
   5795 		if ( isset( $content_struct['wp_author_id'] ) ) {
   5796 			// Check permissions if attempting to switch author to or from another user.
   5797 			if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
   5798 				switch ( $post_type ) {
   5799 					case 'post':
   5800 						if ( ! current_user_can( 'edit_others_posts' ) ) {
   5801 							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
   5802 						}
   5803 						break;
   5804 					case 'page':
   5805 						if ( ! current_user_can( 'edit_others_pages' ) ) {
   5806 							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
   5807 						}
   5808 						break;
   5809 					default:
   5810 						return new IXR_Error( 401, __( 'Invalid post type.' ) );
   5811 				}
   5812 				$post_author = $content_struct['wp_author_id'];
   5813 			}
   5814 		}
   5815 
   5816 		if ( isset( $content_struct['mt_allow_comments'] ) ) {
   5817 			if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
   5818 				switch ( $content_struct['mt_allow_comments'] ) {
   5819 					case 'closed':
   5820 						$comment_status = 'closed';
   5821 						break;
   5822 					case 'open':
   5823 						$comment_status = 'open';
   5824 						break;
   5825 					default:
   5826 						$comment_status = get_default_comment_status( $post_type );
   5827 						break;
   5828 				}
   5829 			} else {
   5830 				switch ( (int) $content_struct['mt_allow_comments'] ) {
   5831 					case 0:
   5832 					case 2:
   5833 						$comment_status = 'closed';
   5834 						break;
   5835 					case 1:
   5836 						$comment_status = 'open';
   5837 						break;
   5838 					default:
   5839 						$comment_status = get_default_comment_status( $post_type );
   5840 						break;
   5841 				}
   5842 			}
   5843 		}
   5844 
   5845 		if ( isset( $content_struct['mt_allow_pings'] ) ) {
   5846 			if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
   5847 				switch ( $content_struct['mt_allow_pings'] ) {
   5848 					case 'closed':
   5849 						$ping_status = 'closed';
   5850 						break;
   5851 					case 'open':
   5852 						$ping_status = 'open';
   5853 						break;
   5854 					default:
   5855 						$ping_status = get_default_comment_status( $post_type, 'pingback' );
   5856 						break;
   5857 				}
   5858 			} else {
   5859 				switch ( (int) $content_struct['mt_allow_pings'] ) {
   5860 					case 0:
   5861 						$ping_status = 'closed';
   5862 						break;
   5863 					case 1:
   5864 						$ping_status = 'open';
   5865 						break;
   5866 					default:
   5867 						$ping_status = get_default_comment_status( $post_type, 'pingback' );
   5868 						break;
   5869 				}
   5870 			}
   5871 		}
   5872 
   5873 		if ( isset( $content_struct['title'] ) ) {
   5874 			$post_title = $content_struct['title'];
   5875 		}
   5876 
   5877 		if ( isset( $content_struct['description'] ) ) {
   5878 			$post_content = $content_struct['description'];
   5879 		}
   5880 
   5881 		$post_category = array();
   5882 		if ( isset( $content_struct['categories'] ) ) {
   5883 			$catnames = $content_struct['categories'];
   5884 			if ( is_array( $catnames ) ) {
   5885 				foreach ( $catnames as $cat ) {
   5886 					$post_category[] = get_cat_ID( $cat );
   5887 				}
   5888 			}
   5889 		}
   5890 
   5891 		if ( isset( $content_struct['mt_excerpt'] ) ) {
   5892 			$post_excerpt = $content_struct['mt_excerpt'];
   5893 		}
   5894 
   5895 		$post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
   5896 
   5897 		$post_status = $publish ? 'publish' : 'draft';
   5898 		if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
   5899 			switch ( $content_struct[ "{$post_type}_status" ] ) {
   5900 				case 'draft':
   5901 				case 'pending':
   5902 				case 'private':
   5903 				case 'publish':
   5904 					$post_status = $content_struct[ "{$post_type}_status" ];
   5905 					break;
   5906 				default:
   5907 					$post_status = $publish ? 'publish' : 'draft';
   5908 					break;
   5909 			}
   5910 		}
   5911 
   5912 		$tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
   5913 
   5914 		if ( 'publish' === $post_status || 'private' === $post_status ) {
   5915 			if ( 'page' === $post_type && ! current_user_can( 'publish_pages' ) ) {
   5916 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
   5917 			} elseif ( ! current_user_can( 'publish_posts' ) ) {
   5918 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
   5919 			}
   5920 		}
   5921 
   5922 		if ( $post_more ) {
   5923 			$post_content = $post_content . '<!--more-->' . $post_more;
   5924 		}
   5925 
   5926 		$to_ping = null;
   5927 		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
   5928 			$to_ping = $content_struct['mt_tb_ping_urls'];
   5929 			if ( is_array( $to_ping ) ) {
   5930 				$to_ping = implode( ' ', $to_ping );
   5931 			}
   5932 		}
   5933 
   5934 		// Do some timestamp voodoo.
   5935 		if ( ! empty( $content_struct['date_created_gmt'] ) ) {
   5936 			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
   5937 			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
   5938 		} elseif ( ! empty( $content_struct['dateCreated'] ) ) {
   5939 			$dateCreated = $content_struct['dateCreated']->getIso();
   5940 		}
   5941 
   5942 		// Default to not flagging the post date to be edited unless it's intentional.
   5943 		$edit_date = false;
   5944 
   5945 		if ( ! empty( $dateCreated ) ) {
   5946 			$post_date     = iso8601_to_datetime( $dateCreated );
   5947 			$post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
   5948 
   5949 			// Flag the post date to be edited.
   5950 			$edit_date = true;
   5951 		} else {
   5952 			$post_date     = $postdata['post_date'];
   5953 			$post_date_gmt = $postdata['post_date_gmt'];
   5954 		}
   5955 
   5956 		// We've got all the data -- post it.
   5957 		$newpost = compact( 'ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template' );
   5958 
   5959 		$result = wp_update_post( $newpost, true );
   5960 		if ( is_wp_error( $result ) ) {
   5961 			return new IXR_Error( 500, $result->get_error_message() );
   5962 		}
   5963 
   5964 		if ( ! $result ) {
   5965 			return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
   5966 		}
   5967 
   5968 		// Only posts can be sticky.
   5969 		if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
   5970 			$data              = $newpost;
   5971 			$data['sticky']    = $content_struct['sticky'];
   5972 			$data['post_type'] = 'post';
   5973 			$error             = $this->_toggle_sticky( $data, true );
   5974 			if ( $error ) {
   5975 				return $error;
   5976 			}
   5977 		}
   5978 
   5979 		if ( isset( $content_struct['custom_fields'] ) ) {
   5980 			$this->set_custom_fields( $post_ID, $content_struct['custom_fields'] );
   5981 		}
   5982 
   5983 		if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
   5984 
   5985 			// Empty value deletes, non-empty value adds/updates.
   5986 			if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
   5987 				delete_post_thumbnail( $post_ID );
   5988 			} else {
   5989 				if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false ) {
   5990 					return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
   5991 				}
   5992 			}
   5993 			unset( $content_struct['wp_post_thumbnail'] );
   5994 		}
   5995 
   5996 		// Handle enclosures.
   5997 		$thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
   5998 		$this->add_enclosure_if_new( $post_ID, $thisEnclosure );
   5999 
   6000 		$this->attach_uploads( $ID, $post_content );
   6001 
   6002 		// Handle post formats if assigned, validation is handled earlier in this function.
   6003 		if ( isset( $content_struct['wp_post_format'] ) ) {
   6004 			set_post_format( $post_ID, $content_struct['wp_post_format'] );
   6005 		}
   6006 
   6007 		/**
   6008 		 * Fires after a post has been successfully updated via the XML-RPC MovableType API.
   6009 		 *
   6010 		 * @since 3.4.0
   6011 		 *
   6012 		 * @param int   $post_ID ID of the updated post.
   6013 		 * @param array $args    An array of arguments to update the post.
   6014 		 */
   6015 		do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   6016 
   6017 		return true;
   6018 	}
   6019 
   6020 	/**
   6021 	 * Retrieve post.
   6022 	 *
   6023 	 * @since 1.5.0
   6024 	 *
   6025 	 * @param array $args {
   6026 	 *     Method arguments. Note: arguments must be ordered as documented.
   6027 	 *
   6028 	 *     @type int    $blog_id (unused)
   6029 	 *     @type int    $post_ID
   6030 	 *     @type string $username
   6031 	 *     @type string $password
   6032 	 * }
   6033 	 * @return array|IXR_Error
   6034 	 */
   6035 	public function mw_getPost( $args ) {
   6036 		$this->escape( $args );
   6037 
   6038 		$post_ID  = (int) $args[0];
   6039 		$username = $args[1];
   6040 		$password = $args[2];
   6041 
   6042 		$user = $this->login( $username, $password );
   6043 		if ( ! $user ) {
   6044 			return $this->error;
   6045 		}
   6046 
   6047 		$postdata = get_post( $post_ID, ARRAY_A );
   6048 		if ( ! $postdata ) {
   6049 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   6050 		}
   6051 
   6052 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   6053 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   6054 		}
   6055 
   6056 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6057 		do_action( 'xmlrpc_call', 'metaWeblog.getPost', $args, $this );
   6058 
   6059 		if ( '' !== $postdata['post_date'] ) {
   6060 			$post_date         = $this->_convert_date( $postdata['post_date'] );
   6061 			$post_date_gmt     = $this->_convert_date_gmt( $postdata['post_date_gmt'], $postdata['post_date'] );
   6062 			$post_modified     = $this->_convert_date( $postdata['post_modified'] );
   6063 			$post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
   6064 
   6065 			$categories = array();
   6066 			$catids     = wp_get_post_categories( $post_ID );
   6067 			foreach ( $catids as $catid ) {
   6068 				$categories[] = get_cat_name( $catid );
   6069 			}
   6070 
   6071 			$tagnames = array();
   6072 			$tags     = wp_get_post_tags( $post_ID );
   6073 			if ( ! empty( $tags ) ) {
   6074 				foreach ( $tags as $tag ) {
   6075 					$tagnames[] = $tag->name;
   6076 				}
   6077 				$tagnames = implode( ', ', $tagnames );
   6078 			} else {
   6079 				$tagnames = '';
   6080 			}
   6081 
   6082 			$post = get_extended( $postdata['post_content'] );
   6083 			$link = get_permalink( $postdata['ID'] );
   6084 
   6085 			// Get the author info.
   6086 			$author = get_userdata( $postdata['post_author'] );
   6087 
   6088 			$allow_comments = ( 'open' === $postdata['comment_status'] ) ? 1 : 0;
   6089 			$allow_pings    = ( 'open' === $postdata['ping_status'] ) ? 1 : 0;
   6090 
   6091 			// Consider future posts as published.
   6092 			if ( 'future' === $postdata['post_status'] ) {
   6093 				$postdata['post_status'] = 'publish';
   6094 			}
   6095 
   6096 			// Get post format.
   6097 			$post_format = get_post_format( $post_ID );
   6098 			if ( empty( $post_format ) ) {
   6099 				$post_format = 'standard';
   6100 			}
   6101 
   6102 			$sticky = false;
   6103 			if ( is_sticky( $post_ID ) ) {
   6104 				$sticky = true;
   6105 			}
   6106 
   6107 			$enclosure = array();
   6108 			foreach ( (array) get_post_custom( $post_ID ) as $key => $val ) {
   6109 				if ( 'enclosure' === $key ) {
   6110 					foreach ( (array) $val as $enc ) {
   6111 						$encdata             = explode( "\n", $enc );
   6112 						$enclosure['url']    = trim( htmlspecialchars( $encdata[0] ) );
   6113 						$enclosure['length'] = (int) trim( $encdata[1] );
   6114 						$enclosure['type']   = trim( $encdata[2] );
   6115 						break 2;
   6116 					}
   6117 				}
   6118 			}
   6119 
   6120 			$resp = array(
   6121 				'dateCreated'            => $post_date,
   6122 				'userid'                 => $postdata['post_author'],
   6123 				'postid'                 => $postdata['ID'],
   6124 				'description'            => $post['main'],
   6125 				'title'                  => $postdata['post_title'],
   6126 				'link'                   => $link,
   6127 				'permaLink'              => $link,
   6128 				// Commented out because no other tool seems to use this.
   6129 				// 'content' => $entry['post_content'],
   6130 				'categories'             => $categories,
   6131 				'mt_excerpt'             => $postdata['post_excerpt'],
   6132 				'mt_text_more'           => $post['extended'],
   6133 				'wp_more_text'           => $post['more_text'],
   6134 				'mt_allow_comments'      => $allow_comments,
   6135 				'mt_allow_pings'         => $allow_pings,
   6136 				'mt_keywords'            => $tagnames,
   6137 				'wp_slug'                => $postdata['post_name'],
   6138 				'wp_password'            => $postdata['post_password'],
   6139 				'wp_author_id'           => (string) $author->ID,
   6140 				'wp_author_display_name' => $author->display_name,
   6141 				'date_created_gmt'       => $post_date_gmt,
   6142 				'post_status'            => $postdata['post_status'],
   6143 				'custom_fields'          => $this->get_custom_fields( $post_ID ),
   6144 				'wp_post_format'         => $post_format,
   6145 				'sticky'                 => $sticky,
   6146 				'date_modified'          => $post_modified,
   6147 				'date_modified_gmt'      => $post_modified_gmt,
   6148 			);
   6149 
   6150 			if ( ! empty( $enclosure ) ) {
   6151 				$resp['enclosure'] = $enclosure;
   6152 			}
   6153 
   6154 			$resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
   6155 
   6156 			return $resp;
   6157 		} else {
   6158 			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
   6159 		}
   6160 	}
   6161 
   6162 	/**
   6163 	 * Retrieve list of recent posts.
   6164 	 *
   6165 	 * @since 1.5.0
   6166 	 *
   6167 	 * @param array $args {
   6168 	 *     Method arguments. Note: arguments must be ordered as documented.
   6169 	 *
   6170 	 *     @type int    $blog_id (unused)
   6171 	 *     @type string $username
   6172 	 *     @type string $password
   6173 	 *     @type int    $numberposts
   6174 	 * }
   6175 	 * @return array|IXR_Error
   6176 	 */
   6177 	public function mw_getRecentPosts( $args ) {
   6178 		$this->escape( $args );
   6179 
   6180 		$username = $args[1];
   6181 		$password = $args[2];
   6182 		if ( isset( $args[3] ) ) {
   6183 			$query = array( 'numberposts' => absint( $args[3] ) );
   6184 		} else {
   6185 			$query = array();
   6186 		}
   6187 
   6188 		$user = $this->login( $username, $password );
   6189 		if ( ! $user ) {
   6190 			return $this->error;
   6191 		}
   6192 
   6193 		if ( ! current_user_can( 'edit_posts' ) ) {
   6194 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
   6195 		}
   6196 
   6197 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6198 		do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts', $args, $this );
   6199 
   6200 		$posts_list = wp_get_recent_posts( $query );
   6201 
   6202 		if ( ! $posts_list ) {
   6203 			return array();
   6204 		}
   6205 
   6206 		$recent_posts = array();
   6207 		foreach ( $posts_list as $entry ) {
   6208 			if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
   6209 				continue;
   6210 			}
   6211 
   6212 			$post_date         = $this->_convert_date( $entry['post_date'] );
   6213 			$post_date_gmt     = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
   6214 			$post_modified     = $this->_convert_date( $entry['post_modified'] );
   6215 			$post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
   6216 
   6217 			$categories = array();
   6218 			$catids     = wp_get_post_categories( $entry['ID'] );
   6219 			foreach ( $catids as $catid ) {
   6220 				$categories[] = get_cat_name( $catid );
   6221 			}
   6222 
   6223 			$tagnames = array();
   6224 			$tags     = wp_get_post_tags( $entry['ID'] );
   6225 			if ( ! empty( $tags ) ) {
   6226 				foreach ( $tags as $tag ) {
   6227 					$tagnames[] = $tag->name;
   6228 				}
   6229 				$tagnames = implode( ', ', $tagnames );
   6230 			} else {
   6231 				$tagnames = '';
   6232 			}
   6233 
   6234 			$post = get_extended( $entry['post_content'] );
   6235 			$link = get_permalink( $entry['ID'] );
   6236 
   6237 			// Get the post author info.
   6238 			$author = get_userdata( $entry['post_author'] );
   6239 
   6240 			$allow_comments = ( 'open' === $entry['comment_status'] ) ? 1 : 0;
   6241 			$allow_pings    = ( 'open' === $entry['ping_status'] ) ? 1 : 0;
   6242 
   6243 			// Consider future posts as published.
   6244 			if ( 'future' === $entry['post_status'] ) {
   6245 				$entry['post_status'] = 'publish';
   6246 			}
   6247 
   6248 			// Get post format.
   6249 			$post_format = get_post_format( $entry['ID'] );
   6250 			if ( empty( $post_format ) ) {
   6251 				$post_format = 'standard';
   6252 			}
   6253 
   6254 			$recent_posts[] = array(
   6255 				'dateCreated'            => $post_date,
   6256 				'userid'                 => $entry['post_author'],
   6257 				'postid'                 => (string) $entry['ID'],
   6258 				'description'            => $post['main'],
   6259 				'title'                  => $entry['post_title'],
   6260 				'link'                   => $link,
   6261 				'permaLink'              => $link,
   6262 				// Commented out because no other tool seems to use this.
   6263 				// 'content' => $entry['post_content'],
   6264 				'categories'             => $categories,
   6265 				'mt_excerpt'             => $entry['post_excerpt'],
   6266 				'mt_text_more'           => $post['extended'],
   6267 				'wp_more_text'           => $post['more_text'],
   6268 				'mt_allow_comments'      => $allow_comments,
   6269 				'mt_allow_pings'         => $allow_pings,
   6270 				'mt_keywords'            => $tagnames,
   6271 				'wp_slug'                => $entry['post_name'],
   6272 				'wp_password'            => $entry['post_password'],
   6273 				'wp_author_id'           => (string) $author->ID,
   6274 				'wp_author_display_name' => $author->display_name,
   6275 				'date_created_gmt'       => $post_date_gmt,
   6276 				'post_status'            => $entry['post_status'],
   6277 				'custom_fields'          => $this->get_custom_fields( $entry['ID'] ),
   6278 				'wp_post_format'         => $post_format,
   6279 				'date_modified'          => $post_modified,
   6280 				'date_modified_gmt'      => $post_modified_gmt,
   6281 				'sticky'                 => ( 'post' === $entry['post_type'] && is_sticky( $entry['ID'] ) ),
   6282 				'wp_post_thumbnail'      => get_post_thumbnail_id( $entry['ID'] ),
   6283 			);
   6284 		}
   6285 
   6286 		return $recent_posts;
   6287 	}
   6288 
   6289 	/**
   6290 	 * Retrieve the list of categories on a given blog.
   6291 	 *
   6292 	 * @since 1.5.0
   6293 	 *
   6294 	 * @param array $args {
   6295 	 *     Method arguments. Note: arguments must be ordered as documented.
   6296 	 *
   6297 	 *     @type int    $blog_id (unused)
   6298 	 *     @type string $username
   6299 	 *     @type string $password
   6300 	 * }
   6301 	 * @return array|IXR_Error
   6302 	 */
   6303 	public function mw_getCategories( $args ) {
   6304 		$this->escape( $args );
   6305 
   6306 		$username = $args[1];
   6307 		$password = $args[2];
   6308 
   6309 		$user = $this->login( $username, $password );
   6310 		if ( ! $user ) {
   6311 			return $this->error;
   6312 		}
   6313 
   6314 		if ( ! current_user_can( 'edit_posts' ) ) {
   6315 			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
   6316 		}
   6317 
   6318 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6319 		do_action( 'xmlrpc_call', 'metaWeblog.getCategories', $args, $this );
   6320 
   6321 		$categories_struct = array();
   6322 
   6323 		$cats = get_categories( array( 'get' => 'all' ) );
   6324 		if ( $cats ) {
   6325 			foreach ( $cats as $cat ) {
   6326 				$struct                        = array();
   6327 				$struct['categoryId']          = $cat->term_id;
   6328 				$struct['parentId']            = $cat->parent;
   6329 				$struct['description']         = $cat->name;
   6330 				$struct['categoryDescription'] = $cat->description;
   6331 				$struct['categoryName']        = $cat->name;
   6332 				$struct['htmlUrl']             = esc_html( get_category_link( $cat->term_id ) );
   6333 				$struct['rssUrl']              = esc_html( get_category_feed_link( $cat->term_id, 'rss2' ) );
   6334 
   6335 				$categories_struct[] = $struct;
   6336 			}
   6337 		}
   6338 
   6339 		return $categories_struct;
   6340 	}
   6341 
   6342 	/**
   6343 	 * Uploads a file, following your settings.
   6344 	 *
   6345 	 * Adapted from a patch by Johann Richard.
   6346 	 *
   6347 	 * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
   6348 	 *
   6349 	 * @since 1.5.0
   6350 	 *
   6351 	 * @global wpdb $wpdb WordPress database abstraction object.
   6352 	 *
   6353 	 * @param array $args {
   6354 	 *     Method arguments. Note: arguments must be ordered as documented.
   6355 	 *
   6356 	 *     @type int    $blog_id (unused)
   6357 	 *     @type string $username
   6358 	 *     @type string $password
   6359 	 *     @type array  $data
   6360 	 * }
   6361 	 * @return array|IXR_Error
   6362 	 */
   6363 	public function mw_newMediaObject( $args ) {
   6364 		global $wpdb;
   6365 
   6366 		$username = $this->escape( $args[1] );
   6367 		$password = $this->escape( $args[2] );
   6368 		$data     = $args[3];
   6369 
   6370 		$name = sanitize_file_name( $data['name'] );
   6371 		$type = $data['type'];
   6372 		$bits = $data['bits'];
   6373 
   6374 		$user = $this->login( $username, $password );
   6375 		if ( ! $user ) {
   6376 			return $this->error;
   6377 		}
   6378 
   6379 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6380 		do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject', $args, $this );
   6381 
   6382 		if ( ! current_user_can( 'upload_files' ) ) {
   6383 			$this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
   6384 			return $this->error;
   6385 		}
   6386 
   6387 		if ( is_multisite() && upload_is_user_over_quota( false ) ) {
   6388 			$this->error = new IXR_Error(
   6389 				401,
   6390 				sprintf(
   6391 					/* translators: %s: Allowed space allocation. */
   6392 					__( 'Sorry, you have used your space allocation of %s. Please delete some files to upload more files.' ),
   6393 					size_format( get_space_allowed() * MB_IN_BYTES )
   6394 				)
   6395 			);
   6396 			return $this->error;
   6397 		}
   6398 
   6399 		/**
   6400 		 * Filters whether to preempt the XML-RPC media upload.
   6401 		 *
   6402 		 * Passing a truthy value will effectively short-circuit the media upload,
   6403 		 * returning that value as a 500 error instead.
   6404 		 *
   6405 		 * @since 2.1.0
   6406 		 *
   6407 		 * @param bool $error Whether to pre-empt the media upload. Default false.
   6408 		 */
   6409 		$upload_err = apply_filters( 'pre_upload_error', false );
   6410 		if ( $upload_err ) {
   6411 			return new IXR_Error( 500, $upload_err );
   6412 		}
   6413 
   6414 		$upload = wp_upload_bits( $name, null, $bits );
   6415 		if ( ! empty( $upload['error'] ) ) {
   6416 			/* translators: 1: File name, 2: Error message. */
   6417 			$errorString = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] );
   6418 			return new IXR_Error( 500, $errorString );
   6419 		}
   6420 		// Construct the attachment array.
   6421 		$post_id = 0;
   6422 		if ( ! empty( $data['post_id'] ) ) {
   6423 			$post_id = (int) $data['post_id'];
   6424 
   6425 			if ( ! current_user_can( 'edit_post', $post_id ) ) {
   6426 				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   6427 			}
   6428 		}
   6429 		$attachment = array(
   6430 			'post_title'     => $name,
   6431 			'post_content'   => '',
   6432 			'post_type'      => 'attachment',
   6433 			'post_parent'    => $post_id,
   6434 			'post_mime_type' => $type,
   6435 			'guid'           => $upload['url'],
   6436 		);
   6437 
   6438 		// Save the data.
   6439 		$id = wp_insert_attachment( $attachment, $upload['file'], $post_id );
   6440 		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
   6441 
   6442 		/**
   6443 		 * Fires after a new attachment has been added via the XML-RPC MovableType API.
   6444 		 *
   6445 		 * @since 3.4.0
   6446 		 *
   6447 		 * @param int   $id   ID of the new attachment.
   6448 		 * @param array $args An array of arguments to add the attachment.
   6449 		 */
   6450 		do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
   6451 
   6452 		$struct = $this->_prepare_media_item( get_post( $id ) );
   6453 
   6454 		// Deprecated values.
   6455 		$struct['id']   = $struct['attachment_id'];
   6456 		$struct['file'] = $struct['title'];
   6457 		$struct['url']  = $struct['link'];
   6458 
   6459 		return $struct;
   6460 	}
   6461 
   6462 	/*
   6463 	 * MovableType API functions.
   6464 	 * Specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
   6465 	 */
   6466 
   6467 	/**
   6468 	 * Retrieve the post titles of recent posts.
   6469 	 *
   6470 	 * @since 1.5.0
   6471 	 *
   6472 	 * @param array $args {
   6473 	 *     Method arguments. Note: arguments must be ordered as documented.
   6474 	 *
   6475 	 *     @type int    $blog_id (unused)
   6476 	 *     @type string $username
   6477 	 *     @type string $password
   6478 	 *     @type int    $numberposts
   6479 	 * }
   6480 	 * @return array|IXR_Error
   6481 	 */
   6482 	public function mt_getRecentPostTitles( $args ) {
   6483 		$this->escape( $args );
   6484 
   6485 		$username = $args[1];
   6486 		$password = $args[2];
   6487 		if ( isset( $args[3] ) ) {
   6488 			$query = array( 'numberposts' => absint( $args[3] ) );
   6489 		} else {
   6490 			$query = array();
   6491 		}
   6492 
   6493 		$user = $this->login( $username, $password );
   6494 		if ( ! $user ) {
   6495 			return $this->error;
   6496 		}
   6497 
   6498 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6499 		do_action( 'xmlrpc_call', 'mt.getRecentPostTitles', $args, $this );
   6500 
   6501 		$posts_list = wp_get_recent_posts( $query );
   6502 
   6503 		if ( ! $posts_list ) {
   6504 			$this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
   6505 			return $this->error;
   6506 		}
   6507 
   6508 		$recent_posts = array();
   6509 
   6510 		foreach ( $posts_list as $entry ) {
   6511 			if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
   6512 				continue;
   6513 			}
   6514 
   6515 			$post_date     = $this->_convert_date( $entry['post_date'] );
   6516 			$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
   6517 
   6518 			$recent_posts[] = array(
   6519 				'dateCreated'      => $post_date,
   6520 				'userid'           => $entry['post_author'],
   6521 				'postid'           => (string) $entry['ID'],
   6522 				'title'            => $entry['post_title'],
   6523 				'post_status'      => $entry['post_status'],
   6524 				'date_created_gmt' => $post_date_gmt,
   6525 			);
   6526 		}
   6527 
   6528 		return $recent_posts;
   6529 	}
   6530 
   6531 	/**
   6532 	 * Retrieve list of all categories on blog.
   6533 	 *
   6534 	 * @since 1.5.0
   6535 	 *
   6536 	 * @param array $args {
   6537 	 *     Method arguments. Note: arguments must be ordered as documented.
   6538 	 *
   6539 	 *     @type int    $blog_id (unused)
   6540 	 *     @type string $username
   6541 	 *     @type string $password
   6542 	 * }
   6543 	 * @return array|IXR_Error
   6544 	 */
   6545 	public function mt_getCategoryList( $args ) {
   6546 		$this->escape( $args );
   6547 
   6548 		$username = $args[1];
   6549 		$password = $args[2];
   6550 
   6551 		$user = $this->login( $username, $password );
   6552 		if ( ! $user ) {
   6553 			return $this->error;
   6554 		}
   6555 
   6556 		if ( ! current_user_can( 'edit_posts' ) ) {
   6557 			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
   6558 		}
   6559 
   6560 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6561 		do_action( 'xmlrpc_call', 'mt.getCategoryList', $args, $this );
   6562 
   6563 		$categories_struct = array();
   6564 
   6565 		$cats = get_categories(
   6566 			array(
   6567 				'hide_empty'   => 0,
   6568 				'hierarchical' => 0,
   6569 			)
   6570 		);
   6571 		if ( $cats ) {
   6572 			foreach ( $cats as $cat ) {
   6573 				$struct                 = array();
   6574 				$struct['categoryId']   = $cat->term_id;
   6575 				$struct['categoryName'] = $cat->name;
   6576 
   6577 				$categories_struct[] = $struct;
   6578 			}
   6579 		}
   6580 
   6581 		return $categories_struct;
   6582 	}
   6583 
   6584 	/**
   6585 	 * Retrieve post categories.
   6586 	 *
   6587 	 * @since 1.5.0
   6588 	 *
   6589 	 * @param array $args {
   6590 	 *     Method arguments. Note: arguments must be ordered as documented.
   6591 	 *
   6592 	 *     @type int    $post_ID
   6593 	 *     @type string $username
   6594 	 *     @type string $password
   6595 	 * }
   6596 	 * @return array|IXR_Error
   6597 	 */
   6598 	public function mt_getPostCategories( $args ) {
   6599 		$this->escape( $args );
   6600 
   6601 		$post_ID  = (int) $args[0];
   6602 		$username = $args[1];
   6603 		$password = $args[2];
   6604 
   6605 		$user = $this->login( $username, $password );
   6606 		if ( ! $user ) {
   6607 			return $this->error;
   6608 		}
   6609 
   6610 		if ( ! get_post( $post_ID ) ) {
   6611 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   6612 		}
   6613 
   6614 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   6615 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   6616 		}
   6617 
   6618 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6619 		do_action( 'xmlrpc_call', 'mt.getPostCategories', $args, $this );
   6620 
   6621 		$categories = array();
   6622 		$catids     = wp_get_post_categories( (int) $post_ID );
   6623 		// First listed category will be the primary category.
   6624 		$isPrimary = true;
   6625 		foreach ( $catids as $catid ) {
   6626 			$categories[] = array(
   6627 				'categoryName' => get_cat_name( $catid ),
   6628 				'categoryId'   => (string) $catid,
   6629 				'isPrimary'    => $isPrimary,
   6630 			);
   6631 			$isPrimary    = false;
   6632 		}
   6633 
   6634 		return $categories;
   6635 	}
   6636 
   6637 	/**
   6638 	 * Sets categories for a post.
   6639 	 *
   6640 	 * @since 1.5.0
   6641 	 *
   6642 	 * @param array $args {
   6643 	 *     Method arguments. Note: arguments must be ordered as documented.
   6644 	 *
   6645 	 *     @type int    $post_ID
   6646 	 *     @type string $username
   6647 	 *     @type string $password
   6648 	 *     @type array  $categories
   6649 	 * }
   6650 	 * @return true|IXR_Error True on success.
   6651 	 */
   6652 	public function mt_setPostCategories( $args ) {
   6653 		$this->escape( $args );
   6654 
   6655 		$post_ID    = (int) $args[0];
   6656 		$username   = $args[1];
   6657 		$password   = $args[2];
   6658 		$categories = $args[3];
   6659 
   6660 		$user = $this->login( $username, $password );
   6661 		if ( ! $user ) {
   6662 			return $this->error;
   6663 		}
   6664 
   6665 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6666 		do_action( 'xmlrpc_call', 'mt.setPostCategories', $args, $this );
   6667 
   6668 		if ( ! get_post( $post_ID ) ) {
   6669 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   6670 		}
   6671 
   6672 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   6673 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
   6674 		}
   6675 
   6676 		$catids = array();
   6677 		foreach ( $categories as $cat ) {
   6678 			$catids[] = $cat['categoryId'];
   6679 		}
   6680 
   6681 		wp_set_post_categories( $post_ID, $catids );
   6682 
   6683 		return true;
   6684 	}
   6685 
   6686 	/**
   6687 	 * Retrieve an array of methods supported by this server.
   6688 	 *
   6689 	 * @since 1.5.0
   6690 	 *
   6691 	 * @return array
   6692 	 */
   6693 	public function mt_supportedMethods() {
   6694 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6695 		do_action( 'xmlrpc_call', 'mt.supportedMethods', array(), $this );
   6696 
   6697 		return array_keys( $this->methods );
   6698 	}
   6699 
   6700 	/**
   6701 	 * Retrieve an empty array because we don't support per-post text filters.
   6702 	 *
   6703 	 * @since 1.5.0
   6704 	 */
   6705 	public function mt_supportedTextFilters() {
   6706 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6707 		do_action( 'xmlrpc_call', 'mt.supportedTextFilters', array(), $this );
   6708 
   6709 		/**
   6710 		 * Filters the MoveableType text filters list for XML-RPC.
   6711 		 *
   6712 		 * @since 2.2.0
   6713 		 *
   6714 		 * @param array $filters An array of text filters.
   6715 		 */
   6716 		return apply_filters( 'xmlrpc_text_filters', array() );
   6717 	}
   6718 
   6719 	/**
   6720 	 * Retrieve trackbacks sent to a given post.
   6721 	 *
   6722 	 * @since 1.5.0
   6723 	 *
   6724 	 * @global wpdb $wpdb WordPress database abstraction object.
   6725 	 *
   6726 	 * @param int $post_ID
   6727 	 * @return array|IXR_Error
   6728 	 */
   6729 	public function mt_getTrackbackPings( $post_ID ) {
   6730 		global $wpdb;
   6731 
   6732 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6733 		do_action( 'xmlrpc_call', 'mt.getTrackbackPings', $post_ID, $this );
   6734 
   6735 		$actual_post = get_post( $post_ID, ARRAY_A );
   6736 
   6737 		if ( ! $actual_post ) {
   6738 			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
   6739 		}
   6740 
   6741 		$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 ) );
   6742 
   6743 		if ( ! $comments ) {
   6744 			return array();
   6745 		}
   6746 
   6747 		$trackback_pings = array();
   6748 		foreach ( $comments as $comment ) {
   6749 			if ( 'trackback' === $comment->comment_type ) {
   6750 				$content           = $comment->comment_content;
   6751 				$title             = substr( $content, 8, ( strpos( $content, '</strong>' ) - 8 ) );
   6752 				$trackback_pings[] = array(
   6753 					'pingTitle' => $title,
   6754 					'pingURL'   => $comment->comment_author_url,
   6755 					'pingIP'    => $comment->comment_author_IP,
   6756 				);
   6757 			}
   6758 		}
   6759 
   6760 		return $trackback_pings;
   6761 	}
   6762 
   6763 	/**
   6764 	 * Sets a post's publish status to 'publish'.
   6765 	 *
   6766 	 * @since 1.5.0
   6767 	 *
   6768 	 * @param array $args {
   6769 	 *     Method arguments. Note: arguments must be ordered as documented.
   6770 	 *
   6771 	 *     @type int    $post_ID
   6772 	 *     @type string $username
   6773 	 *     @type string $password
   6774 	 * }
   6775 	 * @return int|IXR_Error
   6776 	 */
   6777 	public function mt_publishPost( $args ) {
   6778 		$this->escape( $args );
   6779 
   6780 		$post_ID  = (int) $args[0];
   6781 		$username = $args[1];
   6782 		$password = $args[2];
   6783 
   6784 		$user = $this->login( $username, $password );
   6785 		if ( ! $user ) {
   6786 			return $this->error;
   6787 		}
   6788 
   6789 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6790 		do_action( 'xmlrpc_call', 'mt.publishPost', $args, $this );
   6791 
   6792 		$postdata = get_post( $post_ID, ARRAY_A );
   6793 		if ( ! $postdata ) {
   6794 			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
   6795 		}
   6796 
   6797 		if ( ! current_user_can( 'publish_posts' ) || ! current_user_can( 'edit_post', $post_ID ) ) {
   6798 			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
   6799 		}
   6800 
   6801 		$postdata['post_status'] = 'publish';
   6802 
   6803 		// Retain old categories.
   6804 		$postdata['post_category'] = wp_get_post_categories( $post_ID );
   6805 		$this->escape( $postdata );
   6806 
   6807 		return wp_update_post( $postdata );
   6808 	}
   6809 
   6810 	/*
   6811 	 * Pingback functions.
   6812 	 * Specs on www.hixie.ch/specs/pingback/pingback
   6813 	 */
   6814 
   6815 	/**
   6816 	 * Retrieves a pingback and registers it.
   6817 	 *
   6818 	 * @since 1.5.0
   6819 	 *
   6820 	 * @param array $args {
   6821 	 *     Method arguments. Note: arguments must be ordered as documented.
   6822 	 *
   6823 	 *     @type string $pagelinkedfrom
   6824 	 *     @type string $pagelinkedto
   6825 	 * }
   6826 	 * @return string|IXR_Error
   6827 	 */
   6828 	public function pingback_ping( $args ) {
   6829 		global $wpdb;
   6830 
   6831 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   6832 		do_action( 'xmlrpc_call', 'pingback.ping', $args, $this );
   6833 
   6834 		$this->escape( $args );
   6835 
   6836 		$pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
   6837 		$pagelinkedto   = str_replace( '&amp;', '&', $args[1] );
   6838 		$pagelinkedto   = str_replace( '&', '&amp;', $pagelinkedto );
   6839 
   6840 		/**
   6841 		 * Filters the pingback source URI.
   6842 		 *
   6843 		 * @since 3.6.0
   6844 		 *
   6845 		 * @param string $pagelinkedfrom URI of the page linked from.
   6846 		 * @param string $pagelinkedto   URI of the page linked to.
   6847 		 */
   6848 		$pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
   6849 
   6850 		if ( ! $pagelinkedfrom ) {
   6851 			return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
   6852 		}
   6853 
   6854 		// Check if the page linked to is on our site.
   6855 		$pos1 = strpos( $pagelinkedto, str_replace( array( 'http://www.', 'http://', 'https://www.', 'https://' ), '', get_option( 'home' ) ) );
   6856 		if ( ! $pos1 ) {
   6857 			return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
   6858 		}
   6859 
   6860 		/*
   6861 		 * Let's find which post is linked to.
   6862 		 * FIXME: Does url_to_postid() cover all these cases already?
   6863 		 * If so, then let's use it and drop the old code.
   6864 		 */
   6865 		$urltest = parse_url( $pagelinkedto );
   6866 		$post_ID = url_to_postid( $pagelinkedto );
   6867 		if ( $post_ID ) {
   6868 			// $way
   6869 		} elseif ( isset( $urltest['path'] ) && preg_match( '#p/[0-9]{1,}#', $urltest['path'], $match ) ) {
   6870 			// The path defines the post_ID (archives/p/XXXX).
   6871 			$blah    = explode( '/', $match[0] );
   6872 			$post_ID = (int) $blah[1];
   6873 		} elseif ( isset( $urltest['query'] ) && preg_match( '#p=[0-9]{1,}#', $urltest['query'], $match ) ) {
   6874 			// The query string defines the post_ID (?p=XXXX).
   6875 			$blah    = explode( '=', $match[0] );
   6876 			$post_ID = (int) $blah[1];
   6877 		} elseif ( isset( $urltest['fragment'] ) ) {
   6878 			// An #anchor is there, it's either...
   6879 			if ( (int) $urltest['fragment'] ) {
   6880 				// ...an integer #XXXX (simplest case),
   6881 				$post_ID = (int) $urltest['fragment'];
   6882 			} elseif ( preg_match( '/post-[0-9]+/', $urltest['fragment'] ) ) {
   6883 				// ...a post ID in the form 'post-###',
   6884 				$post_ID = preg_replace( '/[^0-9]+/', '', $urltest['fragment'] );
   6885 			} elseif ( is_string( $urltest['fragment'] ) ) {
   6886 				// ...or a string #title, a little more complicated.
   6887 				$title   = preg_replace( '/[^a-z0-9]/i', '.', $urltest['fragment'] );
   6888 				$sql     = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
   6889 				$post_ID = $wpdb->get_var( $sql );
   6890 				if ( ! $post_ID ) {
   6891 					// Returning unknown error '0' is better than die()'ing.
   6892 					return $this->pingback_error( 0, '' );
   6893 				}
   6894 			}
   6895 		} else {
   6896 			// TODO: Attempt to extract a post ID from the given URL.
   6897 			return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
   6898 		}
   6899 		$post_ID = (int) $post_ID;
   6900 
   6901 		$post = get_post( $post_ID );
   6902 
   6903 		if ( ! $post ) { // Post not found.
   6904 			return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
   6905 		}
   6906 
   6907 		if ( url_to_postid( $pagelinkedfrom ) == $post_ID ) {
   6908 			return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
   6909 		}
   6910 
   6911 		// Check if pings are on.
   6912 		if ( ! pings_open( $post ) ) {
   6913 			return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
   6914 		}
   6915 
   6916 		// Let's check that the remote site didn't already pingback this entry.
   6917 		if ( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom ) ) ) {
   6918 			return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
   6919 		}
   6920 
   6921 		// Very stupid, but gives time to the 'from' server to publish!
   6922 		sleep( 1 );
   6923 
   6924 		$remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
   6925 
   6926 		/** This filter is documented in wp-includes/class-http.php */
   6927 		$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $pagelinkedfrom );
   6928 
   6929 		// Let's check the remote site.
   6930 		$http_api_args = array(
   6931 			'timeout'             => 10,
   6932 			'redirection'         => 0,
   6933 			'limit_response_size' => 153600, // 150 KB
   6934 			'user-agent'          => "$user_agent; verifying pingback from $remote_ip",
   6935 			'headers'             => array(
   6936 				'X-Pingback-Forwarded-For' => $remote_ip,
   6937 			),
   6938 		);
   6939 
   6940 		$request                = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
   6941 		$remote_source          = wp_remote_retrieve_body( $request );
   6942 		$remote_source_original = $remote_source;
   6943 
   6944 		if ( ! $remote_source ) {
   6945 			return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
   6946 		}
   6947 
   6948 		/**
   6949 		 * Filters the pingback remote source.
   6950 		 *
   6951 		 * @since 2.5.0
   6952 		 *
   6953 		 * @param string $remote_source Response source for the page linked from.
   6954 		 * @param string $pagelinkedto  URL of the page linked to.
   6955 		 */
   6956 		$remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
   6957 
   6958 		// Work around bug in strip_tags():
   6959 		$remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
   6960 		$remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
   6961 		$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 );
   6962 
   6963 		preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
   6964 		$title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
   6965 		if ( empty( $title ) ) {
   6966 			return $this->pingback_error( 32, __( 'We cannot find a title on that page.' ) );
   6967 		}
   6968 
   6969 		// Remove all script and style tags including their content.
   6970 		$remote_source = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $remote_source );
   6971 		// Just keep the tag we need.
   6972 		$remote_source = strip_tags( $remote_source, '<a>' );
   6973 
   6974 		$p = explode( "\n\n", $remote_source );
   6975 
   6976 		$preg_target = preg_quote( $pagelinkedto, '|' );
   6977 
   6978 		foreach ( $p as $para ) {
   6979 			if ( strpos( $para, $pagelinkedto ) !== false ) { // It exists, but is it a link?
   6980 				preg_match( '|<a[^>]+?' . $preg_target . '[^>]*>([^>]+?)</a>|', $para, $context );
   6981 
   6982 				// If the URL isn't in a link context, keep looking.
   6983 				if ( empty( $context ) ) {
   6984 					continue;
   6985 				}
   6986 
   6987 				// We're going to use this fake tag to mark the context in a bit.
   6988 				// The marker is needed in case the link text appears more than once in the paragraph.
   6989 				$excerpt = preg_replace( '|\</?wpcontext\>|', '', $para );
   6990 
   6991 				// prevent really long link text
   6992 				if ( strlen( $context[1] ) > 100 ) {
   6993 					$context[1] = substr( $context[1], 0, 100 ) . '&#8230;';
   6994 				}
   6995 
   6996 				$marker      = '<wpcontext>' . $context[1] . '</wpcontext>';  // Set up our marker.
   6997 				$excerpt     = str_replace( $context[0], $marker, $excerpt ); // Swap out the link for our marker.
   6998 				$excerpt     = strip_tags( $excerpt, '<wpcontext>' );         // Strip all tags but our context marker.
   6999 				$excerpt     = trim( $excerpt );
   7000 				$preg_marker = preg_quote( $marker, '|' );
   7001 				$excerpt     = preg_replace( "|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt );
   7002 				$excerpt     = strip_tags( $excerpt ); // YES, again, to remove the marker wrapper.
   7003 				break;
   7004 			}
   7005 		}
   7006 
   7007 		if ( empty( $context ) ) { // Link to target not found.
   7008 			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.' ) );
   7009 		}
   7010 
   7011 		$pagelinkedfrom = str_replace( '&', '&amp;', $pagelinkedfrom );
   7012 
   7013 		$context        = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
   7014 		$pagelinkedfrom = $this->escape( $pagelinkedfrom );
   7015 
   7016 		$comment_post_ID      = (int) $post_ID;
   7017 		$comment_author       = $title;
   7018 		$comment_author_email = '';
   7019 		$this->escape( $comment_author );
   7020 		$comment_author_url = $pagelinkedfrom;
   7021 		$comment_content    = $context;
   7022 		$this->escape( $comment_content );
   7023 		$comment_type = 'pingback';
   7024 
   7025 		$commentdata = compact(
   7026 			'comment_post_ID',
   7027 			'comment_author',
   7028 			'comment_author_url',
   7029 			'comment_author_email',
   7030 			'comment_content',
   7031 			'comment_type',
   7032 			'remote_source',
   7033 			'remote_source_original'
   7034 		);
   7035 
   7036 		$comment_ID = wp_new_comment( $commentdata );
   7037 
   7038 		if ( is_wp_error( $comment_ID ) ) {
   7039 			return $this->pingback_error( 0, $comment_ID->get_error_message() );
   7040 		}
   7041 
   7042 		/**
   7043 		 * Fires after a post pingback has been sent.
   7044 		 *
   7045 		 * @since 0.71
   7046 		 *
   7047 		 * @param int $comment_ID Comment ID.
   7048 		 */
   7049 		do_action( 'pingback_post', $comment_ID );
   7050 
   7051 		/* translators: 1: URL of the page linked from, 2: URL of the page linked to. */
   7052 		return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
   7053 	}
   7054 
   7055 	/**
   7056 	 * Retrieve array of URLs that pingbacked the given URL.
   7057 	 *
   7058 	 * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
   7059 	 *
   7060 	 * @since 1.5.0
   7061 	 *
   7062 	 * @global wpdb $wpdb WordPress database abstraction object.
   7063 	 *
   7064 	 * @param string $url
   7065 	 * @return array|IXR_Error
   7066 	 */
   7067 	public function pingback_extensions_getPingbacks( $url ) {
   7068 		global $wpdb;
   7069 
   7070 		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
   7071 		do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks', $url, $this );
   7072 
   7073 		$url = $this->escape( $url );
   7074 
   7075 		$post_ID = url_to_postid( $url );
   7076 		if ( ! $post_ID ) {
   7077 			// We aren't sure that the resource is available and/or pingback enabled.
   7078 			return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
   7079 		}
   7080 
   7081 		$actual_post = get_post( $post_ID, ARRAY_A );
   7082 
   7083 		if ( ! $actual_post ) {
   7084 			// No such post = resource not found.
   7085 			return $this->pingback_error( 32, __( 'The specified target URL does not exist.' ) );
   7086 		}
   7087 
   7088 		$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 ) );
   7089 
   7090 		if ( ! $comments ) {
   7091 			return array();
   7092 		}
   7093 
   7094 		$pingbacks = array();
   7095 		foreach ( $comments as $comment ) {
   7096 			if ( 'pingback' === $comment->comment_type ) {
   7097 				$pingbacks[] = $comment->comment_author_url;
   7098 			}
   7099 		}
   7100 
   7101 		return $pingbacks;
   7102 	}
   7103 
   7104 	/**
   7105 	 * Sends a pingback error based on the given error code and message.
   7106 	 *
   7107 	 * @since 3.6.0
   7108 	 *
   7109 	 * @param int    $code    Error code.
   7110 	 * @param string $message Error message.
   7111 	 * @return IXR_Error Error object.
   7112 	 */
   7113 	protected function pingback_error( $code, $message ) {
   7114 		/**
   7115 		 * Filters the XML-RPC pingback error return.
   7116 		 *
   7117 		 * @since 3.5.1
   7118 		 *
   7119 		 * @param IXR_Error $error An IXR_Error object containing the error code and message.
   7120 		 */
   7121 		return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
   7122 	}
   7123 }